de.betterform.xml.xforms.Container.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.xml.xforms.Container.java

Source

/*
 * Copyright (c) 2012. betterFORM Project - http://www.betterform.de
 * Licensed under the terms of BSD License
 */

package de.betterform.xml.xforms;

import de.betterform.connector.ConnectorFactory;
import de.betterform.xml.config.XFormsConfigException;
import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.events.DOMEventNames;
import de.betterform.xml.events.XFormsEventNames;
import de.betterform.xml.events.XMLEventService;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.ns.NamespaceResolver;
import de.betterform.xml.xforms.exception.XFormsErrorIndication;
import de.betterform.xml.xforms.exception.XFormsException;
import de.betterform.xml.xforms.model.Model;
import de.betterform.xml.xforms.model.bind.BindingResolver;
import de.betterform.xml.xforms.ui.AbstractFormControl;
import de.betterform.xml.xforms.ui.Group;
import de.betterform.xml.xforms.ui.Repeat;
import de.betterform.xml.xforms.ui.Switch;
import de.betterform.xml.xpath.impl.saxon.BetterFormXPathContext;
import de.betterform.xml.xpath.impl.saxon.XPathCache;
import net.sf.saxon.Configuration;
import net.sf.saxon.dom.DocumentWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.xforms.XFormsModelElement;

import java.util.*;

/**
 * This class represents a complete XForms document. It encapsulates the DOM
 * document.
 * <p/>
 * The XForms document may consist of pure XForms markup or may contain mixed
 * markup from other namespaces (e.g. HTML, SVG, WAP).
 * <p/>
 * Responsibilities are: <ul> <li>model creation and initialization</li>
 * <li>event creation, dispatching and error handling</li> <li>element
 * registry</li> </ul>
 *
 * @author Joern Turner
 * @author Ulrich Nicolas Liss&eacute;
 * @author Eduardo Millan <emillan@users.sourceforge.net>
 * @version $Id: Container.java 3492 2008-08-27 12:37:01Z joern $
 */
public class Container {
    private static final Log LOGGER = LogFactory.getLog(Container.class);
    private final Configuration fConfiguration = new Configuration();
    private BindingResolver bindingResolver;
    private XFormsProcessorImpl processor;
    private ConnectorFactory connectorFactory;
    private Document document;
    private Element root;
    private XMLEventService eventService;
    private List<Model> models;
    private Map xFormsElements;
    private XFormsElementFactory elementFactory;
    private CustomElementFactory customElementFactory;
    private boolean modelConstructDone = false;
    private int idCounter = 0;
    private List eventExceptions;
    public static final String XFORMS_1_0 = "1.0";
    public static final String XFORMS_1_1 = "1.1";
    private BetterFormXPathContext hostContext;
    private String version;
    private Stack<EventInfo> fEventInfoStack = new Stack<EventInfo>();
    private String focussedControlId = null;
    private String focussedContainerId = null;

    /**
     * associates DocumentContainer with Processor.
     *
     * @param processor the Processor object
     */
    public Container(XFormsProcessorImpl processor) {
        this.processor = processor;
    }

    /**
     * returns the processor for this container
     *
     * @return the processor for this container
     */
    public XFormsProcessorImpl getProcessor() {
        return processor;
    }

    /**
     * Returns the binding resolver.
     *
     * @return the binding resolver.
     */
    public BindingResolver getBindingResolver() {
        if (this.bindingResolver == null) {
            this.bindingResolver = new BindingResolver();
        }

        return this.bindingResolver;
    }

    /**
     * Returns the connector factory.
     *
     * @return the connector factory.
     */
    public ConnectorFactory getConnectorFactory() {
        if (this.connectorFactory == null) {
            try {
                this.connectorFactory = ConnectorFactory.getFactory();
                this.connectorFactory.setContext(this.processor.getContext());
            } catch (XFormsConfigException xce) {
                throw new RuntimeException(xce);
            }
        }

        return this.connectorFactory;
    }

