de.betterform.xml.xforms.model.submission.Submission.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.xml.xforms.model.submission.Submission.java

Source

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

package de.betterform.xml.xforms.model.submission;

import de.betterform.BetterFORMConstants;
import de.betterform.connector.SubmissionHandler;
import de.betterform.connector.http.AbstractHTTPConnector;
import de.betterform.generator.XSLTGenerator;
import de.betterform.xml.config.Config;
import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.events.BetterFormEventNames;
import de.betterform.xml.events.DefaultAction;
import de.betterform.xml.events.XFormsEventNames;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.ns.NamespaceResolver;
import de.betterform.xml.xforms.*;
import de.betterform.xml.xforms.action.UpdateHandler;
import de.betterform.xml.xforms.exception.*;
import de.betterform.xml.xforms.model.Instance;
import de.betterform.xml.xforms.model.Model;
import de.betterform.xml.xforms.model.bind.Bind;
import de.betterform.xml.xforms.model.constraints.RelevanceSelector;
import de.betterform.xml.xforms.model.constraints.SubmissionValidatorMode;
import de.betterform.xml.xforms.ui.BindingElement;
import de.betterform.xml.xforms.ui.UIElementState;
import de.betterform.xml.xforms.ui.state.BoundElementState;
import de.betterform.xml.xforms.xpath.saxon.function.XPathFunctionContext;
import de.betterform.xml.xpath.impl.saxon.XPathCache;
import de.betterform.xml.xpath.impl.saxon.XPathUtil;
import de.betterform.xml.xslt.impl.CachingTransformerService;
import de.betterform.xml.xslt.impl.ClasspathResourceResolver;
import net.sf.saxon.dom.DocumentWrapper;
import net.sf.saxon.om.Item;
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.NodeList;
import org.w3c.dom.events.Event;

import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * Implementation of XForms Submission Element.
 *
 * @author Ulrich Nicolas Lissé, Joern Turner, Lars Windauer
 * @version $Id: Submission.java 3510 2008-08-31 14:39:56Z lars $
 */
public class Submission extends BindingElement implements DefaultAction {
    private static Log LOGGER = LogFactory.getLog(Submission.class);

    private String action = null;
    private AttributeOrValueChild method = null;
    private String version = null;
    private Boolean indent = null;
    private String mediatype = null;
    private String encoding = null;
    private Boolean omitxmldeclaration = null;
    private Boolean standalone = null;
    private String cdatasectionelements = null;
    private String separator = null;
    private List<String> includenamespaceprefixes = null;
    private String replace = null;
    private String instance = null;
    private String targetExpr = null;

    private Boolean validate = null;
    private Boolean relevant = null;
    private AttributeOrValueChild resource = null;
    protected ArrayList<Header> submissionHeaders = new ArrayList<Header>();
    private String targetModelId;
    private String serialization;
    private static final String EMBEDNODE = "embedElement";
    private static final String DOCUMENT = "document";

    /**
     * Creates a new Submission object.
     *
     * @param element DOM Element of this submission
     * @param model   the parent Model
     */
    public Submission(Element element, Model model) {
        super(element, model);
    }

    // todo: refactor submission driver to have setters for these (IOC)
    // submission options

    /**
     * Returns the <code>action</code> submission option.
     *
     * @return the <code>action</code> submission option.
     */
    @Deprecated
    public String getAction() {
        return this.action;
    }

    /**
     * Returns the <code>resource</code> submission option.
     *
     * @return the <code>resource</code> submission option.
     * @throws XFormsException 
     */
    public String getResource() throws XFormsException {
        return this.resource.getValue();
    }

    /**
     * Returns the <code>cdata-section-elements</code> submission option.
     *
     * @return the <code>cdata-section-elements</code> submission option.
     */
    public String getCDATASectionElements() {
        return this.cdatasectionelements;
    }

    /**
     * Returns the <code>encoding</code> submission option.
     *
     * @return the <code>encoding</code> submission option.
     */
    public String getEncoding() {
        return this.encoding;
    }

    /**
     * Returns the <code>includenamespaceprefixes</code> submission option.
     *
     * @return the <code>includenamespaceprefixes</code> submission option.
     */
    public List<String> getIncludeNamespacePrefixes() {
        return this.includenamespaceprefixes;
    }

    /**
     * Returns the <code>indent</code> submission option.
     *
     * @return the <code>indent</code> submission option.
     */
    public Boolean getIndent() {
        return this.indent;
    }

    /**
     * Returns the <code>mediatype</code> submission option.
     *
     * @return the <code>mediatype</code> submission option.
     */
    public String getMediatype() {
        return this.mediatype;
    }

    /**
     * Returns the <code>method</code> submission option.
     *
     * @return the <code>method</code> submission option.
     * @throws XFormsException 
     */
    public String getMethod() throws XFormsException {
        return this.method.getValue();
    }

    /**
     * Returns the <code>omit-xml-declaration</code> submission option.
     *
     * @return the <code>omit-xml-declaration</code> submission option.
     */
    public Boolean getOmitXMLDeclaration() {
        return this.omitxmldeclaration;
    }

    /**
     * Returns the <code>separator</code> submission option.
     *
     * @return the <code>separator</code> submission option.
     */
    public String getSeparator() {
        return this.separator;
    }

    /**
     * Returns the <code>standalone</code> submission option.
     *
     * @return the <code>standalone</code> submission option.
     */
    public Boolean getStandalone() {
        return this.standalone;
    }

    /**
     * Returns the <code>version</code> submission option.
     *
     * @return the <code>version</code> submission option.
     */
    public String getVersion() {
        return this.version;
    }

    /**
     * Returns the submission <code>replace</code> mode.
     *
     * @return the submission <code>replace</code> mode.
     */
    public String getReplace() {
        return this.replace;
    }

    /**
     * Returns the <code>instance</code> submission option.
     *
     * @return the <code>instance</code> submission option.
     */
    public String getInstance() {
        return this.instance;
    }

    // XForms 1.1 support