    /**
     * Returns the XForms element factory which is responsible for creating
     * objects for all XForms elements in the input document.
     *
     * @return the XForms element factory
     * @see XFormsElementFactory
     */
    public XFormsElementFactory getElementFactory() {
        if (this.elementFactory == null) {
            this.elementFactory = new XFormsElementFactory();
        }

        return this.elementFactory;
    }

    /**
     * Returns the custom element factory which is responsible for creating
     * objects for all custom elements in the input document.
     *
     * @return the custom element factory
     * @see CustomElementFactory
     */
    public CustomElementFactory getCustomElementFactory() throws XFormsException {
        if (this.customElementFactory == null) {
            this.customElementFactory = new CustomElementFactory();
        }

        return this.customElementFactory;
    }

    /**
     * Returns the XML Event Service.
     *
     * @return the XML Event Service.
     */
    public XMLEventService getXMLEventService() {
        return this.eventService;
    }

    /**
     * returns the id of the currently focussed  control if any. This value will be set exclusively by an
     * setfocus action.
     *
     * @return the id of the currently focussed  control if any or null if undefined (no setfocus was called on the form)
     */
    public String getFocussedControlId() {
        return this.focussedControlId;
    }

    /**
     * sets the id of the currently focussed control
     *
     * @param id - an id of an existing focussable control
     */
    public void setFocussedControlId(String id) {
        this.focussedControlId = id;
    }

    public String getFocussedContainerId() {
        return this.focussedContainerId;
    }

    public void setFocussedContainerId(String id) {
        this.focussedContainerId = id;
    }

    /**
     * Sets the XML Event Service.
     *
     * @param eventService the XML Event Service.
     */
    public void setXMLEventService(XMLEventService eventService) {
        this.eventService = eventService;
    }

    /**
     * passes the XML container document which contains XForms markup. This
     * method must be called before init() and already builds up the
     * NamespaceResolver and RootContext objects.
     *
     * @param document a DOM Document
     */
    public void setDocument(Document document) {
        this.document = document;
        this.root = this.document.getDocumentElement();

        // check for betterform namespace
        if (!this.root.hasAttributeNS(NamespaceConstants.XMLNS_NS, NamespaceConstants.BETTERFORM_PREFIX)) {
            this.root.setAttributeNS(NamespaceConstants.XMLNS_NS, "xmlns:" + NamespaceConstants.BETTERFORM_PREFIX,
                    NamespaceConstants.BETTERFORM_NS);
        }

        NamespaceResolver.init(root);
    }

    /**
     * Returns the container as a dom tree representing an (external) XML
     * representation of the xforms container.  The return value is live, that
     * means changes to the return tree affects the internal container
     * representation.
     *
     * @return the container as a document.
     */
    public Document getDocument() {
        return this.document;
    }

    //    public NodeInfo getRootNodeInfo() {
    //        return new DocumentWrapper(document, this.processor.getBaseURI(), new IndependentContext().getConfiguration()).wrap(document.getDocumentElement());
    //    }

    /**
     * stores this container as userobject in document element and triggers
     * model initialization
     *
     * @throws XFormsException
     */
    public void init() throws XFormsException {
        this.root.setUserData("", this, null);

        //        checkVersionCompatibility();

        // trigger model initialization
        initModels();

    }

    /**
     * @return the version of XForms required by form author or if attribute is not present "1.1" as default.
     *         <p/>
     *         todo: checks only the default model for now. This must be improved
     */
    public String getVersion() throws XFormsException {
        return this.version;
    }

    /**
     * Triggers model destruction.
     *
     * @throws XFormsException
     */
    public void shutdown() throws XFormsException {
        if (this.models != null) {
            Model model;
            for (int index = 0; index < this.models.size(); index++) {
                model = (Model) this.models.get(index);
                // todo: release resources in Model.performDefault()
                dispatch(model.getTarget(), XFormsEventNames.MODEL_DESTRUCT, null);
            }
        }
    }

    /**
     * Adds a DOM Event Listener to the specified target node.
     *
     * @param targetId      the id of the target node.
     * @param eventType     the event type.
     * @param eventListener the Event Listener.
     * @param useCapture    use event capturing or not.
     * @throws XFormsException if the target node could not be found.
     * todo: remove this method - unused
     */
    public void addEventListener(String targetId, String eventType, EventListener eventListener, boolean useCapture)
            throws XFormsException {
        EventTarget eventTarget = lookupEventTarget(targetId);
        if (eventTarget != null) {
            eventTarget.addEventListener(eventType, eventListener, useCapture);
            return;
        }

        throw new XFormsException("Unable to add eventlistener. Event target '" + targetId + "' not found");
    }

    /**
     * Removes a DOM Event Listener from the specified target node.
     *
     * @param targetId      the id of the target node.
     * @param eventType     the event type.
     * @param eventListener the Event Listener.
     * @param useCapture    use event capturing or not.
     * @throws XFormsException if the target node could not be found.
     * todo: remove this method - unused
     */
    public void removeEventListener(String targetId, String eventType, EventListener eventListener,
            boolean useCapture) throws XFormsException {
        EventTarget eventTarget = lookupEventTarget(targetId);
        if (eventTarget != null) {
            eventTarget.addEventListener(eventType, eventListener, useCapture);
            return;
        }

        throw new XFormsException("Unable to remove eventlistener. Event target '" + targetId + "' not found");
    }

    /**
     * Dispatches a DOM Event to the specified target node.
     *
     * @param targetId  the id of the target node.
     * @param eventType the event type.
     * @return <code>true</code> if the event has been cancelled during dispatch,
     *         otherwise <code>false</code>.
     * @throws XFormsException if the target node could not be found.
     */
    public boolean dispatch(String targetId, String eventType) throws XFormsException {
        return dispatch(targetId, eventType, null);

    }

    /**
     * Dispatches a DOM Event to the specified target node.
     *
     * @param targetId  the id of the target node.
     * @param eventType the event type.
     * @param info      a context information object.
     * @return <code>true</code> if the event has been cancelled during dispatch,
     *         otherwise <code>false</code>.
     * @throws XFormsException if the target node could not be found.
     */
    public boolean dispatch(String targetId, String eventType, Object info) throws XFormsException {
        return dispatch(targetId, eventType, info, true, true);
    }

    /**
     * Dispatches a DOM Event to the specified target node.
     * <p/>
     * The bubbles and cancelable parameters are ignored for well-known event types.
     *
     * @param targetId   the id of the target node.
     * @param eventType  the event type.
     * @param info       a context information object.
     * @param bubbles    specifies wether the event can bubble.
     * @param cancelable specifies wether the event's default action can be prevented.
     * @return <code>true</code> if the event has been cancelled during dispatch,
     *         otherwise <code>false</code>.
     * @throws XFormsException if the target node could not be found.
     */
    public boolean dispatch(String targetId, String eventType, Object info, boolean bubbles, boolean cancelable)
            throws XFormsException {
        XFormsElement xFormsElement = lookup(targetId);
        if (xFormsElement != null && xFormsElement instanceof AbstractFormControl
                && (DOMEventNames.FOCUS_IN.equals(eventType) || DOMEventNames.FOCUS_OUT.equals(eventType))) {
            // if focussedContainerId xforms element does not exist set focussedContainerId to null
            if (!this.xFormsElements.containsKey(focussedContainerId)) {
                focussedContainerId = null;
            }
            //fetch parent XForms Element if any
            XFormsElement parent = xFormsElement.getEnclosingXFormsContainer();
            if (parent == null) {
                if (focussedContainerId != null) {
                    dispatch(focussedContainerId, DOMEventNames.FOCUS_OUT);
                }
                focussedContainerId = null;
            }
            //todo: ...or switch or repeat
            else if (parent instanceof Group || parent instanceof Switch || parent instanceof Repeat) {
                //check if parent group has not(!) already the focus
                if (!(parent.getId().equals(focussedContainerId))) {
                    if (focussedContainerId != null) {
                        dispatch(focussedContainerId, DOMEventNames.FOCUS_OUT);
                    }
                    //remember current group
                    this.focussedContainerId = parent.getId();
                    //if not dispatch to group
                    if (DOMEventNames.FOCUS_IN.equals(eventType)) {
                        dispatch(parent.getId(), DOMEventNames.FOCUS_IN);
                    }
                }
            }
        }

        EventTarget eventTarget = lookupEventTarget(targetId);
        if (eventTarget != null) {
            return dispatch(eventTarget, eventType, info, bubbles, cancelable);
        }

        throw new XFormsException("event target '" + targetId + "' not found for event '" + eventType + "'");
    }