    /**
     * Returns the <code>relevant</code> submission option.
     *
     * @return the <code>relevant</code> submission option.
     */
    public Boolean getRelevant() {
        return this.relevant;
    }

    /**
     * Returns the <code>validate</code> submission option.
     *
     * @return the <code>validate</code> submission option.
     */
    public Boolean getValidate() {
        return this.validate;
    }

    public String getLocationPath() {
        return this.locationPath;
    }

    public String getSerialization() {
        return this.serialization;
    }

    // lifecycle methods

    /**
     * Performs element init.
     *
     * @throws XFormsException if any error occurred during init.
     *                         todo: should call BindingElement.init() but some methods have to be refactored first
     */
    public void init() throws XFormsException {
        if (getLogger().isTraceEnabled()) {
            getLogger().trace(this + " init");
        }

        initializeDefaultAction();
        initializeInstanceNode();
        updateXPathContext();
        initializeSubmission();
        Initializer.initializeActionElements(this.model, this.element, null);
    }

    /**
     * Performs element disposal.
     *
     * @throws XFormsException if any error occurred during disposal.
     */
    public void dispose() throws XFormsException {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(this + " dispose");
        }

        disposeDefaultAction();
    }

    // lifecycle template methods

    /**
     * Initializes the default action.
     */
    protected void initializeDefaultAction() {
        super.initializeDefaultAction();
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.SUBMIT, this);
    }

    /**
     * Updates all ui children of this element.
     *
     * @throws de.betterform.xml.xforms.exception.XFormsException
     *          if any error occurred during update.
     */
    protected void updateChildren() throws XFormsException {
        //dirty overwrite - but calls to updateChildren must be refactored above in the hierarchy first
    }

    /**
     * Performs submission attribute defaulting.
     * <p/>
     * Supports XForms 1.1 relevant and validate attributes. However, this
     * support should be externalized in a - say Submission11 - class which
     * simply overwrites this template method.
     */
    protected void initializeSubmission() throws XFormsException {
        // 1. init binding context
        // path expression
        this.locationPath = "/";
        String refAttribute = getXFormsAttribute(REF_ATTRIBUTE);
        if (refAttribute != null) {
            this.locationPath = refAttribute;
        }
        String bindAttribute = getXFormsAttribute(BIND_ATTRIBUTE);
        if (bindAttribute != null) {
            Object bindObject = this.container.lookup(bindAttribute);
            if (bindObject == null || !(bindObject instanceof Bind)) {
                throw new XFormsBindingException(
                        "invalid bind id at " + DOMUtil.getCanonicalPath(this.getElement()), this.target,
                        bindAttribute);
            }

            this.locationPath = ((Bind) bindObject).getLocationPath();
        }

        // instance id
        this.instanceId = this.model.computeInstanceId(this.locationPath);

        // 2. submission options
        // get required method attribute
        this.method = new AttributeOrValueChild(this.element, this.model, METHOD_ATTRIBUTE);
        this.method.init();
        if (this.method == null) {
            // complain
            throw new XFormsLinkException("no method specified for submission", this.target, null);
        }

        // get optional version attribute
        this.version = getXFormsAttribute(VERSION_ATTRIBUTE);
        if (this.version == null) {
            this.version = "1.0"; // setting default
        }

        // get optional indent attribute
        String indentAttribute = getXFormsAttribute(INDENT_ATTRIBUTE);
        if (indentAttribute != null) {
            this.indent = Boolean.valueOf(indentAttribute);
        } else {
            this.indent = false; // setting default
        }

        // get optional mediatype attribute
        this.mediatype = getXFormsAttribute(MEDIATYPE_ATTRIBUTE);
        if (this.mediatype == null) {
            this.mediatype = "application/xml"; //setting default
        }

        // get optional encoding attribute
        this.encoding = getXFormsAttribute(ENCODING_ATTRIBUTE);
        if (this.encoding == null) {
            this.encoding = "UTF-8"; // setting default
        }

        // get optional omit-xml-declaration attribute
        String omitxmldeclarationAttribute = getXFormsAttribute(OMIT_XML_DECLARATION_ATTRIBUTE);
        if (omitxmldeclarationAttribute != null) {
            this.omitxmldeclaration = "true".equals(omitxmldeclarationAttribute)
                    || "1".equals(omitxmldeclarationAttribute);
        } else {
            this.omitxmldeclaration = false;
        }

        // get optional standalone attribute
        String standaloneAttribute = getXFormsAttribute(STANDALONE_ATTRIBUTE);
        if (standaloneAttribute != null) {
            this.standalone = Boolean.valueOf(standaloneAttribute);
        }

        // get optional cdata-section-elements attribute
        this.cdatasectionelements = getXFormsAttribute(CDATA_SECTION_ELEMENTS_ATTRIBUTE);

        // get optional action attribute
        this.separator = getXFormsAttribute(SEPARATOR_ATTRIBUTE);
        if (this.separator == null) {
            // default per schema
            this.separator = "&";
        }

        // get optional includenamespaceprefixes attribute
        String includenamespaceprefixesAttribute = getXFormsAttribute(INCLUDENAMESPACEPREFIXES_ATTRIBUTE);
        if (includenamespaceprefixesAttribute != null) {
            StringTokenizer tokenizer = new StringTokenizer(includenamespaceprefixesAttribute);
            this.includenamespaceprefixes = new ArrayList<String>(tokenizer.countTokens());

            while (tokenizer.hasMoreTokens()) {
                this.includenamespaceprefixes.add(tokenizer.nextToken());
            }
        }

        // get optional replace attribute
        this.replace = getXFormsAttribute(REPLACE_ATTRIBUTE);
        if (this.replace == null) {
            // default per schema
            this.replace = "all";
        }

        // get optional instance attribute
        this.instance = getXFormsAttribute(INSTANCE_ATTRIBUTE);

        // get optional target attribute

        this.targetExpr = getXFormsAttribute(TARGETREF_ATTRIBUTE);
        if (targetExpr == null) {
            //try deprecated 'target' attrbute
            if (getXFormsAttribute(TARGET_ATTRIBUTE) != null) {
                this.targetExpr = getXFormsAttribute(TARGET_ATTRIBUTE);
                LOGGER.warn("'target' Attribute is deprecated - Please use 'targetref' instead.");
            }
        }

        // check for cross model submission
        if (this.targetExpr != null && this.targetExpr.startsWith("model('")) {
            targetModelId = this.targetExpr.substring(this.targetExpr.indexOf("'") + 1);
            targetModelId = targetModelId.substring(0, targetModelId.indexOf("'"));

            if (targetModelId != null && !targetModelId.equals("")) {
                this.targetExpr = this.targetExpr.replace("model('" + targetModelId + "')", "");
                if (targetExpr.equals("")) {
                    targetExpr = null;
                }
            }

        }

        // 3. XForms 1.1 support

        // XForms 1.0 Version
        if (this.container.getVersion().equals(Container.XFORMS_1_0)) {
            this.action = getXFormsAttribute(ACTION_ATTRIBUTE);
            if (this.action == null) {
                throw new XFormsLinkException("no action or resource specified for submission", this.target, null);
            }
        } else {
            // initialize XForms 1.1 submission children
            initializeSubmissionOptions();
            // XForms Version > 1.0 or not set
            this.resource = new AttributeOrValueChild(this.element, this.model, RESOURCE_ATTRIBUTE);
            this.resource.init();
            if (!resource.isAvailable()) {
                // handle action attribtue
                this.action = getXFormsAttribute(ACTION_ATTRIBUTE);
            }
            // either resource or action must be set
            if (!this.resource.isAvailable() && this.action == null) {
                //todo: this should be corrected as resouce may be empty due to spec see the xforms-submit event and 'validation-error'
                throw new XFormsLinkException("no action or resource specified for submission", this.target, null);
            } else if (this.resource == null) {
                getLogger().warn(toString() + " relying on deprecated action attribute");
            }
        }

        // ##### validation and serailization must be handled together #####
        this.validate = true; //default

        //get serialization attribute. If serialization is 'none' validate defaults to false
        this.serialization = getXFormsAttribute(SERIALIZATION_ATTRIBUTE);
        if (this.serialization != null && this.serialization.equalsIgnoreCase("none")) {
            this.validate = false; // setting default when not serialized that might get overwritten by evaluation of validateAttribute below
        }

        // get optional validate attribute
        String validateAttribute = getXFormsAttribute(VALIDATE_ATTRIBUTE);
        if (validateAttribute != null) {
            this.validate = Boolean.valueOf(validateAttribute);
        }

        // get optional relevant attribute
        String relevantAttribute = getXFormsAttribute(RELEVANT_ATTRIBUTE);
        this.relevant = relevantAttribute != null ? Boolean.valueOf(relevantAttribute) : Boolean.TRUE;

    }

    /**
     * Disposes the default action.
     */
    protected void disposeDefaultAction() {
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.SUBMIT, this);
    }

    /**
     * Factory method for the element state.
     *
     * @return an element state implementation or <code>null</code> if no
     *         state keeping is required.
     * @throws de.betterform.xml.xforms.exception.XFormsException
     *          if an error occurred during creation.
     */
    protected UIElementState createElementState() throws XFormsException {
        return hasBindingExpression() ? new BoundElementState() : null;
    }

    /**
     * Returns the logger object.
     *
     * @return the logger object.
     */
    protected Log getLogger() {
        return LOGGER;
    }

    // implementation of 'de.betterform.xml.events.DefaultAction'

    /**
     * Performs the implementation specific default action for this event.
     *
     * @param event the event.
     */
    public void performDefault(Event event) {
        try {
            if (event.getType().equals(XFormsEventNames.SUBMIT)) {
                submit();
            }
        } catch (Exception e) {
            // handle exception and stop event propagation
            this.container.handleEventException(e);
            event.stopPropagation();
        }
    }

    /**
     * Implements <code>xforms-submit</code> default action.
     */
    protected void submit() throws XFormsException {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(this + " submit");
        }

        try {
            updateXPathContext();
        } catch (Exception xe) {
            LOGGER.warn("Exception occured while updating nodeset bound by submission "
                    + DOMUtil.getCanonicalPath(this.element) + " " + xe.getMessage());
            LOGGER.warn(
                    "Exception occured while updating nodeset bound by submission - exception will be ignored. Submission cancelled");
            return;
        }

        // get instance object and location path to submit
        Instance instanceObject = this.model.getInstance(getInstanceId());
        /*
                if(instanceObject == null) {
        instanceObject = targetModel.getInstance(getInstanceId());
                }
        */
        String pathExpression = getLocationPath();
        if (!XPathUtil.existsNode(instanceObject.getInstanceNodeset(), 1, locationPath, getPrefixMapping(),
                this.xpathFunctionContext)) {
            throw new XFormsSubmitError("nodeset is empty at: " + DOMUtil.getCanonicalPath(this.getElement()),
                    this.getTarget(), XFormsSubmitError.constructInfoObject(this.element, this.container,
                            locationPath, XFormsConstants.NO_DATA, getResourceURI()));
        }

        //todo: when serialization is 'none' validation and relevance selection should be skipped if not explicitly set as attributes
        // validate instance items
        submitValidate(instanceObject, pathExpression, getPrefixMapping(), this.xpathFunctionContext);

        // select relevant items
        //todo: this should happen before submitValidate above according to spec - see the xforms-submit event
        Node instanceNode = submitSelectRelevant(instanceObject, pathExpression);

        Map response;
        try {
            // todo: should be supported by serializers
            /*
                        if (this.includenamespaceprefixes != null) {
            getLogger().warn(this + " submit: the 'includenamespaceprefixes' attribute is not supported yet");
                        }
            */

            processSubmissionOptions();

            // todo: refactor submission options to become a typed object, e.g. SubmissionOptions
            // todo: refactor submission response to become a typed object, e.g. SubmissionResponse
            // todo: refactor serializers to be set excplicitly

            // Look for resource submission option and possibly replace action
            // Implementation of the resource attribute version 1.1 feature
            // http://www.w3.org/TR/2007/CR-xforms11-20071129/#submit
            // chapter 11.1
            if (this.resource != null && this.resource.isAvailable()) {
                // obtain relative URI
                //                String relativeURI = null;
                //                relativeURI = instanceObject.getNodeValue(this.resource);
                //
                //                if (relativeURI != null) {
                //                    // resolve uri and assign to action
                //                    this.action = this.container.getConnectorFactory().getAbsoluteURI(relativeURI, this.element).toString();
                //                }
                this.action = getResourceURI();
            }

            //do xforms-submit-serialize handling
            final Element submissionBodyEl = this.element.getOwnerDocument().createElement("submission-body");
            final Map<String, Object> info = new HashMap<String, Object>();
            info.put(XFormsConstants.SUBMISSION_BODY,
                    this.container.getDocumentWrapper(this.element).wrap(submissionBodyEl));

            this.container.dispatch(this.id, XFormsEventNames.SUBMIT_SERIALIZE, info);
            submissionBodyEl.normalize();

            // serialize and transmit instance items
            SubmissionHandler sh = this.container.getConnectorFactory().createSubmissionHandler(this.action,
                    this.element);
            if (submissionBodyEl.getFirstChild() == null) {
                response = sh.submit(this, instanceNode);
            } else {
                response = sh.submit(this, submissionBodyEl.getFirstChild());
            }
        } catch (XFormsInternalSubmitException e) {
            Map<String, Object> info = XFormsSubmitError.constructInfoObject(this.element, this.container,
                    locationPath, e.getErrorType(), getResourceURI(), e.getStatusCode(), null, e.getStatusText(),
                    e.getResponseBodyAsString());
            throw new XFormsSubmitError(
                    "instance submission failed at: " + DOMUtil.getCanonicalPath(this.getElement()), e,
                    this.getTarget(), info);
        } catch (Exception e) {
            String errorType;
            if (e instanceof XFormsInternalSubmitException) {
                errorType = ((XFormsInternalSubmitException) e).getErrorType();
            } else {
                errorType = XFormsConstants.RESOURCE_ERROR;
            }
            Map<String, Object> info = XFormsSubmitError.constructInfoObject(this.element, this.container,
                    locationPath, errorType, getResourceURI());

            //todo: hacky - event context info construction must be reviewed - using exception cause as response-reason-phrase for now
            if (e.getCause() != null && e.getCause().getMessage() != null) {
                info.put(RESPONSE_REASON_PHRASE, e.getCause().getMessage());
                if (e.getCause() instanceof XFormsInternalSubmitException) {
                    info.put(RESPONSE_STATUS_CODE,
                            new Integer(((XFormsInternalSubmitException) e.getCause()).getStatusCode())
                                    .doubleValue());
                }
            }
            throw new XFormsSubmitError(
                    "instance submission failed at: " + DOMUtil.getCanonicalPath(this.getElement()), e,
                    this.getTarget(), info);
            //throw new XFormsSubmitError("instance submission failed", e, this.getTarget(), this.action);
        }

        //todo: for async submits processing should stop here!!!
        // handle replace mode
        if (this.replace.equals("all")) {
            submitReplaceAll(response);
            return;
        }
        if (this.replace.equals("instance")) {
            submitReplaceInstance(response);
            return;
        }
        if (this.replace.equals("text")) {
            submitReplaceText(response);
            return;
        }
        if (this.replace.equals("none")) {
            submitReplaceNone(response);
            return;
        }
        if (this.replace.equals("embedHTML")) {
            submitReplaceEmbedHTML(response);
            return;
        }
        if (this.replace.equals("embedXFormsUI")) {
            submitReplaceEmbedXForms(response);
            return;
        }

        if (this.replace.equals("new")) {
            submitReplaceNew(response);
            return;
        }

        throw new XFormsSubmitError("unknown replace mode " + this.replace, this.getTarget(),
                XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                        XFormsConstants.VALIDATION_ERROR, getResourceURI()));
    }

    /**
    * @return
    * @throws XFormsException
    */
    private String getResourceURI() throws XFormsException {
        final String effectiveResource;
        if (this.resource.isAvailable()) {
            effectiveResource = this.resource.getValue();
        } else {
            effectiveResource = this.action;
        }
        return this.container.getConnectorFactory().getAbsoluteURI(effectiveResource, this.element).toString();
    }

    private void initializeSubmissionOptions() throws XFormsException {
        XFormsElementFactory elementFactory = model.getContainer().getElementFactory();
        NodeList childNodes = element.getChildNodes();
        // initialize Submission child elements 'header'
        for (int index = 0; index < childNodes.getLength(); index++) {
            Node node = childNodes.item(index);

            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element elementImpl = (Element) node;

                if ((XFormsElementFactory.isHeaderElement(elementImpl))) {
                    Header submissionHeader = (Header) elementFactory.createXFormsElement(elementImpl, this.model);
                    submissionHeader.init();
                    if (this.submissionHeaders == null) {
                        this.submissionHeaders = new ArrayList<Header>();
                    }
                    if (submissionHeader.getName() != null && !submissionHeader.getName().equals("")) {
                        this.submissionHeaders.add(submissionHeader);
                    }

                }
            }
        }
    }

    private void processSubmissionOptions() throws XFormsException {
        Map contextMap = this.container.getProcessor().getContext();
        RequestHeaders httpRequestHeader = null;
        if (contextMap.containsKey(AbstractHTTPConnector.HTTP_REQUEST_HEADERS)) {
            httpRequestHeader = (RequestHeaders) contextMap.get(AbstractHTTPConnector.HTTP_REQUEST_HEADERS);
        } else {
            httpRequestHeader = new RequestHeaders();
        }

        // remove all existing headers with same name as xforms:header
        // must be done before headers are added again, don't(!) merge with following for loop

        for (int z = 0; z < submissionHeaders.size(); z++) {
            RequestHeaders requestHeaders = submissionHeaders.get(z).getHeaders();
            List<RequestHeader> allRequestHeaderList = requestHeaders.getAllHeaders();
            for (int i = 0; i < allRequestHeaderList.size(); i++) {
                RequestHeader header = allRequestHeaderList.get(i);
                String headerName = header.getName();
                if (httpRequestHeader.containes(headerName)) {
                    httpRequestHeader.removeHeader(headerName);
                }
            }
        }

        // add headers to HTTPRequestHeader
        for (int i = 0; i < submissionHeaders.size(); i++) {
            RequestHeaders requestHeadersTmp = submissionHeaders.get(i).getHeaders();
            List<RequestHeader> requestHeadersTmpList = requestHeadersTmp.getAllHeaders();
            for (int z = 0; z < requestHeadersTmpList.size(); z++) {
                RequestHeader header = requestHeadersTmpList.get(z);
                httpRequestHeader.addHeader(header);
            }
        }
        contextMap.put(AbstractHTTPConnector.HTTP_REQUEST_HEADERS, httpRequestHeader);
    }

    // template methods for submit processing

    /**
     * Performs validation according to section 11.1, para 2.
     * <p/>
     * Supports XForms 1.1 validate attribute. However, this support should be
     * externalized in a - say Submission11 - class which simply overwrites this
     * template method.
     */
    protected boolean submitValidate(Instance instance, String path, Map prefixMapping,
            XPathFunctionContext xpathFunctionContext) throws XFormsException {
        // validate model items in submission mode: non-relevant items are ignored
        // and the first occurrence of an invalid item discontinues validation
        SubmissionValidatorMode mode = new SubmissionValidatorMode();
        this.model.getValidator().validate(instance, instance.getInstanceNodeset(), 1, path, prefixMapping,
                xpathFunctionContext, mode);

        if (mode.isDiscontinued()) {
            // XForms 1.1 support, section 4.3.1
            // in case of an invalid instance report submit error only if the validate attribute is true
            if (Boolean.TRUE.equals(this.validate)) {
                throw new XFormsSubmitError("instance validation failed: " + mode.getStatusText(), this.target,
                        XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                                XFormsConstants.VALIDATION_ERROR, getResourceURI()));
            }
        }

        return !mode.isDiscontinued();
    }

    /**
     * Performs relevance selection to section 11.1, para 1.
     * <p/>
     * Supports XForms 1.1 relevant attribute. However, this support should be
     * externalized in a - say Submission11 - class which simply overwrites this
     * template method.
     */
    protected Node submitSelectRelevant(Instance instanceObject, String path) throws XFormsException {
        try {
            // XForms 1.1 support, section 4.3.2
            // select relevant instance items only if the relevant attribute is true
            //todo: should throw xforms-submit-error if no data are relevant - see spec the xforms-submit-event
            return (Node) (Boolean.TRUE.equals(this.relevant)
                    ? RelevanceSelector.selectRelevant(instanceObject, path)
                    : XPathCache.getInstance().evaluateAsSingleNode(instanceObject.getRootContext(), path));
        } catch (Exception e) {
            throw new XFormsSubmitError("instance relevance selection failed", e, this.target,
                    XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                            XFormsConstants.TARGET_ATTRIBUTE, getResourceURI()));
        }
    }

    /**
     * Performs replace processing according to section 11.1, para 5.
     */
    protected void submitReplaceAll(Map response) throws XFormsException {
        // XForms 1.0, section 11.1, para 5
        // - For a success response including a body, when the value of the
        // replace attribute on element submission is "all", the event
        // xforms-submit-done is dispatched, and submit processing concludes
        // with entire containing document being replaced with the returned
        // body.
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(this + " submit: replacing all");
        }

        // todo: refactor submission response
        // split copied response into header and body (keep original response
        // for backwards compat)
        Map header = new HashMap();
        header.putAll(response);
        Object body = header.remove(XFormsProcessor.SUBMISSION_RESPONSE_STREAM);

        HashMap map = new HashMap();
        map.put("header", header);
        map.put("body", body);

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, constructEventInfo(response));

        // dispatch internal betterform event
        // special case for URI redirection, resubmit as GET
        if (header.containsKey("Location")) {
            map.clear();
            map.put("show", "replace");
            map.put("uri", response.get("Location"));
            this.container.dispatch(this.target, BetterFormEventNames.LOAD_URI, map);
            return;
        } else {
            if (getMediatype().equals(BetterFORMConstants.XFORMS_MEDIATYPE)) {
                map.put(BetterFORMConstants.SUBMISSION_REDIRECT_XFORMS, this.action);
                this.container.dispatch(this.target, BetterFormEventNames.REPLACE_ALL_XFORMS, map);
            } else {
                this.container.dispatch(this.target, BetterFormEventNames.REPLACE_ALL, map);
            }
            this.container.dispatch(this.target, XFormsEventNames.MODEL_DESTRUCT, null);
        }

        // backwards compat
        forward(response);
    }

    /**
     * Performs replace processing according to section 11.1, para 5.
     */
    protected void submitReplaceInstance(Map response) throws XFormsException {
        // XForms 1.0, section 11.1, para 5
        // - For a success response including a body of an XML media type (as
        // defined by the content type specifiers in [RFC 3023]), when the value
        // of the replace attribute on element submission is "instance", the
        // response is parsed as XML. An xforms-link-exception (4.5.2 The
        // xforms-link-exception Event) occurs if the parse fails. If the parse
        // succeeds, then all of the internal instance data of the instance
        // indicated by the instance attribute setting is replaced with the
        // result. Once the XML instance data has been replaced, the rebuild,
        // recalculate, revalidate and refresh operations are performed on the
        // model, without dispatching events to invoke those four operations.
        // Submit processing then concludes after dispatching
        // xforms-submit-done.
        // - For a success response including a body of a non-XML media type
        // (i.e. with a content type not matching any of the specifiers in
        // [RFC 3023]), when the value of the replace attribute on element
        // submission is "instance", nothing in the document is replaced and
        // submit processing concludes after dispatching xforms-submit-error.
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(this + " submit: replacing instance");
        }

        Document responseInstance = getResponseAsDocument(response);

        // replace instance
        if (this.targetModelId != null) {
            List<Model> models = container.getModels();
            Model targetModel = null;
            for (Model tmpmodel : models) {
                if (tmpmodel.getId().equals(this.targetModelId)) {
                    targetModel = tmpmodel;
                }
            }
            updateInstanceAndModel(targetModel, responseInstance);
        } else {
            updateInstanceAndModel(this.model, responseInstance);
        }

        this.container.refresh();
        // deferred update behaviour
        UpdateHandler updateHandler = this.model.getUpdateHandler();
        if (updateHandler != null) {
            updateHandler.doRebuild(false);
            updateHandler.doRecalculate(false);
            updateHandler.doRevalidate(false);
            updateHandler.doRefresh(false);
        }

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, constructEventInfo(response));
    }

    private Document getResponseAsDocument(Map response) throws XFormsException {
        Document responseInstance;
        try {

            if (response.containsKey(XFormsProcessor.SUBMISSION_RESPONSE_DOCUMENT)) {
                responseInstance = (Document) response.get(XFormsProcessor.SUBMISSION_RESPONSE_DOCUMENT);
            } else {
                InputStream responseStream = (InputStream) response.get(XFormsProcessor.SUBMISSION_RESPONSE_STREAM);
                responseInstance = DOMUtil.parseInputStream(responseStream, true, false);
                responseStream.close();
            }
        } catch (Exception e) {
            // todo: check for response media type (needs submission response
            // refactoring) in order to dispatch xforms-link-exception
            throw new XFormsSubmitError("instance parsing failed", e, this.getTarget(),
                    XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                            XFormsConstants.PARSE_ERROR, getResourceURI(), 200d, null, "", ""));
        }
        return responseInstance;
    }

    private void submitReplaceEmbedHTML(Map response) throws XFormsException {
        // check for targetid
        String targetid = getXFormsAttribute(TARGETID_ATTRIBUTE);
        String evaluatedTarget = evalAttributeValueTemplates(targetid, this.element);
        String resource = getResource();
        Map eventInfo = new HashMap();
        String error = null;

        if (evaluatedTarget == null) {
            error = "evaluatedTarget";
        } else if (resource == null) {
            error = "resource";
        }

        if (error != null && error.length() > 0) {
            eventInfo.put(XFormsConstants.ERROR_TYPE, "no " + error + "defined for submission resource");
            this.container.dispatch(this.target, XFormsEventNames.SUBMIT_ERROR, eventInfo);
            return;
        }

        Document result = getResponseAsDocument(response);
        Node embedElement = result.getDocumentElement();
        if (resource.indexOf("#") != -1) {
            // detected a fragment so extract that from our result Document

            String fragmentid = resource.substring(resource.indexOf("#") + 1);
            if (fragmentid.indexOf("?") != -1) {
                fragmentid = fragmentid.substring(0, fragmentid.indexOf("?"));
            }
            embedElement = DOMUtil.getById(result, fragmentid);
        }

        // Map eventInfo = constructEventInfo(response);

        OutputStream outputStream = new ByteArrayOutputStream();
        try {
            DOMUtil.prettyPrintDOM(embedElement, outputStream);
        } catch (TransformerException e) {
            throw new XFormsException(e);
        }

        eventInfo.put(EMBEDNODE, outputStream.toString());
        eventInfo.put("embedTarget", evaluatedTarget);

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, eventInfo);

    }

    private void submitReplaceEmbedXForms(Map response) throws XFormsException {
        // check for targetid
        String targetid = getXFormsAttribute(TARGETID_ATTRIBUTE);
        String resource = getResource();
        Map eventInfo = new HashMap();
        String error = null;
        if (targetid == null) {
            error = "targetId";
        } else if (resource == null) {
            error = "resource";
        }

        if (error != null && error.length() > 0) {
            eventInfo.put(XFormsConstants.ERROR_TYPE, "no " + error + "defined for submission resource");
            this.container.dispatch(this.target, XFormsEventNames.SUBMIT_ERROR, eventInfo);
            return;
        }

        Document result = getResponseAsDocument(response);
        Node embedElement = result.getDocumentElement();

        if (resource.indexOf("#") != -1) {
            // detected a fragment so extract that from our result Document

            String fragmentid = resource.substring(resource.indexOf("#") + 1);
            if (fragmentid.indexOf("?") != -1) {
                fragmentid = fragmentid.substring(0, fragmentid.indexOf("?"));
            }
            embedElement = DOMUtil.getById(result, fragmentid);
        }

        Element embeddedNode = null;
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("get target element for id: " + targetid);
        }
        Element targetElem = this.container.getElementById(targetid);
        DOMResult domResult = new DOMResult();
        //Test if targetElem exist.
        if (targetElem != null) {
            // destroy existing embedded form within targetNode
            if (targetElem.hasChildNodes()) {
                // destroyembeddedModels(targetElem);
                Initializer.disposeUIElements(targetElem);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("destroyed any existing ui elements for target elem");
            }

            // import referenced embedded form into host document
            embeddedNode = (Element) this.container.getDocument().importNode(embedElement, true);

            //import namespaces
            NamespaceResolver.applyNamespaces(targetElem.getOwnerDocument().getDocumentElement(),
                    (Element) embeddedNode);

            // keep original targetElem id within hostdoc
            embeddedNode.setAttributeNS(null, "id", targetElem.getAttributeNS(null, "id"));
            //copy all Attributes that might have been on original mountPoint to embedded node
            DOMUtil.copyAttributes(targetElem, embeddedNode, null);
            targetElem.getParentNode().replaceChild(embeddedNode, targetElem);
            //create model for it
            Initializer.initializeUIElements(model, embeddedNode, null, null);

            try {
                CachingTransformerService transformerService = new CachingTransformerService(
                        new ClasspathResourceResolver("unused"));
                // TODO: MUST BE GENERIFIED USING USERAGENT MECHANISM
                //TODO: check exploded mode!!!
                String path = getClass().getResource("/META-INF/resources/xslt/xhtml.xsl").getPath();

                //String xslFilePath = "file:" + path;
                transformerService.getTransformer(new URI(path));
                XSLTGenerator generator = new XSLTGenerator();
                generator.setTransformerService(transformerService);
                generator.setStylesheetURI(new URI(path));
                generator.setInput(embeddedNode);
                generator.setOutput(domResult);
                generator.generate();
            } catch (TransformerException e) {
                throw new XFormsException(
                        "Transformation error while executing 'Submission.submitReplaceEmbedXForms'", e);
            } catch (URISyntaxException e) {
                throw new XFormsException(
                        "Malformed URI throwed URISyntaxException in 'Submission.submitReplaceEmbedXForms'", e);
            }
        }

        // Map eventInfo = constructEventInfo(response);
        OutputStream outputStream = new ByteArrayOutputStream();
        try {
            DOMUtil.prettyPrintDOM(domResult.getNode(), outputStream);
        } catch (TransformerException e) {
            throw new XFormsException(e);
        }

        eventInfo.put(EMBEDNODE, outputStream.toString());
        eventInfo.put("embedTarget", targetid);
        eventInfo.put("embedXForms", true);

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, eventInfo);

    }

    private void submitReplaceNew(Map response) throws XFormsException {
        Document result = getResponseAsDocument(response);
        Node embedElement = result.getDocumentElement();

        OutputStream outputStream = new ByteArrayOutputStream();
        try {
            DOMUtil.prettyPrintDOM(embedElement, outputStream);
        } catch (TransformerException e) {
            throw new XFormsException(e);
        }
        Map eventInfo = new HashMap();
        eventInfo.put(DOCUMENT, outputStream.toString());

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, eventInfo);

    }

    private void updateInstanceAndModel(Model referedModel, Document responseInstance) throws XFormsException {
        if (this.targetExpr != null) {
            Node targetNode;
            if (this.instance == null)
                targetNode = XPathUtil.getAsNode(XPathCache.getInstance().evaluate(evalInScopeContext(), 1,
                        this.targetExpr, this.prefixMapping, this.xpathFunctionContext), 1);
            else {
                targetNode = XPathUtil.getAsNode(XPathCache.getInstance().evaluate(
                        referedModel.getInstance(this.instance).getRootContext().getNodeset(), 1, this.targetExpr,
                        this.prefixMapping, this.xpathFunctionContext), 1);
            }
            if (targetNode != null && targetNode.getNodeType() == Node.ELEMENT_NODE) {
                targetNode.getParentNode().replaceChild(
                        targetNode.getOwnerDocument().importNode(responseInstance.getDocumentElement(), true),
                        targetNode);

            } else if (targetNode != null && targetNode.getNodeType() == Node.ATTRIBUTE_NODE) {
                if (LOGGER.isDebugEnabled()) {
                    DOMUtil.prettyPrintDOM(responseInstance);
                }
                // targetNode.setContent(responseInstance.getTextContent());
                String attrValue = responseInstance.getDocumentElement().getTextContent();
                targetNode.setNodeValue(attrValue);
            } else {
                throw new XFormsSubmitError("Invalid target", this.getTarget(),
                        XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                                XFormsConstants.TARGET_ERROR, getResourceURI(), 200d, null, "", ""));
            }
        } else if (this.instance != null && referedModel.getInstance(this.instance) == null) {
            this.container.dispatch(referedModel.getId(), XFormsEventNames.BINDING_EXCEPTION);
            // throw new XFormsBindingException("invalid instance id at " + DOMUtil.getCanonicalPath(this.getElement()), this.target, this.instance);
        } else if (this.instance != null) {
            referedModel.getInstance(this.instance).setInstanceDocument(responseInstance);
        } else {
            referedModel.getInstance(getInstanceId()).setInstanceDocument(responseInstance);
        }

        // perform rebuild, recalculate, revalidate, and refresh
        referedModel.rebuild();
        referedModel.recalculate();
        referedModel.revalidate();
    }

    /**
     * Performs replace processing according to section 11.1, para 5.
     */
    protected void submitReplaceText(Map response) throws XFormsException {

        if (getLogger().isDebugEnabled()) {
            getLogger().debug(this + " submit: replacing text");
        }
        Node targetNode;
        if (this.targetExpr != null) {
            targetNode = XPathUtil
                    .getAsNode(
                            XPathCache.getInstance()
                                    .evaluate(
                                            this.instance == null ? evalInScopeContext()
                                                    : this.model.getInstance(this.instance).getRootContext()
                                                            .getNodeset(),
                                            1, this.targetExpr, this.prefixMapping, this.xpathFunctionContext),
                            1);
        } else if (this.instance == null) {
            targetNode = this.model.getInstance(getInstanceId()).getInstanceDocument().getDocumentElement();
        } else {
            targetNode = this.model.getInstance(this.instance).getInstanceDocument().getDocumentElement();
        }
        final InputStream responseStream = (InputStream) response.get(XFormsProcessor.SUBMISSION_RESPONSE_STREAM);

        StringBuilder text = new StringBuilder(512);
        try {
            String contentType = (String) response.get("Content-Type");
            String encoding = "UTF-8";

            if (contentType != null) {
                final String[] contTypeEntries = contentType.split(", ?");

                for (int i = 0; i < contTypeEntries.length; i++) {
                    if (contTypeEntries[i].startsWith("charset=")) {
                        encoding = contTypeEntries[i].substring(8);
                    }
                }
            }
            byte[] buffer = new byte[512];
            int bytesRead;
            while ((bytesRead = responseStream.read(buffer)) > 0) {
                text.append(new String(buffer, 0, bytesRead, encoding));
            }

            responseStream.close();
        } catch (Exception e) {
            // todo: check for response media type (needs submission response
            // refactoring) in order to dispatch xforms-link-exception
            throw new XFormsSubmitError("instance parsing failed", e, this.getTarget(),
                    XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                            XFormsConstants.PARSE_ERROR, getResourceURI(), 200d, null, "", ""));
        }

        if (targetNode == null) {
            throw new XFormsSubmitError("Invalid target", this.getTarget(),
                    XFormsSubmitError.constructInfoObject(this.element, this.container, locationPath,
                            XFormsConstants.TARGET_ERROR, getResourceURI(), 200d, null, "", ""));
        }

        else if (targetNode.getNodeType() == Node.ELEMENT_NODE) {
            while (targetNode.getFirstChild() != null) {
                targetNode.removeChild(targetNode.getFirstChild());
            }

            targetNode.appendChild(targetNode.getOwnerDocument().createTextNode(text.toString()));
        } else if (targetNode.getNodeType() == Node.ATTRIBUTE_NODE) {
            targetNode.setNodeValue(text.toString());
        } else {
            LOGGER.warn("Don't know how to handle targetNode '" + targetNode.getLocalName()
                    + "', node is neither an element nor an attribute Node");
        }

        // perform rebuild, recalculate, revalidate, and refresh
        this.model.rebuild();
        this.model.recalculate();
        this.model.revalidate();
        this.container.refresh();

        // deferred update behaviour
        UpdateHandler updateHandler = this.model.getUpdateHandler();
        if (updateHandler != null) {
            updateHandler.doRebuild(false);
            updateHandler.doRecalculate(false);
            updateHandler.doRevalidate(false);
            updateHandler.doRefresh(false);
        }

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, constructEventInfo(response));
    }

    /**
     * Performs replace processing according to section 11.1, para 5.
     */
    protected void submitReplaceNone(Map response) throws XFormsException {
        // XForms 1.0, section 11.1, para 5
        // - For a success response including a body, when the value of the
        // replace attribute on element submission is "none", submit processing
        // concludes after dispatching xforms-submit-done.
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(this + " submit: replacing none");
        }

        // dispatch xforms-submit-done
        this.container.dispatch(this.target, XFormsEventNames.SUBMIT_DONE, constructEventInfo(response));
    }

    private Map<String, Object> constructEventInfo(Map response) throws XFormsException {
        Map<String, Object> result = new HashMap<String, Object>();

        final Document ownerDocument = this.element.getOwnerDocument();
        final DocumentWrapper wrapper = new DocumentWrapper(ownerDocument,
                this.container.getProcessor().getBaseURI(), this.container.getConfiguration());

        List<Item> headerItems = new ArrayList<Item>(response.size());
        for (Iterator<Map.Entry<String, String>> it = response.entrySet().iterator(); it.hasNext();) {
            Map.Entry<String, String> entry = it.next();
            if (!XFormsProcessor.SUBMISSION_RESPONSE_STREAM.equals(entry.getKey())
                    && !XFormsProcessor.SUBMISSION_RESPONSE_DOCUMENT.equals(entry.getKey())
                    && !RESPONSE_STATUS_CODE.equals(entry.getKey())
                    && !RESPONSE_REASON_PHRASE.equals(entry.getKey())) {

                Element headerEl = ownerDocument.createElement("header");

                Element nameEl = ownerDocument.createElement("name");
                nameEl.appendChild(ownerDocument.createTextNode(entry.getKey()));
                headerEl.appendChild(nameEl);

                Element valueEl = ownerDocument.createElement("value");
                valueEl.appendChild(ownerDocument.createTextNode(entry.getValue()));
                headerEl.appendChild(valueEl);

                headerItems.add(wrapper.wrap(headerEl));
            }
        }
        result.put(RESOURCE_URI, getResourceURI());
        result.put(RESPONSE_STATUS_CODE,
                (response.containsKey(RESPONSE_STATUS_CODE)
                        ? Double.parseDouble((String) response.get(RESPONSE_STATUS_CODE))
                        : Double.valueOf(200d))); //TODO get real response code
        result.put(RESPONSE_HEADERS, headerItems);
        result.put(RESPONSE_REASON_PHRASE,
                (response.containsKey(RESPONSE_REASON_PHRASE) ? (String) response.get(RESPONSE_REASON_PHRASE)
                        : "")); //TODO get real response reason phrase

        return result;
    }

    // deprecated crap

    /**
     * @deprecated backwards compat
     */
    public Map getSubmissionMap() {
        return (Map) container.getProcessor().getContext().get(XFormsProcessor.SUBMISSION_RESPONSE);
    }

    /**
     * @deprecated backwards compat
     */
    public void forward(Map response) {
        this.container.getProcessor().getContext().put(XFormsProcessor.SUBMISSION_RESPONSE, response);
    }

    /**
     * @deprecated backwards compat
     */
    public void redirect(String uri) {
        this.container.getProcessor().getContext().put(XFormsProcessor.LOAD_URI, uri);
    }

    private void destroyembeddedModels(Element targetElem) throws XFormsException {
        NodeList childNodes = targetElem.getChildNodes();

        for (int index = 0; index < childNodes.getLength(); index++) {
            Node node = childNodes.item(index);

            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element elementImpl = (Element) node;

                String name = elementImpl.getLocalName();
                String uri = elementImpl.getNamespaceURI();

                if (NamespaceConstants.XFORMS_NS.equals(uri) && name.equals(XFormsConstants.MODEL)) {
                    Model model = (Model) elementImpl.getUserData("");
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("dispatch 'model-destruct' event to embedded model: " + model.getId());
                    }
                    String modelId = model.getId();
                    // do not dispatch model-destruct to avoid problems in lifecycle
                    // TODO: review: this.container.dispatch(model.getTarget(), XFormsEventNames.MODEL_DESTRUCT, null);
                    model.dispose();
                    this.container.removeModel(model);
                    model = null;
                    if (Config.getInstance().getProperty("betterform.debug-allowed").equals("true")) {
                        Map contextInfo = new HashMap(1);
                        contextInfo.put("modelId", modelId);
                        this.container.dispatch(this.target, BetterFormEventNames.MODEL_REMOVED, contextInfo);
                    }

                } else {
                    destroyembeddedModels(elementImpl);
                }
            }
        }
    }

}