    /**
     * Dispatches a DOM Event to the specified target node.
     *
     * @param eventTarget the target node.
     * @param eventType   the event type.
     * @param info        a context information object.
     * @return <code>true</code> if the event has been cancelled during dispatch,
     *         otherwise <code>false</code>.
     * @throws XFormsException if the target node could not be found.
     */
    public boolean dispatch(EventTarget eventTarget, String eventType, Object info) throws XFormsException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("dispatching event '" + eventType + "' to target '"
                    + DOMUtil.getCanonicalPath((Node) eventTarget) + "'");
        }
        return dispatch(eventTarget, eventType, info, true, true);
    }

    /**
     * Dispatches a DOM Event to the specified target node.
     * <p/>
     * The bubbles and cancelable parameters are ignored for well-known event types.
     *
     * @param eventTarget the target node.
     * @param eventType   the event type.
     * @param info        a context information object.
     * @param bubbles     specifies wether the event can bubble.
     * @param cancelable  specifies wether the event's default action can be prevented.
     * @return <code>true</code> if the event has been cancelled during dispatch,
     *         otherwise <code>false</code>.
     * @throws XFormsException if the target node could not be found.
     */
    public boolean dispatch(EventTarget eventTarget, String eventType, Object info, boolean bubbles,
            boolean cancelable) throws XFormsException {
        boolean result = false;
        XFormsException xFormsException = null;
        try {
            fEventInfoStack.push(new EventInfo(eventTarget, eventType, info, bubbles, cancelable));
            result = this.eventService.dispatch(eventTarget, eventType, bubbles, cancelable, info);
        } finally {
            fEventInfoStack.pop();
            // exception(s) during event flow ?
            if (this.eventExceptions != null && this.eventExceptions.size() > 0) {
                Exception exception = (Exception) this.eventExceptions.get(0);

                if (exception instanceof XFormsErrorIndication) {
                    if (((XFormsErrorIndication) exception).isFatal()) {
                        // downcast fatal error for rethrowal
                        xFormsException = (XFormsException) exception;
                    } else {
                        LOGGER.warn("dispatch: non-fatal xforms error", exception);
                    }
                } else if (exception instanceof XFormsException) {
                    // downcast exception for rethrowal
                    xFormsException = (XFormsException) exception;
                } else {
                    // wrap exception for rethrowal
                    xFormsException = new XFormsException(exception);
                }

                // get rid of follow-up exceptions
                this.eventExceptions.clear();
            }
        }

        if (xFormsException != null) {
            // rethrow exception
            LOGGER.error("dispatch: exception during event flow", xFormsException);
            throw xFormsException;
        }

        return result;
    }

    /**
     * Stores an exception until the currently ongoing event flow has finished.
     *
     * @param exception an exception occurring during event flow.
     */
    public void handleEventException(Exception exception) {

        LOGGER.warn("handle event exception: " + exception.getClass().getName()
                + " kept for rethrowal after dispatch() has finished");

        if (this.eventExceptions == null) {
            this.eventExceptions = new ArrayList();
        }

        if (exception instanceof XFormsErrorIndication) {
            XFormsErrorIndication indication = (XFormsErrorIndication) exception;

            LOGGER.warn("XForms Error: " + indication.getMessage());
            if (!indication.isHandled()) {
                // dispatch error indication event
                try {
                    dispatch(indication.getEventTarget(), indication.getEventType(), indication.getContextInfo());
                } catch (XFormsException e) {
                    LOGGER.error("handle event exception: exception during error indication event", e);
                }

                // set error indication handled
                indication.setHandled();
            }
        }

        // keep exception
        this.eventExceptions.add(exception);
    }

    /**
     * Returns the specified XForms element.
     *
     * @param id the id of the XForms element.
     * @return the specified XForms element or <code>null</code> if the id is
     *         unknown.
     */
    public XFormsElement lookup(String id) {
        if (this.xFormsElements != null) {
            return (XFormsElement) this.xFormsElements.get(id);
        }

        return null;
    }

    /**
     * Generates an unique identifier.
     *
     * @return an unique identifier.
     */
    public String generateId() {
        // todo: build external is service
        String id = "C" + (++this.idCounter);

        while (lookup(id) != null) {
            id = "C" + (++this.idCounter);
        }

        return id;
    }

    /**
     * Registers the specified XForms element with this <code>container</code>.
     * <p/>
     * Attaches Container as listener for XForms exception events.
     *
     * @param element the XForms element to be registered.
     */
    public void register(XFormsElement element) {
        if (this.xFormsElements == null) {
            this.xFormsElements = new HashMap();
        }

        this.xFormsElements.put(element.getId(), element);
    }

    /**
     * Deregisters the specified XForms element with this
     * <code>container</code>.
     *
     * @param element the XForms element to be deregistered.
     */
    public void deregister(XFormsElement element) {
        if (this.xFormsElements != null) {
            this.xFormsElements.remove(element.getId());
        }
    }

    /**
     * convenience method to return default model without knowing its id.
     *
     * @return returns the first model in document order
     */
    public Model getDefaultModel() throws XFormsException {
        return getModel(null);
    }

    /**
     * return a model object by its id. If id is null or an empty string, the
     * default model (first found in document order) is returned.
     */
    public Model getModel(String id) throws XFormsException {
        if ((id == null) || (id.length() == 0)) {
            if (this.models != null && this.models.size() > 0) {
                return (Model) this.models.get(0);
            }

            throw new XFormsException("default model not found");
        }

        if (this.models != null) {
            Model model;
            for (int index = 0; index < this.models.size(); index++) {
                model = (Model) this.models.get(index);

                if (model.getId().equals(id)) {
                    return model;
                }
            }
        }

        throw new XFormsException("model '" + id + "' not found");
    }

    /**
     * Returns all models.
     *
     * @return all models.
     */
    public List getModels() {
        return this.models;
    }

    /**
     * returns true, if the default-processing for xforms-model-construct-done
     * Event has been executed already.
     *
     * @return true, if the default-processing for xforms-model-construct-done
     *         Event has been executed already.
     */
    public boolean isModelConstructDone() {
        return this.modelConstructDone;
    }

    /**
     * create Model-objects which simply hold their Model-element node (formerly
     * named XForm-element).
     * <p/>
     * The first Model-element found in the container is the default-model and
     * if it has no model-id it is stored with a key of 'default' in the models
     * hashtable. Otherwise the provided id is used.
     * <p/>
     * The subsequent model-elements are stored with their id as the key. If no
     * id exists an exception is thrown (as defined by Spec).
     * <p/>
     */
    private void initModels() throws XFormsException {
        this.models = new ArrayList();
        List<Element> modelElements = getModelElements();
        Model model;
        Element modelElement;

        // create all models and dispatch xforms-model-construct to all models
        final int nrOfModels = modelElements.size();
        if (nrOfModels == 0) {
            return;
        }

        for (int i = 0; i < nrOfModels; i++) {
            modelElement = modelElements.get(i);

            model = (Model) getElementFactory().createXFormsElement(modelElement, null);
            this.models.add(model);
        }

        for (int i = 0; i < nrOfModels; i++) {
            boolean isCompatible = true;
            model = (Model) this.models.get(i);
            model.init();

            if (i == 0) {
                isCompatible = checkVersionCompatibility();

            }

            if (!(isCompatible)) {
                return;
            }

            Initializer.initializeModelConstructActionElements(model, model.getElement());
            dispatch(model.getTarget(), XFormsEventNames.MODEL_CONSTRUCT, null);
        }

        for (int i = 0; i < nrOfModels; i++) {
            model = (Model) this.models.get(i);
            dispatch(model.getTarget(), XFormsEventNames.MODEL_CONSTRUCT_DONE, null);

            // set flag to signal that construction has been performed
            this.modelConstructDone = true;
        }

        for (int i = 0; i < nrOfModels; i++) {
            model = (Model) this.models.get(i);
            dispatch(model.getTarget(), XFormsEventNames.READY, null);
        }
    }

    /**
     * Creates at runtime all models specified within an given element
     * Used to initialize embedded forms at runtime
     *
     * @param startElement
     * @throws XFormsException
     */
    public void createEmbeddedForm(Element startElement) throws XFormsException {
        // receive all models from given element
        List<Element> modelElements = getModelElements(startElement);
        final int nrOfModels = modelElements.size();
        // list containing all model of embded form
        ArrayList<Model> embeddedModels = new ArrayList<Model>(nrOfModels);
        for (int i = 0; i < nrOfModels; i++) {
            Element modelElement = modelElements.get(i);
            Model model = (Model) getElementFactory().createXFormsElement(modelElement, null);
            this.models.add(model);
            embeddedModels.add(model);
        }

        boolean isCompatible = false;
        for (int i = 0; i < nrOfModels; i++) {
            if (i == 0) {
                isCompatible = checkVersionCompatibility();
            }
            Model model = embeddedModels.get(i);
            model.init();
            if (!(isCompatible)) {
                return;
            }
            dispatch(model.getTarget(), XFormsEventNames.MODEL_CONSTRUCT, null);
        }

        for (Model model : embeddedModels) {
            dispatch(model.getTarget(), XFormsEventNames.MODEL_CONSTRUCT_DONE, null);
            // set up UI for specific model
            Initializer.initializeUIElements(model, startElement, null, null);
        }
        for (Model model : embeddedModels) {
            dispatch(model.getTarget(), XFormsEventNames.READY, null);
        }

    }

    /**
     *
     * @param start
     * @return
     */
    private List<Element> getModelElements(Element start) {
        List<Element> result = new ArrayList<Element>();
        addModelElements(start, result);
        return result;
    }

    /**
     *
     * @return
     */
    private List<Element> getModelElements() {
        return getModelElements(root);
    }

    private void addModelElements(Node parent, List<Element> models) {
        for (Node it = parent.getFirstChild(); it != null; it = it.getNextSibling()) {
            if (it.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }

            if (NamespaceConstants.XFORMS_NS.equals(it.getNamespaceURI())) {
                Element el = (Element) it;
                if (XFormsConstants.MODEL.equals(el.getLocalName())) {
                    models.add(el);
                }
                // Model is a top level XForms element so we don't need to go in recursion here
                else {
                    addModelElements(it, models);
                }
            } else {
                addModelElements(it, models);
            }
        }
    }

    public void removeModel(Model model) {
        if (this.models.contains(model)) {
            this.models.remove(model);
        }
    }

    public Element getElementById(String id) throws XFormsException {
        String baseURI = getProcessor().getBaseURI();
        return (Element) XPathCache.getInstance().evaluateAsSingleNode(getHostContext(baseURI),
                "//*[@id='" + id + "']");
    }

    /**
     * todo:prefixMapping for Host document needed
     *
     * @param baseURI
     * @return
     */
    public BetterFormXPathContext getHostContext(String baseURI) {
        //lazy instanciation of BetterFormContext
        if (this.hostContext == null) {
            List nodes = de.betterform.xml.xpath.impl.saxon.XPathUtil.getRootContext(document, baseURI);
            this.hostContext = new BetterFormXPathContext(nodes, 1, Collections.EMPTY_MAP, null);
        }
        return this.hostContext;
    }

    /**
     * Returns the specified Event target.
     *
     * @param id the id of the Event target.
     * @return the specified Event target or <code>null</code> if the id is
     *         unknown.
     */
    private EventTarget lookupEventTarget(String id) throws XFormsException {
        XFormsElement xFormsElement = lookup(id);
        if (xFormsElement != null) {
            return xFormsElement.getTarget();
        }

        return (EventTarget) getElementById(id);
    }

    /**
     * check if the processor supports the version required by model
     *
     * @return
     * @throws XFormsException
     */
    private boolean checkVersionCompatibility() throws XFormsException {
        String versionString = null;
        final Element defaultModelElement = models.get(0).getElement();

        for (int i = 0; i < models.size(); i++) {
            Element modelElement = models.get(i).getElement();
            versionString = XFormsElement.getXFormsAttribute(modelElement, "version");

            if (i == 0) {
                //Default Model
                this.version = checkForValidVersion(versionString);

                if (this.version == null) {
                    HashMap contextInfo = new HashMap();
                    contextInfo.put("error-information",
                            "version setting of default model not supported: '" + versionString + "'");
                    dispatch((EventTarget) defaultModelElement, XFormsEventNames.VERSION_EXCEPTION, contextInfo);
                    return false;
                }
            } else {
                String versionTmp = checkForValidVersion(versionString);

                if (versionTmp == null || versionTmp.compareTo(this.version) > 0) {
                    HashMap contextInfo = new HashMap();
                    contextInfo.put("error-information", "Incompatible version setting: " + versionString
                            + " on model: " + DOMUtil.getCanonicalPath(modelElement));

                    dispatch((EventTarget) defaultModelElement, XFormsEventNames.VERSION_EXCEPTION, contextInfo);
                    return false;
                }
            }
        }
        LOGGER.info("running XForms version" + this.version);
        return true;
    }

    protected String checkForValidVersion(String versionAttribute) {
        String versionTmp = null;

        //default setting for betterForm currently
        if (versionAttribute == null || versionAttribute.equals("")) {
            return XFORMS_1_1;

        } else {
            String[] versionArray = versionAttribute.trim().split(" ");

            for (int j = 0; j < versionArray.length; j++) {
                if (XFORMS_1_1.equals(versionArray[j])) {
                    return XFORMS_1_1;
                } else if (XFORMS_1_0.equals(versionArray[j])) {
                    versionTmp = XFORMS_1_0;
                    //be nice an search further on for 1.1
                }
            }
        }

        return versionTmp;
    }

    /**
     * trigger a global refresh of all models. This can be necessary e.g. if the locale changes dynamically.
     */
    public void refresh() throws XFormsException {
        if (isModelConstructDone()) {
            List models = getModels();
            for (int i = 0; i < models.size(); i++) {
                XFormsModelElement model = (XFormsModelElement) models.get(i);
                model.refresh();
            }
        }
    }

    /**
     * @return Returns the current event info if any
     */
    public EventInfo getCurrentEventInfo() {
        if (fEventInfoStack.isEmpty()) {
            return null;
        }
        return fEventInfoStack.peek();
    }

    public Configuration getConfiguration() {
        return fConfiguration;
    }

    public static class EventInfo {
        private final EventTarget eventTarget;
        private final String eventType;
        private final Object info;
        private final boolean bubbles;
        private final boolean cancelable;

        /**
         * @param eventTarget
         * @param eventType
         * @param info
         * @param bubbles
         * @param cancelable
         */
        public EventInfo(EventTarget eventTarget, String eventType, Object info, boolean bubbles,
                boolean cancelable) {
            this.eventTarget = eventTarget;
            this.eventType = eventType;
            this.info = info;
            this.bubbles = bubbles;
            this.cancelable = cancelable;
        }

        /**
         * @return the eventTarget
         */
        public EventTarget getEventTarget() {
            return eventTarget;
        }

        /**
         * @return the eventType
         */
        public String getEventType() {
            return eventType;
        }

        /**
         * @return the info
         */
        public Object getInfo() {
            return info;
        }

        /**
         * @return the bubbles
         */
        public boolean bubbles() {
            return bubbles;
        }

        /**
         * @return the cancelable
         */
        public boolean isCancelable() {
            return cancelable;
        }
    }

    private Map<Document, DocumentWrapper> fgDocumentWrapperCache = new HashMap<Document, DocumentWrapper>();

    public DocumentWrapper getDocumentWrapper(Node n) {
        final Document ownerDocument = n.getOwnerDocument();
        DocumentWrapper documentWrapper = fgDocumentWrapperCache.get(ownerDocument);
        if (documentWrapper == null) {
            documentWrapper = new DocumentWrapper(ownerDocument, getProcessor().getBaseURI(), getConfiguration());
            fgDocumentWrapperCache.put(ownerDocument, documentWrapper);
        }

        return documentWrapper;
    }
}