patch: support error-type attribute on xforms-submit-error event

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

patch: support error-type attribute on xforms-submit-error event

Adrian Baker
Attached is a fairly straightforward patch to XFormsModelSubmission et al to support the XForms 1.1 error-type attribute (http://www.w3.org/TR/xforms11/#submit-evt-submit).

This event is particularly useful for distinguishing between submission failures caused by invalid data (user error) or a failure of the submission target (system error). Previously I had been using the xforms-submit-serialize to set a flag to identify the two cases, but came across a case where this was broken by @serialize="false" on the <xf:submission>, and rather than update my rather fiddly workaround it was quicker to implement the proper solution.

Previously the XFormsSubmitError event was being created throughout the body of the performDefaultAction() method (always just before an exception is thrown), as well as in a catch block in the bottom. I felt a bit uneasy about spreading the creation around like this, and changed this so the event is created solely in the catch block at the bottom, since there is always an exception thrown if the submission fails.

Adrian

/**
 *  Copyright (C) 2005 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms;

import org.apache.log4j.Logger;
import org.dom4j.*;
import org.dom4j.io.DocumentSource;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.ProcessorUtils;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xforms.action.actions.XFormsLoadAction;
import org.orbeon.oxf.xforms.control.controls.XFormsUploadControl;
import org.orbeon.oxf.xforms.event.*;
import org.orbeon.oxf.xforms.event.events.*;
import org.orbeon.oxf.xforms.event.events.XFormsSubmitErrorEvent.ErrorType;
import org.orbeon.oxf.xforms.processor.XFormsServer;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.XMLUtils;
import org.orbeon.oxf.xml.dom4j.Dom4jUtils;
import org.orbeon.oxf.xml.dom4j.LocationData;
import org.orbeon.oxf.xml.dom4j.LocationDocumentResult;
import org.orbeon.oxf.xml.dom4j.ExtendedLocationData;
import org.orbeon.saxon.dom4j.NodeWrapper;
import org.orbeon.saxon.functions.FunctionLibrary;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.NodeInfo;
import org.orbeon.saxon.om.FastStringBuffer;

import javax.xml.transform.Transformer;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Represents an XForms model submission instance.
 *
 * TODO: This badly needs to be modularized instead of being a soup of "ifs"!
 */
public class XFormsModelSubmission implements XFormsEventTarget, XFormsEventHandlerContainer {

        public final static Logger logger = LoggerFactory.createLogger(XFormsModelSubmission.class);

    private final XFormsContainingDocument containingDocument;
    private final String id;
    private final XFormsModel model;
    private final Element submissionElement;
    private boolean submissionElementExtracted = false;

    // Event handlers
    private final List eventHandlers;

    private String avtActionOrResource; // required unless there is a nested xforms:resource element
    private String resolvedActionOrResource;
    private String method; // required

    private boolean validate = true;
    private boolean relevant = true;
    private boolean serialize = true;

    private String version;
    private boolean indent;
    private String mediatype;
    private String encoding;
    private boolean omitxmldeclaration;
    private Boolean standalone;
    private String cdatasectionelements;

    private String replace = XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL;
    private String replaceInstanceId;
    private String xxfReplaceInstanceId;
    private String separator = ";";
    private String includenamespaceprefixes;

    private String avtXXFormsUsername;
    private String resolvedXXFormsUsername;
    private String avtXXFormsPassword;
    private String resolvedXXFormsPassword;
    private String avtXXFormsReadonly;
    private String resolvedXXFormsReadonly;
    private String avtXXFormsShared;
    private String resolvedXXFormsShared;
    private String avtXXFormsTarget;
    private String resolvedXXFormsTarget;

    private boolean xxfShowProgress;

    private boolean fURLNorewrite;

    public XFormsModelSubmission(XFormsContainingDocument containingDocument, String id, Element submissionElement, XFormsModel model) {
        this.containingDocument = containingDocument;
        this.id = id;
        this.submissionElement = submissionElement;
        this.model = model;

        // Extract event handlers
        eventHandlers = XFormsEventHandlerImpl.extractEventHandlers(containingDocument, this, submissionElement);
    }

    public XFormsContainingDocument getContainingDocument() {
        return containingDocument;
    }

    public Element getSubmissionElement() {
        return submissionElement;
    }


    public boolean isXxfShowProgress() {
        return xxfShowProgress;
    }

    public String getReplace() {
        return replace;
    }

    public String getResolvedXXFormsTarget() {
        return resolvedXXFormsTarget;
    }

    private void extractSubmissionElement() {
        if (!submissionElementExtracted) {

            avtActionOrResource = submissionElement.attributeValue("resource");
            if (avtActionOrResource == null) // @resource has precedence over @action
                avtActionOrResource = submissionElement.attributeValue("action");
            if (avtActionOrResource == null) {
                // TODO: For XForms 1.1, support @resource and nested xforms:resource
                throw new XFormsSubmissionException("xforms:submission: action attribute or resource attribute is missing.",
                        "processing xforms:submission attributes");
            }

            method = submissionElement.attributeValue("method");
            method = Dom4jUtils.qNameToexplodedQName(Dom4jUtils.extractAttributeValueQName(submissionElement, "method"));

            validate = !"false".equals(submissionElement.attributeValue("validate"));
            relevant = !"false".equals(submissionElement.attributeValue("relevant"));
            serialize = !"false".equals(submissionElement.attributeValue("serialize"));

            version = submissionElement.attributeValue("version");

            if (submissionElement.attributeValue("indent") != null) {
                indent = Boolean.valueOf(submissionElement.attributeValue("indent")).booleanValue();
            }
            mediatype = submissionElement.attributeValue("mediatype");
            encoding = submissionElement.attributeValue("encoding");
            if (submissionElement.attributeValue("omitxmldeclaration") != null) {
                omitxmldeclaration = Boolean.valueOf(submissionElement.attributeValue("omit-xml-declaration")).booleanValue();
            }
            if (submissionElement.attributeValue("standalone") != null) {
                standalone = new Boolean(submissionElement.attributeValue("standalone"));
            }

            cdatasectionelements = submissionElement.attributeValue("cdata-section-elements");
            if (submissionElement.attributeValue("replace") != null) {
                replace = submissionElement.attributeValue("replace");

                if (replace.equals("instance")) {
                    replaceInstanceId = XFormsUtils.namespaceId(containingDocument, submissionElement.attributeValue("instance"));
                    xxfReplaceInstanceId = XFormsUtils.namespaceId(containingDocument, submissionElement.attributeValue(XFormsConstants.XXFORMS_INSTANCE_QNAME));
                }
            }
            if (submissionElement.attributeValue("separator") != null) {
                separator = submissionElement.attributeValue("separator");
            }
            includenamespaceprefixes = submissionElement.attributeValue("includenamespaceprefixes");

            // Extension attributes
            avtXXFormsUsername = submissionElement.attributeValue(XFormsConstants.XXFORMS_USERNAME_QNAME);
            avtXXFormsPassword = submissionElement.attributeValue(XFormsConstants.XXFORMS_PASSWORD_QNAME);

            avtXXFormsReadonly = submissionElement.attributeValue(XFormsConstants.XXFORMS_READONLY_ATTRIBUTE_QNAME);
            avtXXFormsShared = submissionElement.attributeValue(XFormsConstants.XXFORMS_SHARED_QNAME);

            avtXXFormsTarget = submissionElement.attributeValue(XFormsConstants.XXFORMS_TARGET_QNAME);

            // Whether we must show progress or not
            xxfShowProgress = !"false".equals(submissionElement.attributeValue(XFormsConstants.XXFORMS_SHOW_PROGRESS_QNAME));

            // Whether or not to rewrite URLs
            fURLNorewrite = XFormsUtils.resolveUrlNorewrite(submissionElement);

            // Remember that we did this
            submissionElementExtracted = true;
        }
    }

    private boolean isMethodOptimizedLocalSubmission() {
        return method.startsWith(XMLUtils.buildExplodedQName(XFormsConstants.XXFORMS_NAMESPACE_URI, ""))
                && (XFormsSubmissionUtils.isGet(method) || XFormsSubmissionUtils.isPost(method) || XFormsSubmissionUtils.isPut(method));
    }

    public String getEffectiveId() {
        return id;
    }

    public LocationData getLocationData() {
        return (LocationData) submissionElement.getData();
    }

    public XFormsEventHandlerContainer getParentContainer(XFormsContainingDocument containingDocument) {
        return model;
    }

    public List getEventHandlers(XFormsContainingDocument containingDocument) {
        return eventHandlers;
    }

    public void performDefaultAction(PipelineContext pipelineContext, XFormsEvent event) {
        final String eventName = event.getEventName();

        if (XFormsEvents.XFORMS_SUBMIT.equals(eventName) || XFormsEvents.XXFORMS_SUBMIT.equals(eventName)) {
            // 11.1 The xforms-submit Event
            // Bubbles: Yes / Cancelable: Yes / Context Info: None

            containingDocument.setGotSubmission(true);

            boolean isDeferredSubmissionSecondPassReplaceAll = false;
            boolean submitDone = false;
            final long submissionStartTime = XFormsServer.logger.isDebugEnabled() ? System.currentTimeMillis() : 0;

            // Make sure submission element info is extracted
            extractSubmissionElement();

            try {
                final boolean isReplaceAll = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL);
                final boolean isReplaceInstance = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_INSTANCE);

                final boolean isHandlingOptimizedGet = XFormsUtils.isOptimizeGetAllSubmission() && XFormsSubmissionUtils.isGet(method)
                        && isReplaceAll
                        && avtXXFormsUsername == null; // can't optimize if there are authentication credentials

                final XFormsControls xformsControls = containingDocument.getXFormsControls();

                // Get current node for xforms:submission
                final NodeInfo currentNodeInfo;
                {
                    // TODO FIXME: the submission element is not a control, so we shouldn't use XFormsControls.
                    // "The default value is '/'."
                    final String refAttribute = (submissionElement.attributeValue("ref") != null)
                            ? submissionElement.attributeValue("ref") : "/";
                    xformsControls.resetBindingContext();
                    xformsControls.pushBinding(pipelineContext, refAttribute, null, null, model.getEffectiveId(), null, submissionElement,
                            Dom4jUtils.getNamespaceContextNoDefault(submissionElement));

                    currentNodeInfo = xformsControls.getCurrentSingleNode();

                    // Check that we have a current node and that it is pointing to a document or an element
                    if (currentNodeInfo == null)
                        throw new XFormsSubmissionException("Empty single-node binding on xforms:submission for submission id: " + id, "getting submission single-node binding",
                        XFormsSubmitErrorEvent.ErrorType.NO_DATA);

                    if (!(currentNodeInfo instanceof DocumentInfo || currentNodeInfo.getNodeKind() == org.w3c.dom.Document.ELEMENT_NODE)) {
                        throw new XFormsSubmissionException("xforms:submission: single-node binding must refer to a document node or an element.", "getting submission single-node binding",
                        XFormsSubmitErrorEvent.ErrorType.NO_DATA);
                    }
                }

                // Get instance containing the node to submit
                final XFormsInstance currentInstance = xformsControls.getCurrentInstance();

                // Determine if the instance to submit has one or more bound and relevant upload controls
                boolean hasBoundRelevantUploadControl = false;
                if (serialize) {
                    final List uploadControls = xformsControls.getCurrentControlsState().getUploadControls();
                    if (uploadControls != null) {
                        for (Iterator i = uploadControls.iterator(); i.hasNext();) {
                            final XFormsUploadControl currentControl = (XFormsUploadControl) i.next();
                            if (currentControl.isRelevant()) {
                                final NodeInfo boundNodeInfo = currentControl.getBoundNode();
                                if (currentInstance ==  model.getInstanceForNode(boundNodeInfo)) {
                                    // Found one relevant bound control
                                    hasBoundRelevantUploadControl = true;
                                    break;
                                }
                            }
                        }
                    }
                }

                final boolean isDeferredSubmission = (isReplaceAll && !isHandlingOptimizedGet) || (!isReplaceAll && serialize && hasBoundRelevantUploadControl);
                final boolean isDeferredSubmissionFirstPass = isDeferredSubmission && XFormsEvents.XFORMS_SUBMIT.equals(eventName);
                final boolean isDeferredSubmissionSecondPass = isDeferredSubmission && !isDeferredSubmissionFirstPass; // here we get XXFORMS_SUBMIT
                isDeferredSubmissionSecondPassReplaceAll = isDeferredSubmissionSecondPass && isReplaceAll;

                // "The data model is updated"
                final XFormsModel modelForInstance = currentInstance.getModel(containingDocument);
                {
                    // NOTE: As of 2007-07-30, the spec says this should happen regardless of whether we serialize or not.
                    final XFormsModel.DeferredActionContext deferredActionContext = modelForInstance.getDeferredActionContext();
                    if (deferredActionContext != null) {
                        if (deferredActionContext.rebuild) {
                            modelForInstance.doRebuild(pipelineContext);
                        }
                        if (deferredActionContext.recalculate) {
                            modelForInstance.doRecalculate(pipelineContext);
                        }
                    }
                }

                final Document initialDocumentToSubmit;
                if (serialize && !isDeferredSubmissionSecondPass) {
                    // Create document to submit
                    try {
                        initialDocumentToSubmit = createDocumentToSubmit(currentNodeInfo, currentInstance);

                        // Temporarily change instance document so that we can run validation again
                        modelForInstance.setInstanceDocument(initialDocumentToSubmit,
                                currentInstance.getModelId(), currentInstance.getEffectiveId(), currentInstance.getSourceURI(),
                                currentInstance.getUsername(), currentInstance.getPassword(),
                                currentInstance.isApplicationShared(),
                                currentInstance.getTimeToLive(),
                                currentInstance.getValidation());

                        // Revalidate instance
                        modelForInstance.doRevalidate(pipelineContext);
                        // TODO: Check if the validation state can really change. If so, find a solution.
                        // "no notification events are marked for dispatching due to this operation"

                        // Check that there are no validation errors
                        // NOTE: If the instance is read-only, it can't have MIPs, and can't fail validation/requiredness, so we don't go through the process at all.
                        final boolean instanceSatisfiesValidRequired = currentInstance.isReadOnly() || isDocumentSatisfiesValidRequired(initialDocumentToSubmit);
                        if (!instanceSatisfiesValidRequired) {
                            if (logger.isDebugEnabled()) {
                                final LocationDocumentResult documentResult = new LocationDocumentResult();
                                final TransformerHandler identity = TransformerUtils.getIdentityTransformerHandler();
                                identity.setResult(documentResult);
                                currentInstance.read(identity);
                                final String documentString = Dom4jUtils.domToString(documentResult.getDocument());

                                logger.debug("XForms - submission - instance document or subset thereof cannot be submitted:\n" + documentString);
                            }
                            throw new XFormsSubmissionException("xforms:submission: instance to submit does not satisfy valid and/or required model item properties.",
                                    "checking instance validity", XFormsSubmitErrorEvent.ErrorType.VALIDATION_ERROR);
                        }
                    } finally {
                        // Restore instance document
                        modelForInstance.setInstance(currentInstance, false);
                    }
                } else {
                    initialDocumentToSubmit = null;
                }

                // Deferred submission: end of the first pass
                if (isDeferredSubmissionFirstPass) {

                    // Resolve the target AVT if needed
                    final FunctionLibrary functionLibrary = XFormsContainingDocument.getFunctionLibrary();
                    resolvedXXFormsTarget = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsTarget);

                    // When replace="all", we wait for the submission of an XXFormsSubmissionEvent from the client
                    containingDocument.setClientActiveSubmission(this);
                    return;
                }

                // Evaluate AVTs
                // TODO FIXME: the submission element is not a control, so we shouldn't use XFormsControls.
                {
                    final FunctionLibrary functionLibrary = XFormsContainingDocument.getFunctionLibrary();

                    final String tempActionOrResource = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtActionOrResource);
                    resolvedActionOrResource = XFormsUtils.encodeHRRI(tempActionOrResource, true);

                    resolvedXXFormsUsername = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsUsername);
                    resolvedXXFormsPassword = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsPassword);
                    resolvedXXFormsReadonly = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsReadonly);
                    resolvedXXFormsShared = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsShared);
                }

                // Check read-only and shared hints
                XFormsInstance.checkSharedHints(submissionElement, resolvedXXFormsReadonly, resolvedXXFormsShared);
                final boolean isReadonlyHint = "true".equals(resolvedXXFormsReadonly);
                final boolean isApplicationSharedHint = "application".equals(resolvedXXFormsShared);
                final long timeToLive = XFormsInstance.getTimeToLive(submissionElement);
                if (isApplicationSharedHint) {
                    if (!XFormsSubmissionUtils.isGet(method))
                        throw new XFormsSubmissionException("xforms:submission: xxforms:shared=\"application\" can be set only with method=\"get\".",
                                "checking read-only and shared hints");
                    if (!isReplaceInstance)
                        throw new XFormsSubmissionException("xforms:submission: xxforms:shared=\"application\" can be set only with replace=\"instance\".",
                                "checking read-only and shared hints");
                } else if (isReadonlyHint) {
                    if (!isReplaceInstance)
                        throw new XFormsSubmissionException("xforms:submission: xxforms:readonly=\"true\" can be \"true\" only with replace=\"instance\".",
                                "checking read-only and shared hints");
                }

                final Document documentToSubmit;
                if (serialize && isDeferredSubmissionSecondPass) {
                    // Handle uploaded files if any
                    final Element filesElement = (event instanceof XXFormsSubmitEvent) ? ((XXFormsSubmitEvent) event).getFilesElement() : null;
                    if (filesElement != null) {
                        for (Iterator i = filesElement.elements().iterator(); i.hasNext();) {
                            final Element parameterElement = (Element) i.next();
                            final String name = parameterElement.element("name").getTextTrim();

                            final XFormsUploadControl uploadControl
                                        = (XFormsUploadControl) containingDocument.getObjectById(pipelineContext, name);

                            // In case of xforms:repeat, the name of the template will not match an existing control
                            if (uploadControl == null)
                                continue;

                            final Element valueElement = parameterElement.element("value");
                            final String value = valueElement.getTextTrim();

                            final String filename;
                            {
                                final Element filenameElement = parameterElement.element("filename");
                                filename = (filenameElement != null) ? filenameElement.getTextTrim() : "";
                            }
                            final String mediatype;
                            {
                                final Element mediatypeElement = parameterElement.element("content-type");
                                mediatype = (mediatypeElement != null) ? mediatypeElement.getTextTrim() : "";
                            }
                            final String size = parameterElement.element("content-length").getTextTrim();

                            if (size.equals("0") && filename.equals("")) {
                                // No file was selected in the UI
                            } else {
                                // A file was selected in the UI (note that the file may be empty)
                                final String paramValueType = Dom4jUtils.qNameToexplodedQName(Dom4jUtils.extractAttributeValueQName(valueElement, XMLConstants.XSI_TYPE_QNAME));

                                // Set value of uploaded file into the instance (will be xs:anyURI or xs:base64Binary)
                                uploadControl.setExternalValue(pipelineContext, value, paramValueType, !isReplaceAll);

                                // Handle filename, mediatype and size if necessary
                                uploadControl.setFilename(pipelineContext, filename);
                                uploadControl.setMediatype(pipelineContext, mediatype);
                                uploadControl.setSize(pipelineContext, size);
                            }
                        }
                    }

                    // Create document to submit
                    try {
                        documentToSubmit = createDocumentToSubmit(currentNodeInfo, currentInstance);

                        // Temporarily change instance document so that we can run validation again
                        modelForInstance.setInstanceDocument(documentToSubmit,
                                currentInstance.getModelId(), currentInstance.getEffectiveId(), currentInstance.getSourceURI(),
                                currentInstance.getUsername(), currentInstance.getPassword(),
                                currentInstance.isApplicationShared(),
                                currentInstance.getTimeToLive(),
                                currentInstance.getValidation());

                        // Revalidate instance
                        modelForInstance.doRevalidate(pipelineContext);
                        // sent out. Check if the validation state can really change. If so, find a
                        // solution.
                        // "no notification events are marked for dispatching due to this operation"

                        // Check that there are no validation errors
                        // NOTE: If the instance is read-only, it can't have MIPs, and can't fail validation/requiredness, so we don't go through the process at all.
                        final boolean instanceSatisfiesValidRequired = currentInstance.isReadOnly() || isDocumentSatisfiesValidRequired(documentToSubmit);
                        if (!instanceSatisfiesValidRequired) {
                            if (logger.isDebugEnabled()) {
                                final LocationDocumentResult documentResult = new LocationDocumentResult();
                                final TransformerHandler identity = TransformerUtils.getIdentityTransformerHandler();
                                identity.setResult(documentResult);
                                currentInstance.read(identity);
                                final String documentString = Dom4jUtils.domToString(documentResult.getDocument());

                                logger.debug("XForms - submission - instance document or subset thereof cannot be submitted:\n" + documentString);
                            }
                            throw new XFormsSubmissionException("xforms:submission: instance to submit does not satisfy valid and/or required model item properties.",
                                    "checking instance validity", XFormsSubmitErrorEvent.ErrorType.VALIDATION_ERROR);
                        }
                    } finally {
                        // Restore instance document
                        modelForInstance.setInstance(currentInstance, false);
                    }
                } else {
                    // Don't recreate document
                    documentToSubmit = initialDocumentToSubmit;
                }

                if (serialize && !isDeferredSubmissionSecondPassReplaceAll) { // we don't want any changes to happen to the document upon xxforms-submit when producing a new document
                    // Fire xforms-submit-serialize

                    // "The event xforms-submit-serialize is dispatched. If the submission-body property of the event
                    // is changed from the initial value of empty string, then the content of the submission-body
                    // property string is used as the submission serialization. Otherwise, the submission serialization
                    // consists of a serialization of the selected instance data according to the rules stated at 11.9
                    // Submission Options."

                    // TODO: pass submission-body attribute
                    containingDocument.dispatchEvent(pipelineContext, new XFormsSubmitSerializeEvent(XFormsModelSubmission.this));
                    // TODO: look at result of submission-body attribute and see if submission body needs to be replaced
                }

                // Serialize
                // To support: application/xml, application/x-www-form-urlencoded, multipart/related, multipart/form-data
                final byte[] messageBody;
                final String queryString;
                final String defaultMediatype;
                {
                    if (method.equals("multipart-post")) {
                        // TODO
                        throw new XFormsSubmissionException("xforms:submission: submission method not yet implemented: " + method, "serializing instance");
                    } else if (method.equals("form-data-post")) {
                        // TODO

//                        final MultipartFormDataBuilder builder = new MultipartFormDataBuilder(, , null);

                        throw new XFormsSubmissionException("xforms:submission: submission method not yet implemented: " + method, "serializing instance");
                    } else if (method.equals("urlencoded-post")) {

                        // Perform "application/x-www-form-urlencoded" serialization
                        queryString = null;
                        messageBody = serialize? createWwwFormUrlEncoded(documentToSubmit).getBytes("UTF-8") : null;// the resulting string is already ASCII
                        defaultMediatype = "application/x-www-form-urlencoded";

                    } else if (XFormsSubmissionUtils.isPost(method) || XFormsSubmissionUtils.isPut(method)) {

                        if (serialize) {
                            // Serialize XML to a stream of bytes
                            try {
                                final Transformer identity = TransformerUtils.getIdentityTransformer();
                                TransformerUtils.applyOutputProperties(identity,
                                        "xml", version, null, null, encoding, omitxmldeclaration, standalone, indent, 4);

                                // TODO: use cdata-section-elements

                                final ByteArrayOutputStream os = new ByteArrayOutputStream();
                                identity.transform(new DocumentSource(documentToSubmit), new StreamResult(os));
                                messageBody = os.toByteArray();
                            } catch (Exception e) {
                                throw new XFormsSubmissionException(e, "xforms:submission: exception while serializing instance to XML.", "serializing instance");
                            }
                        } else {
                            messageBody = null;
                        }
                        queryString = null;
                        defaultMediatype = "application/xml";

                    } else if (XFormsSubmissionUtils.isGet(method) || XFormsSubmissionUtils.isDelete(method)) {

                        // Perform "application/x-www-form-urlencoded" serialization
                        queryString = serialize ? createWwwFormUrlEncoded(documentToSubmit) : null;
                        messageBody = null;
                        defaultMediatype = null;

                    } else {
                        throw new XFormsSubmissionException("xforms:submission: invalid submission method requested: " + method, "serializing instance");
                    }
                }

                final ExternalContext externalContext = (ExternalContext) pipelineContext.getAttribute(PipelineContext.EXTERNAL_CONTEXT);

                // Get URL type
                final String urlType = submissionElement.attributeValue(XMLConstants.FORMATTING_URL_TYPE_QNAME);
                final ExternalContext.Request request = externalContext.getRequest();

                // Find instance to update
                final XFormsInstance replaceInstance;
                if (isReplaceInstance) {
                    if (xxfReplaceInstanceId != null)
                        replaceInstance = containingDocument.findInstance(xxfReplaceInstanceId);
                    else if (replaceInstanceId != null)
                        replaceInstance = model.getInstance(replaceInstanceId);
                    else
                        replaceInstance = currentInstance;
                } else {
                    replaceInstance = null;
                }

                // Result information
                ConnectionResult connectionResult = null;
                final long externalSubmissionStartTime = XFormsServer.logger.isDebugEnabled() ? System.currentTimeMillis() : 0;
                try {
                    if (isReplaceInstance && resolvedActionOrResource.startsWith("test:")) {
                        // Test action

                        if (messageBody == null)
                            throw new XFormsSubmissionException("Action 'test:': no message body.", "processing submission response");

                        connectionResult = new ConnectionResult(null);
                        connectionResult.resultCode = 200;
                        connectionResult.resultHeaders = new HashMap();
                        connectionResult.lastModified = 0;
                        connectionResult.resultMediaType = "application/xml";
                        connectionResult.dontHandleResponse = false;
                        connectionResult.setResultInputStream(new ByteArrayInputStream(messageBody));

                    } else if (isHandlingOptimizedGet) {
                        // GET with replace="all": we can optimize and tell the client to just load the URL

                        final String actionString = (queryString == null) ? resolvedActionOrResource : resolvedActionOrResource + ((resolvedActionOrResource.indexOf('?') == -1) ? "?" : "") + queryString;
                        final String resultURL = XFormsLoadAction.resolveLoadValue(containingDocument, pipelineContext, submissionElement, true, actionString, null, null, fURLNorewrite, xxfShowProgress);
                        connectionResult = new ConnectionResult(resultURL);
                        connectionResult.dontHandleResponse = true;

                    } else if (!NetUtils.urlHasProtocol(resolvedActionOrResource)
                               && !fURLNorewrite
                               && ((request.getContainerType().equals("portlet") && !"resource".equals(urlType))
                                    || (request.getContainerType().equals("servlet")
                                        && (XFormsUtils.isOptimizeLocalSubmission() || isMethodOptimizedLocalSubmission())
                                        &&  isReplaceAll))) {

                        // This is an "optimized" submission, i.e. one that does not use an actual
                        // protocol handler to access the resource

                        // NOTE: Optimizing with include() for servlets doesn't allow detecting
                        // errors caused by the included resource, so we don't allow this for now.

                        // NOTE: For portlets, paths are served directly by the portlet, NOT as
                        // resources.

                        // Current limitations:
                        // o Portlets cannot access resources outside the portlet except by using absolute URLs (unless f:url-type="resource")
                        // o Servlets cannot access resources on the same server but not in the current application
                        //   except by using absolute URLs

                        final URI resolvedURI = XFormsUtils.resolveXMLBase(submissionElement, resolvedActionOrResource);

                        // NOTE: We don't want any changes to happen to the document upon xxforms-submit when producing
                        // a new document so we don't dispatch xforms-submit-done and pass a null XFormsModelSubmission
                        // in that case
                        connectionResult = XFormsSubmissionUtils.doOptimized(pipelineContext, externalContext,
                                isDeferredSubmissionSecondPassReplaceAll ? null : this, method, resolvedURI.toString(), (mediatype == null) ? defaultMediatype : mediatype, isReplaceAll,
                                messageBody, queryString);

                    } else {
                        // This is a regular remote submission going through a protocol handler

                        // Absolute URLs or absolute paths are allowed to a local servlet
                        String resolvedURL;

                        if (NetUtils.urlHasProtocol(resolvedActionOrResource) || fURLNorewrite) {
                            // Don't touch the URL if it is absolute or if f:url-norewrite="true"
                            resolvedURL = resolvedActionOrResource;
                        } else {
                            // Rewrite URL
                            resolvedURL= XFormsUtils.resolveResourceURL(pipelineContext, submissionElement, resolvedActionOrResource);

                            if (request.getContainerType().equals("portlet") && "resource".equals(urlType) && !NetUtils.urlHasProtocol(resolvedURL)) {
                                // In this case, we have to prepend the complete server path
                                resolvedURL = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() > 0 ? ":" + request.getServerPort() : "") + resolvedURL;
                            }
                        }

                        if (isApplicationSharedHint) {
                            // Get the instance from shared instance cache
                            // This can only happen is method="get" and replace="instance" and xxforms:readonly="true" and xxforms:shared="application"

                            if (XFormsServer.logger.isDebugEnabled())
                                XFormsServer.logger.debug("XForms - submission - using instance from application shared instance cache: " + replaceInstance.getEffectiveId());

                            final URL absoluteResolvedURL = XFormsSubmissionUtils.createAbsoluteURL(resolvedURL, queryString, externalContext);
                            final String absoluteResolvedURLString = absoluteResolvedURL.toExternalForm();

                            final SharedXFormsInstance sharedInstance
                                    = XFormsServerSharedInstancesCache.instance().find(pipelineContext, replaceInstance.getEffectiveId(), replaceInstance.getModelId(), absoluteResolvedURLString, timeToLive, replaceInstance.getValidation());

                            if (XFormsServer.logger.isDebugEnabled())
                                XFormsServer.logger.debug("XForms - submission - replacing instance with read-only instance: " + sharedInstance.getEffectiveId());

                            // Handle new instance and associated events
                            final XFormsModel replaceModel = sharedInstance.getModel(containingDocument);
                            replaceModel.handleNewInstanceDocuments(pipelineContext, sharedInstance);

                            connectionResult = null;
                            submitDone = true;
                        } else {
                            // Perform actual submission
                            connectionResult = XFormsSubmissionUtils.doRegular(externalContext,
                                    method, resolvedURL, resolvedXXFormsUsername, resolvedXXFormsPassword, (mediatype == null) ? defaultMediatype : mediatype,
                                    messageBody, queryString);
                        }
                    }

                    if (connectionResult != null && !connectionResult.dontHandleResponse) {
                        // Handle response
                        if (connectionResult.resultCode >= 200 && connectionResult.resultCode < 300) {// accept any success code (in particular "201 Resource Created")
                            // Sucessful response
                            if (connectionResult.hasContent()) {
                                // There is a body

                                if (isReplaceAll) {
                                    // When we get here, we are in a mode where we need to send the reply
                                    // directly to an external context, if any.

                                    // "the event xforms-submit-done is dispatched"
                                    if (!isDeferredSubmissionSecondPassReplaceAll) // we don't want any changes to happen to the document upon xxforms-submit when producing a new document
                                        containingDocument.dispatchEvent(pipelineContext, new XFormsSubmitDoneEvent(XFormsModelSubmission.this));

                                    final ExternalContext.Response response = externalContext.getResponse();

                                    // Forward headers to response
                                    connectionResult.forwardHeaders(response);

                                    // Forward content to response
                                    NetUtils.copyStream(connectionResult.getResultInputStream(), response.getOutputStream());

                                    // TODO: [#306918] RFE: Must be able to do replace="all" during initialization.
                                    // http://forge.objectweb.org/tracker/index.php?func=detail&aid=306918&group_id=168&atid=350207
                                    // Suggestion is to write either binary or XML to processor output ContentHandler,
                                    // and make sure the code which would output the XHTML+XForms is disabled.

                                } else if (isReplaceInstance) {

                                    if (ProcessorUtils.isXMLContentType(connectionResult.resultMediaType)) {
                                        // Handling of XML media type
                                        try {
                                            // Set new instance document to replace the one submitted

                                            if (replaceInstance == null) {
                                                // Replacement instance was specified but not found
                                                // TODO: XForms 1.1 won't dispatch xforms-binding-exception here, but instead
                                            // will dispatch xforms-submit-error with error-type of target-error.
                                                containingDocument.dispatchEvent(pipelineContext, new XFormsBindingExceptionEvent(XFormsModelSubmission.this));
                                            } else {

                                                // Read stream into Document
                                                final XFormsInstance newInstance;
                                                if (!isReadonlyHint) {
                                                    // Resulting instance is not read-only

                                                    if (XFormsServer.logger.isDebugEnabled())
                                                        XFormsServer.logger.debug("XForms - submission - replacing instance with mutable instance: " + replaceInstance.getEffectiveId());

                                                    // TODO: Sure what default we want. In most cases, we don't use DTDs. Will XML Schema validate as well?
                                                    final boolean validating = false;

                                                    // We don't want XInclude automatically handled for security reasons
                                                    final boolean handleXInclude = false;

                                                    // TODO: Use TransformerUtils.readDom4j() instead?

                                                    final Document resultingInstanceDocument = Dom4jUtils.readDom4j(connectionResult.getResultInputStream(), connectionResult.resourceURI, validating, handleXInclude);
                                                    newInstance = new XFormsInstance(replaceInstance.getModelId(), replaceInstance.getEffectiveId(), resultingInstanceDocument,
                                                            connectionResult.resourceURI, resolvedXXFormsUsername, resolvedXXFormsPassword, false, -1, replaceInstance.getValidation());
                                                } else {
                                                    // Resulting instance is read-only

                                                    if (XFormsServer.logger.isDebugEnabled())
                                                        XFormsServer.logger.debug("XForms - submission - replacing instance with read-only instance: " + replaceInstance.getEffectiveId());

                                                    // TODO: Handle validating and handleXInclude!

                                                    // NOTE: isApplicationSharedHint is always false when get get here. isApplicationSharedHint="true" is handled above.
                                                    final DocumentInfo resultingInstanceDocument = TransformerUtils.readTinyTree(connectionResult.getResultInputStream(), connectionResult.resourceURI);
                                                    newInstance = new SharedXFormsInstance(replaceInstance.getModelId(), replaceInstance.getEffectiveId(), resultingInstanceDocument,
                                                            connectionResult.resourceURI, resolvedXXFormsUsername, resolvedXXFormsPassword, false, -1, replaceInstance.getValidation());
                                                }

                                                // Handle new instance and associated events
                                                final XFormsModel replaceModel = newInstance.getModel(containingDocument);
                                                replaceModel.handleNewInstanceDocuments(pipelineContext, newInstance);

                                                // Notify that submission is done
                                                submitDone = true;
                                            }
                                        } catch (Exception e) {
                                            throw new XFormsSubmissionException(e, "xforms:submission: exception while serializing XML to instance.", "processing instance replacement",
                                            XFormsSubmitErrorEvent.ErrorType.PARSE_ERROR, connectionResult);
                                        }
                                    } else {
                                        // Other media type
                                        throw new XFormsSubmissionException("Body received with non-XML media type for replace=\"instance\": " + connectionResult.resultMediaType, "processing instance replacement",
                                        XFormsSubmitErrorEvent.ErrorType.RESOURCE_ERROR, connectionResult);
                                    }
                                } else if (replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_NONE)) {
                                    // Just notify that processing is terminated
                                    submitDone = true;
                                } else {
                                    throw new XFormsSubmissionException("xforms:submission: invalid replace attribute: " + replace, "processing instance replacement",
                                    XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS, connectionResult);
                                }

                            } else {
                                // There is no body, notify that processing is terminated
                                submitDone = true;
                            }
                        } else if (connectionResult.resultCode == 302 || connectionResult.resultCode == 301) {
                            // Got a redirect

                            final ExternalContext.Response response = externalContext.getResponse();

                            // Forward headers to response
                            connectionResult.forwardHeaders(response);

                            // Forward redirect
                            response.setStatus(connectionResult.resultCode);

                        } else {
                            // Error code received
//                            submitErrorEvent = createErrorEvent(connectionResult, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS);
                            throw new XFormsSubmissionException("xforms:submission for submission id: " + id + ", error code received when submitting instance: " + connectionResult.resultCode, "processing submission response",
                            XFormsSubmitErrorEvent.ErrorType.RESOURCE_ERROR, connectionResult);
                        }
                    }
                } finally {
                    // Clean-up
                    if (connectionResult != null) {
                        connectionResult.close();
                    }
                    // Log time spent in submission if needed
                    if (XFormsServer.logger.isDebugEnabled()) {
                        final long submissionTime = System.currentTimeMillis() - externalSubmissionStartTime;
                        XFormsServer.logger.debug("XForms - submission - external submission time (including handling returned body): " + submissionTime);
                    }
                }
            } catch (Throwable e) {
                if (isDeferredSubmissionSecondPassReplaceAll && XFormsUtils.isOptimizePostAllSubmission()) {
                    // It doesn't serve any purpose here to dispatch an event, so we just propagate the exception
                    throw new XFormsSubmissionException(e, "Error while processing xforms:submission", "processing submission");
                } else {
                // Any exception will cause an error event to be dispatched
                final XFormsSubmitErrorEvent submitErrorEvent;
                if(e instanceof XFormsSubmissionException) {
                final XFormsSubmissionException submissionException = (XFormsSubmissionException) e;
                submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this, resolvedActionOrResource,submissionException.getErrorType(), submissionException.getConnectionResult());
                } else {
                submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this, resolvedActionOrResource, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS,null);
                }

                    submitErrorEvent.setThrowable(e);
                    containingDocument.dispatchEvent(pipelineContext, submitErrorEvent);
                }
            } finally {
                // If submission succeeded, dispatch success event
                if (submitDone && !isDeferredSubmissionSecondPassReplaceAll) { // we don't want any changes to happen to the document upon xxforms-submit when producing a new document
                    containingDocument.dispatchEvent(pipelineContext, new XFormsSubmitDoneEvent(XFormsModelSubmission.this));
                }
                // Log total time spent in submission if needed
                if (XFormsServer.logger.isDebugEnabled()) {
                    final long submissionTime = System.currentTimeMillis() - submissionStartTime;
                    XFormsServer.logger.debug("XForms - submission - total submission time: " + Long.toString(submissionTime));
                }
            }

        } else if (XFormsEvents.XFORMS_BINDING_EXCEPTION.equals(eventName)) {
            // The default action for this event results in the following: Fatal error.
            throw new OXFException("Binding exception.");// TODO: what location information should we provide here?
        }
    }

    private Document createDocumentToSubmit(final NodeInfo currentNodeInfo, final XFormsInstance currentInstance) {

        final Document documentToSubmit;
        if (currentNodeInfo instanceof NodeWrapper) {
            final Node currentNode = (Node) ((NodeWrapper) currentNodeInfo).getUnderlyingNode();

            // "A node from the instance data is selected, based on attributes on the submission
            // element. The indicated node and all nodes for which it is an ancestor are considered for
            // the remainder of the submit process. "
            if (currentNode instanceof Element) {
                // Create subset of document
                documentToSubmit = Dom4jUtils.createDocument((Element) currentNode);
            } else {
                // Use entire instance document
                documentToSubmit = Dom4jUtils.createDocument(currentInstance.getDocument().getRootElement());
            }

            if (relevant) {
                // "Any node which is considered not relevant as defined in 6.1.4 is removed."
                final Node[] nodeToDetach = new Node[1];
                do {
                    // NOTE: This is not very efficient, but at least we avoid NPEs that we would get by
                    // detaching elements within accept(). Should implement a more efficient algorithm to
                    // prune non-relevant nodes.
                    nodeToDetach[0] = null;
                    documentToSubmit.accept(new VisitorSupport() {

                        public final void visit(Element element) {
                            checkInstanceData(element);
                        }

                        public final void visit(Attribute attribute) {
                            checkInstanceData(attribute);
                        }

                        private final void checkInstanceData(Node node) {
                            if (nodeToDetach[0] == null) {
                                // Check "relevant" MIP and remove non-relevant nodes
                                    if (!InstanceData.getInheritedRelevant(node))
                                        nodeToDetach[0] = node;
                            }
                        }
                    });
                    if (nodeToDetach[0] != null)
                        nodeToDetach[0].detach();

                } while (nodeToDetach[0] != null);
            }

            // TODO: handle includenamespaceprefixes
        } else {
            // Submitting read-only instance backed by TinyTree (no MIPs to check)
            if (currentNodeInfo.getNodeKind() == org.w3c.dom.Document.ELEMENT_NODE) {
                documentToSubmit = TransformerUtils.tinyTreeToDom4j2(currentNodeInfo);
            } else {
                documentToSubmit = TransformerUtils.tinyTreeToDom4j2(currentNodeInfo.getRoot());
            }
        }
        return documentToSubmit;
    }

    private String createWwwFormUrlEncoded(final Document document) {

        final StringBuffer sb = new StringBuffer();
        document.accept(new VisitorSupport() {
            public final void visit(Element element) {
                // We only care about elements

                final List children = element.elements();
                if (children == null || children.size() == 0) {
                    // Only consider leaves
                    final String text = element.getText();
                    if (text != null && text.length() > 0) {
                        // Got one!
                        final String localName = element.getName();

                        if (sb.length() > 0)
                            sb.append(separator);

                        try {
                            sb.append(URLEncoder.encode(localName, "utf-8"));
                            sb.append('=');
                            sb.append(URLEncoder.encode(text, "utf-8"));
                            // TODO: check if line breaks will be correcly encoded as "%0D%0A"
                        } catch (UnsupportedEncodingException e) {
                            // Should not happen: utf-8 must be supported
                            throw new OXFException(e);
                        }
                    }
                }
            }
        });

        return sb.toString();
    }

    private boolean isDocumentSatisfiesValidRequired(final Document documentToSubmit) {
        final boolean[] instanceSatisfiesValidRequired = new boolean[]{true};
        if (validate) {
            documentToSubmit.accept(new VisitorSupport() {

                public final void visit(Element element) {
                    final boolean valid = checkInstanceData(element);

                    instanceSatisfiesValidRequired[0] &= valid;

                    if (!valid && XFormsServer.logger.isDebugEnabled()) {
                        XFormsServer.logger.debug("XForms - submission - found invalid element: " + elementToString(element));
                    }
                }

                public final void visit(Attribute attribute) {
                    final boolean valid = checkInstanceData(attribute);

                    instanceSatisfiesValidRequired[0] &= valid;

                    if (!valid && XFormsServer.logger.isDebugEnabled()) {
                        XFormsServer.logger.debug("XForms - submission - found invalid attribute: " + attributeToString(attribute)
                                + " (parent element: " + elementToString(attribute.getParent()) + ")");
                    }
                }

                private final boolean checkInstanceData(Node node) {
                    // Check "valid" MIP
                    if (!InstanceData.getValid(node)) return false;
                    // Check "required" MIP
                    {
                        final boolean isRequired = InstanceData.getRequired(node);
                        if (isRequired) {
                            final String value = XFormsInstance.getValueForNode(node);
                            if (value.length() == 0) {
                                // Required and empty
                                return false;
                            }
                        }
                    }
                    return true;
                }
            });
        }
        return instanceSatisfiesValidRequired[0];
    }

    private static String elementToString(Element element) {
        // Open start tag
        final FastStringBuffer sb = new FastStringBuffer("<");
        sb.append(element.getQualifiedName());

        // Attributes if any
        for (Iterator i = element.attributeIterator(); i.hasNext();) {
            final Attribute currentAttribute = (Attribute) i.next();

            sb.append(' ');
            sb.append(currentAttribute.getQualifiedName());
            sb.append("=\"");
            sb.append(currentAttribute.getValue());
            sb.append('\"');
        }

        // Close start tag
        sb.append('>');

        if (!element.elements().isEmpty()) {
            // Mixed content
            final Object firstChild = element.content().get(0);
            if (firstChild instanceof Text) {
                sb.append(((Text) firstChild).getText());
            }
            sb.append("[...]");
        } else {
            // Not mixed content
            sb.append(element.getText());
        }

        // Close element with end tag
        sb.append("</");
        sb.append(element.getQualifiedName());
        sb.append('>');

        return sb.toString();
    }

    private static String attributeToString(Attribute attribute) {
        final FastStringBuffer sb = new FastStringBuffer(attribute.getQualifiedName());
        sb.append("=\"");
        sb.append(attribute.getValue());
        sb.append('\"');
        return sb.toString();
    }

    public static class ConnectionResult {
        public boolean dontHandleResponse;
        public int resultCode;
        public String resultMediaType;
        public Map resultHeaders;
        public long lastModified;
        public String resourceURI;

        private InputStream resultInputStream;
        private boolean hasContent;

        public ConnectionResult(String resourceURI) {
            this.resourceURI = resourceURI;
        }

        public InputStream getResultInputStream() {
        return resultInputStream;
        }

        public boolean hasContent() {
            return hasContent;
        }

        public void setResultInputStream(final InputStream resultInputStream) throws IOException {
        this.resultInputStream = resultInputStream;
        setHasContentFlag();

        }

        private void setHasContentFlag() throws IOException {
            if (resultInputStream == null) {
                hasContent = false;
            } else {
                if (!resultInputStream.markSupported())
                    this.resultInputStream = new BufferedInputStream(resultInputStream);

                resultInputStream.mark(1);
                hasContent = resultInputStream.read() != -1;
                resultInputStream.reset();
            }
        }

        public void forwardHeaders(ExternalContext.Response response) {
            if (resultHeaders != null) {
                for (Iterator i = resultHeaders.entrySet().iterator(); i.hasNext();) {
                    final Map.Entry currentEntry = (Map.Entry) i.next();
                    final String headerName = (String) currentEntry.getKey();

                    if (headerName != null) {
                        // NOTE: As per the doc, this should always be a List, but for some unknown reason
                        // it appears to be a String sometimes
                        if (currentEntry.getValue() instanceof String) {
                            // Case of String
                            final String headerValue = (String) currentEntry.getValue();
                            forwardHeaderFilter(response, headerName, headerValue);
                        } else {
                            // Case of List
                            final List headerValues = (List) currentEntry.getValue();
                            if (headerValues != null) {
                                for (Iterator j = headerValues.iterator(); j.hasNext();) {
                                    final String headerValue = (String) j.next();
                                    if (headerValue != null) {
                                        forwardHeaderFilter(response, headerName, headerValue);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private void forwardHeaderFilter(ExternalContext.Response response, String headerName, String headerValue) {
            /**
             * Filtering the Transfer-Encoding header
             *
             * We don't pass the Transfer-Encoding header, as the request body is
             * already decoded for us. Passing along the Transfer-Encoding causes a
             * problem if the server sends us chunked data and we send it in the
             * response not chunked but saying in the header that it is chunked.
             *
             * Non-filtering of Content-Encoding header
             *
             * The Content-Encoding has the potential of causing the same problem as
             * the Transfer-Encoding header. It could be an issue if we get data with
             * Content-Encoding: gzip, but pass it along uncompressed but still
             * include the Content-Encoding: gzip. However this does not happen, as
             * the request we send does not contain a Accept-Encoding: gzip,deflate. So
             * filtering the Content-Encoding header is safe here.
             */
            if (!"transfer-encoding".equals(headerName.toLowerCase())) {
                response.addHeader(headerName, headerValue);
            }
        }

        public void close() {}
    }

    private class XFormsSubmissionException extends ValidationException {
   
    private final ErrorType errorType;
    private final ConnectionResult connectionResult;
   
    public XFormsSubmissionException(final String message, final String description) {
    this(message,description, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS);
    }
   
  public XFormsSubmissionException(final String message, final String description, final ErrorType errorType) {
    this(message,description, errorType,null);
        }
        public XFormsSubmissionException(final String message, final String description, final XFormsSubmitErrorEvent.ErrorType errorType, final ConnectionResult connectionResult) {
            super(message, new ExtendedLocationData(XFormsModelSubmission.this.getLocationData(), description,
                    XFormsModelSubmission.this.getSubmissionElement()));
    this.errorType = errorType;
    this.connectionResult = connectionResult;
    }
   
        public XFormsSubmissionException(final Throwable e, final String message, final String description) {
        this(e,message,description, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS,null);
        }
        public XFormsSubmissionException(final Throwable e, final String message, final String description, final XFormsSubmitErrorEvent.ErrorType errorType, final ConnectionResult connectionResult) {
            super(message, e, new ExtendedLocationData(XFormsModelSubmission.this.getLocationData(), description,
                    XFormsModelSubmission.this.getSubmissionElement()));
        this.errorType = errorType;
        this.connectionResult = connectionResult;
        }
 
                public ErrorType getErrorType() {
        return errorType;
        }
                public ConnectionResult getConnectionResult() {
                        return connectionResult;
                }
     }
}
/**
 *  Copyright (C) 2005 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.event;

import org.dom4j.Element;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.event.events.*;

import java.util.HashMap;
import java.util.Map;

/**
 * Factory for XForms events
 */
public class XFormsEventFactory {

    private static Map nameToClassMap = new HashMap();

    static {
        nameToClassMap.put(XFormsEvents.XFORMS_DOM_ACTIVATE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsDOMActivateEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_COMPUTE_EXCEPTION, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsComputeExceptionEvent(targetObject, contextString, contextThrowable);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_DELETE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsDeleteEvent(targetObject, contextString);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_DESELECT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsDeselectEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_INSERT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsInsertEvent(targetObject, contextString);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_LINK_ERROR, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsLinkErrorEvent(targetObject, contextString, contextElement, contextThrowable);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_LINK_EXCEPTION, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsLinkExceptionEvent(targetObject, contextString, contextElement, contextThrowable);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_BINDING_EXCEPTION, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsBindingExceptionEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_REFRESH, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsRefreshEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SELECT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsSelectEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SUBMIT_ERROR, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsSubmitErrorEvent(targetObject, contextString, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS,null);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SUBMIT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsSubmitEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SUBMIT_SERIALIZE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsSubmitSerializeEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SUBMIT_DONE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsSubmitDoneEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XXFORMS_READY, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XXFormsReadyEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XXFORMS_VALUE_CHANGE_WITH_FOCUS_CHANGE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XXFormsValueChangeWithFocusChangeEvent(targetObject, otherTargetObject, contextString);
            }
        });
        nameToClassMap.put(XFormsEvents.XXFORMS_SUBMIT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XXFormsSubmitEvent(targetObject, filesElement);
            }
        });
        nameToClassMap.put(XFormsEvents.XXFORMS_LOAD, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XXFormsLoadEvent(targetObject, contextString);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_MODEL_CONSTRUCT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsModelConstructEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_MODEL_DESTRUCT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsModelDestructEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_RESET, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsResetEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_MODEL_CONSTRUCT_DONE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsModelConstructDoneEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_READY, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsReadyEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_REBUILD, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsRebuildEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_RECALCULATE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsRecalculateEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_REVALIDATE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsRevalidateEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_VALUE_CHANGED, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsValueChangeEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_DOM_FOCUS_OUT, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsDOMFocusOutEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_DOM_FOCUS_IN, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsDOMFocusInEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_VALID, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsValidEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_INVALID, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsInvalidEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_REQUIRED, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsRequiredEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_OPTIONAL, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsOptionalEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_READWRITE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsReadwriteEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_READONLY, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsReadonlyEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_ENABLED, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsEnabledEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_DISABLED, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsDisabledEvent((XFormsControl) targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_FOCUS, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsFocusEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SCROLL_FIRST, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsScrollFirstEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_SCROLL_LAST, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsScrollLastEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_LINK_EXCEPTION, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsLinkExceptionEvent(targetObject, contextString, contextElement, contextThrowable);
            }
        });
        nameToClassMap.put(XFormsEvents.XFORMS_LINK_ERROR, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XFormsLinkErrorEvent(targetObject, contextString, contextElement, contextThrowable);
            }
        });
        nameToClassMap.put(XFormsEvents.XXFORMS_DIALOG_CLOSE, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XXFormsDialogCloseEvent(targetObject);
            }
        });
        nameToClassMap.put(XFormsEvents.XXFORMS_DIALOG_OPEN, new Factory() {
            public XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {
                return new XXFormsDialogOpenEvent(targetObject);
            }
        });
    }

    public static XFormsEvent createEvent(String newEventName, XFormsEventTarget targetObject) {
        return createEvent(newEventName, targetObject, null, false, true, true, null, null, null, null);
    }

    public static XFormsEvent createEvent(String newEventName, XFormsEventTarget targetObject, boolean bubbles, boolean cancelable) {
        return createEvent(newEventName, targetObject, null, true, bubbles, cancelable, null, null, null, null);
    }

    public static XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject, boolean allowCustomEvents, boolean bubbles, boolean cancelable,
                                           String contextString, Element contextElement, Throwable contextThrowable, Element filesElement) {

        final Factory factory = (Factory) nameToClassMap.get(eventName);
        if (factory == null) {
            if (!allowCustomEvents) {
                // No custom events are allowed, just throw
                throw new OXFException("Invalid event name: " + eventName);
            } else {
                // Return a custom event
                return new XFormsCustomEvent(eventName, targetObject, bubbles, cancelable);
            }
        } else {
            // Return a built-in event
            return factory.createEvent(eventName, targetObject, otherTargetObject, allowCustomEvents, bubbles, cancelable, contextString, contextElement, contextThrowable, filesElement);
        }
    }

    /**
     * Check whether an event name maps to a built-in event.
     *
     * @param eventName event name to check
     * @return          true if built-in, false otherwise
     */
    public static boolean isBuiltInEvent(String eventName) {
        return nameToClassMap.get(eventName) != null;
    }

    private static abstract class Factory {
        public abstract XFormsEvent createEvent(String eventName, XFormsEventTarget targetObject, XFormsEventTarget otherTargetObject,
                                                boolean allowCustomEvents, boolean bubbles, boolean cancelable, String contextString,
                                                Element contextElement, Throwable contextThrowable, Element filesElement);
    }

    private XFormsEventFactory() {}
}

/**
 *  Copyright (C) 2005 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.event.events;

import java.io.CharArrayWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Collections;

import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.processor.ProcessorUtils;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xforms.XFormsModelSubmission.ConnectionResult;
import org.orbeon.oxf.xforms.event.XFormsEvent;
import org.orbeon.oxf.xforms.event.XFormsEventTarget;
import org.orbeon.oxf.xforms.event.XFormsEvents;
import org.orbeon.oxf.xforms.processor.XFormsServer;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.ListIterator;
import org.orbeon.saxon.om.SequenceIterator;
import org.orbeon.saxon.value.StringValue;


/**
 * 4.4.19 The xforms-submit-error Event
 *
 * Target: model / Bubbles: Yes / Cancelable: No / Context Info: The submit method URI that failed (xsd:anyURI)
 * The default action for this event results in the following: None; notification event only.
 */
public class XFormsSubmitErrorEvent extends XFormsEvent {
       
    public static final String DEFAULT_TEXT_READING_ENCODING = "iso-8859-1";

    private Throwable throwable;
    private final String urlString;
    private DocumentInfo bodyDocument;
    private String bodyString;
    private final ErrorType errorType;

    public XFormsSubmitErrorEvent(XFormsEventTarget targetObject, String urlString, final ErrorType errorType, final ConnectionResult connectionResult) {
        super(XFormsEvents.XFORMS_SUBMIT_ERROR, targetObject, true, false);
        this.urlString = urlString;
        this.errorType = errorType;
       
        if (connectionResult != null && connectionResult.hasContent()) {
            if (ProcessorUtils.isXMLContentType(connectionResult.resultMediaType)) {
                // XML content-type
                // TODO: XForms 1.1 may mandate that we always try to parse the body as XML first
                // Read stream into Document
                try {
                this.bodyDocument = TransformerUtils.readTinyTree(connectionResult.getResultInputStream(), connectionResult.resourceURI);
                } catch (Exception e) {
                    XFormsServer.logger.error("XForms - submission - error while parsing respond body ", e);
                }
            } else if (ProcessorUtils.isTextContentType(connectionResult.resultMediaType)) {
                // Text content-type
                // Read stream into String
                try {
                    final String charset;
                    {
                        final String connectionCharset = NetUtils.getContentTypeCharset(connectionResult.resultMediaType);
                        if (connectionCharset != null)
                            charset = connectionCharset;
                        else
                            charset = DEFAULT_TEXT_READING_ENCODING;
                    }
                    final Reader reader = new InputStreamReader(connectionResult.getResultInputStream(), charset);
                    this.bodyString = NetUtils.readStreamAsString(reader);
                } catch (Exception e) {
                    XFormsServer.logger.error("XForms - submission - error while reading respond body ", e);
                }
            } else {
                // This is binary
                // Don't store anything for now
            }
        }
    }

    public Throwable getThrowable() {
        return throwable;
    }

    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;

        // Log exception at error level
        final CharArrayWriter writer = new CharArrayWriter();
        OXFException.getRootThrowable(throwable).printStackTrace(new PrintWriter(writer));
        XFormsServer.logger.error("XForms - submission - xforms-submit-error throwable: " + writer.toString());
    }

    public String getUrlString() {
        return urlString;
    }

    public DocumentInfo getBodyDocument() {
        return bodyDocument;
    }

    public String getBodyString() {
        return bodyString;
    }

    public SequenceIterator getAttribute(String name) {

        if ("body".equals(name)) {
            // Return the body of the response if possible
            if (getBodyDocument() != null)
                return new ListIterator(Collections.singletonList(getBodyDocument()));
            else if (getBodyString() != null)
                return new ListIterator(Collections.singletonList(new StringValue(getBodyString())));
            else
                return super.getAttribute(name);
        } else if ("resource-uri".equals(name)) {
            return new ListIterator(Collections.singletonList(new StringValue(getUrlString())));
        } else if("error-type".equals(name)) {
        return new ListIterator(Collections.singletonList(new StringValue(String.valueOf(errorType))));
        } else {
            return super.getAttribute(name);
        }
    }
   
    /** Enumeration of possible error-type values. */
    public static class ErrorType {
   
    public static final ErrorType SUBMISSION_IN_PROGRESS = new ErrorType("submission-in-progress");
    public static final ErrorType NO_DATA = new ErrorType("no-data");
    public static final ErrorType VALIDATION_ERROR = new ErrorType("validation-error");
    public static final ErrorType RESOURCE_ERROR = new ErrorType("resource-error");
    public static final ErrorType PARSE_ERROR = new ErrorType("parse-error");
   
    private final String errorType;
   
    private ErrorType(final String errorType) {
    this.errorType = errorType;
    }

                public String toString() {
                        return errorType;
                }
    }
}


--
You receive this message as a subscriber of the [hidden email] mailing list.
To unsubscribe: mailto:[hidden email]
For general help: mailto:[hidden email]?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws
Reply | Threaded
Open this post in threaded view
|

Re: patch: support error-type attribute on xforms-submit-error event

Adrian Baker
Oops - slight problem with passing the connection result around: it's stream gets closed before it's read in the XFormsSubmitErrorEvent constructor. Will fix and resend.

Adrian Baker wrote:
Attached is a fairly straightforward patch to XFormsModelSubmission et al to support the XForms 1.1 error-type attribute (http://www.w3.org/TR/xforms11/#submit-evt-submit).

This event is particularly useful for distinguishing between submission failures caused by invalid data (user error) or a failure of the submission target (system error). Previously I had been using the xforms-submit-serialize to set a flag to identify the two cases, but came across a case where this was broken by @serialize="false" on the <xf:submission>, and rather than update my rather fiddly workaround it was quicker to implement the proper solution.

Previously the XFormsSubmitError event was being created throughout the body of the performDefaultAction() method (always just before an exception is thrown), as well as in a catch block in the bottom. I felt a bit uneasy about spreading the creation around like this, and changed this so the event is created solely in the catch block at the bottom, since there is always an exception thrown if the submission fails.

Adrian

______________________________________________________________________
This email has been scanned by the MessageLabs Email Security System.
For more information please visit http://www.messagelabs.com/email
______________________________________________________________________


--
You receive this message as a subscriber of the [hidden email] mailing list.
To unsubscribe: mailto:[hidden email]
For general help: mailto:[hidden email]?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws
Reply | Threaded
Open this post in threaded view
|

Re: patch: support error-type attribute on xforms-submit-error event

Adrian Baker
Fixed version attached.

Adrian Baker wrote:<a href="cid:part1.05060907.09090603@orionhealth.com">file:///E:/dev/Orbeon/Orion/work/src/java/org/orbeon/oxf/xforms/event/events/XFormsSubmitErrorEvent.java
Oops - slight problem with passing the connection result around: it's stream gets closed before it's read in the XFormsSubmitErrorEvent constructor. Will fix and resend.

Adrian Baker wrote:
Attached is a fairly straightforward patch to XFormsModelSubmission et al to support the XForms 1.1 error-type attribute (http://www.w3.org/TR/xforms11/#submit-evt-submit).

This event is particularly useful for distinguishing between submission failures caused by invalid data (user error) or a failure of the submission target (system error). Previously I had been using the xforms-submit-serialize to set a flag to identify the two cases, but came across a case where this was broken by @serialize="false" on the <xf:submission>, and rather than update my rather fiddly workaround it was quicker to implement the proper solution.

Previously the XFormsSubmitError event was being created throughout the body of the performDefaultAction() method (always just before an exception is thrown), as well as in a catch block in the bottom. I felt a bit uneasy about spreading the creation around like this, and changed this so the event is created solely in the catch block at the bottom, since there is always an exception thrown if the submission fails.

Adrian

______________________________________________________________________
This email has been scanned by the MessageLabs Email Security System.
For more information please visit http://www.messagelabs.com/email
______________________________________________________________________

______________________________________________________________________
This email has been scanned by the MessageLabs Email Security System.
For more information please visit http://www.messagelabs.com/email
______________________________________________________________________

/**
 *  Copyright (C) 2005 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.event.events;

import java.io.CharArrayWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Collections;

import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.processor.ProcessorUtils;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xforms.XFormsModelSubmission.ConnectionResult;
import org.orbeon.oxf.xforms.event.XFormsEvent;
import org.orbeon.oxf.xforms.event.XFormsEventTarget;
import org.orbeon.oxf.xforms.event.XFormsEvents;
import org.orbeon.oxf.xforms.processor.XFormsServer;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.ListIterator;
import org.orbeon.saxon.om.SequenceIterator;
import org.orbeon.saxon.value.StringValue;


/**
 * 4.4.19 The xforms-submit-error Event
 *
 * Target: model / Bubbles: Yes / Cancelable: No / Context Info: The submit method URI that failed (xsd:anyURI)
 * The default action for this event results in the following: None; notification event only.
 */
public class XFormsSubmitErrorEvent extends XFormsEvent {
       
    public static final String DEFAULT_TEXT_READING_ENCODING = "iso-8859-1";

    private Throwable throwable;
    private final String urlString;
    private DocumentInfo bodyDocument;
    private String bodyString;
    private final ErrorType errorType;

    public XFormsSubmitErrorEvent(XFormsEventTarget targetObject, String urlString, final ErrorType errorType, final ConnectionResult connectionResult) {
        super(XFormsEvents.XFORMS_SUBMIT_ERROR, targetObject, true, false);
        this.urlString = urlString;
        this.errorType = errorType;
       
        readConnectionResult(connectionResult);
    }

        private void readConnectionResult(final ConnectionResult connectionResult) {
                if (connectionResult != null && connectionResult.hasContent()) {
            if (ProcessorUtils.isXMLContentType(connectionResult.resultMediaType)) {
                // XML content-type
                // TODO: XForms 1.1 may mandate that we always try to parse the body as XML first
                // Read stream into Document
                try {
                this.bodyDocument = TransformerUtils.readTinyTree(connectionResult.getResultInputStream(), connectionResult.resourceURI);
                } catch (Exception e) {
                    XFormsServer.logger.error("XForms - submission - error while parsing respond body ", e);
                }
            } else if (ProcessorUtils.isTextContentType(connectionResult.resultMediaType)) {
                // Text content-type
                // Read stream into String
                try {
                    final String charset;
                    {
                        final String connectionCharset = NetUtils.getContentTypeCharset(connectionResult.resultMediaType);
                        if (connectionCharset != null)
                            charset = connectionCharset;
                        else
                            charset = DEFAULT_TEXT_READING_ENCODING;
                    }
                    final Reader reader = new InputStreamReader(connectionResult.getResultInputStream(), charset);
                    this.bodyString = NetUtils.readStreamAsString(reader);
                } catch (Exception e) {
                    XFormsServer.logger.error("XForms - submission - error while reading respond body ", e);
                }
            } else {
                // This is binary
                // Don't store anything for now
            }
        }
        }

    public Throwable getThrowable() {
        return throwable;
    }

    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;

        // Log exception at error level
        final CharArrayWriter writer = new CharArrayWriter();
        OXFException.getRootThrowable(throwable).printStackTrace(new PrintWriter(writer));
        XFormsServer.logger.error("XForms - submission - xforms-submit-error throwable: " + writer.toString());
    }

    public String getUrlString() {
        return urlString;
    }

    public DocumentInfo getBodyDocument() {
        return bodyDocument;
    }

    public String getBodyString() {
        return bodyString;
    }

    public SequenceIterator getAttribute(String name) {

        if ("body".equals(name)) {
            // Return the body of the response if possible
            if (getBodyDocument() != null)
                return new ListIterator(Collections.singletonList(getBodyDocument()));
            else if (getBodyString() != null)
                return new ListIterator(Collections.singletonList(new StringValue(getBodyString())));
            else
                return super.getAttribute(name);
        } else if ("resource-uri".equals(name)) {
            return new ListIterator(Collections.singletonList(new StringValue(getUrlString())));
        } else if("error-type".equals(name)) {
        return new ListIterator(Collections.singletonList(new StringValue(String.valueOf(errorType))));
        } else {
            return super.getAttribute(name);
        }
    }
   
    /** Enumeration of possible error-type values. */
    public static class ErrorType {
   
    public static final ErrorType SUBMISSION_IN_PROGRESS = new ErrorType("submission-in-progress");
    public static final ErrorType NO_DATA = new ErrorType("no-data");
    public static final ErrorType VALIDATION_ERROR = new ErrorType("validation-error");
    public static final ErrorType RESOURCE_ERROR = new ErrorType("resource-error");
    public static final ErrorType PARSE_ERROR = new ErrorType("parse-error");
   
    private final String errorType;
   
    private ErrorType(final String errorType) {
    this.errorType = errorType;
    }

                public String toString() {
                        return errorType;
                }
    }
}
/**
 *  Copyright (C) 2005 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.event.events;

import java.io.CharArrayWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Collections;

import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.processor.ProcessorUtils;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xforms.XFormsModelSubmission.ConnectionResult;
import org.orbeon.oxf.xforms.event.XFormsEvent;
import org.orbeon.oxf.xforms.event.XFormsEventTarget;
import org.orbeon.oxf.xforms.event.XFormsEvents;
import org.orbeon.oxf.xforms.processor.XFormsServer;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.ListIterator;
import org.orbeon.saxon.om.SequenceIterator;
import org.orbeon.saxon.value.StringValue;


/**
 * 4.4.19 The xforms-submit-error Event
 *
 * Target: model / Bubbles: Yes / Cancelable: No / Context Info: The submit method URI that failed (xsd:anyURI)
 * The default action for this event results in the following: None; notification event only.
 */
public class XFormsSubmitErrorEvent extends XFormsEvent {
       
    public static final String DEFAULT_TEXT_READING_ENCODING = "iso-8859-1";

    private Throwable throwable;
    private final String urlString;
    private DocumentInfo bodyDocument;
    private String bodyString;
    private final ErrorType errorType;

    public XFormsSubmitErrorEvent(XFormsEventTarget targetObject, String urlString, final ErrorType errorType, final ConnectionResult connectionResult) {
        super(XFormsEvents.XFORMS_SUBMIT_ERROR, targetObject, true, false);
        this.urlString = urlString;
        this.errorType = errorType;
       
        readConnectionResult(connectionResult);
    }

        private void readConnectionResult(final ConnectionResult connectionResult) {
                if (connectionResult != null && connectionResult.hasContent()) {
            if (ProcessorUtils.isXMLContentType(connectionResult.resultMediaType)) {
                // XML content-type
                // TODO: XForms 1.1 may mandate that we always try to parse the body as XML first
                // Read stream into Document
                try {
                this.bodyDocument = TransformerUtils.readTinyTree(connectionResult.getResultInputStream(), connectionResult.resourceURI);
                } catch (Exception e) {
                    XFormsServer.logger.error("XForms - submission - error while parsing respond body ", e);
                }
            } else if (ProcessorUtils.isTextContentType(connectionResult.resultMediaType)) {
                // Text content-type
                // Read stream into String
                try {
                    final String charset;
                    {
                        final String connectionCharset = NetUtils.getContentTypeCharset(connectionResult.resultMediaType);
                        if (connectionCharset != null)
                            charset = connectionCharset;
                        else
                            charset = DEFAULT_TEXT_READING_ENCODING;
                    }
                    final Reader reader = new InputStreamReader(connectionResult.getResultInputStream(), charset);
                    this.bodyString = NetUtils.readStreamAsString(reader);
                } catch (Exception e) {
                    XFormsServer.logger.error("XForms - submission - error while reading respond body ", e);
                }
            } else {
                // This is binary
                // Don't store anything for now
            }
        }
        }

    public Throwable getThrowable() {
        return throwable;
    }

    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;

        // Log exception at error level
        final CharArrayWriter writer = new CharArrayWriter();
        OXFException.getRootThrowable(throwable).printStackTrace(new PrintWriter(writer));
        XFormsServer.logger.error("XForms - submission - xforms-submit-error throwable: " + writer.toString());
    }

    public String getUrlString() {
        return urlString;
    }

    public DocumentInfo getBodyDocument() {
        return bodyDocument;
    }

    public String getBodyString() {
        return bodyString;
    }

    public SequenceIterator getAttribute(String name) {

        if ("body".equals(name)) {
            // Return the body of the response if possible
            if (getBodyDocument() != null)
                return new ListIterator(Collections.singletonList(getBodyDocument()));
            else if (getBodyString() != null)
                return new ListIterator(Collections.singletonList(new StringValue(getBodyString())));
            else
                return super.getAttribute(name);
        } else if ("resource-uri".equals(name)) {
            return new ListIterator(Collections.singletonList(new StringValue(getUrlString())));
        } else if("error-type".equals(name)) {
        return new ListIterator(Collections.singletonList(new StringValue(String.valueOf(errorType))));
        } else {
            return super.getAttribute(name);
        }
    }
   
    /** Enumeration of possible error-type values. */
    public static class ErrorType {
   
    public static final ErrorType SUBMISSION_IN_PROGRESS = new ErrorType("submission-in-progress");
    public static final ErrorType NO_DATA = new ErrorType("no-data");
    public static final ErrorType VALIDATION_ERROR = new ErrorType("validation-error");
    public static final ErrorType RESOURCE_ERROR = new ErrorType("resource-error");
    public static final ErrorType PARSE_ERROR = new ErrorType("parse-error");
   
    private final String errorType;
   
    private ErrorType(final String errorType) {
    this.errorType = errorType;
    }

                public String toString() {
                        return errorType;
                }
    }
}
/**
 *  Copyright (C) 2005 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms;

import org.apache.log4j.Logger;
import org.dom4j.*;
import org.dom4j.io.DocumentSource;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.ProcessorUtils;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xforms.action.actions.XFormsLoadAction;
import org.orbeon.oxf.xforms.control.controls.XFormsUploadControl;
import org.orbeon.oxf.xforms.event.*;
import org.orbeon.oxf.xforms.event.events.*;
import org.orbeon.oxf.xforms.event.events.XFormsSubmitErrorEvent.ErrorType;
import org.orbeon.oxf.xforms.processor.XFormsServer;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.XMLUtils;
import org.orbeon.oxf.xml.dom4j.Dom4jUtils;
import org.orbeon.oxf.xml.dom4j.LocationData;
import org.orbeon.oxf.xml.dom4j.LocationDocumentResult;
import org.orbeon.oxf.xml.dom4j.ExtendedLocationData;
import org.orbeon.saxon.dom4j.NodeWrapper;
import org.orbeon.saxon.functions.FunctionLibrary;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.NodeInfo;
import org.orbeon.saxon.om.FastStringBuffer;

import javax.xml.transform.Transformer;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Represents an XForms model submission instance.
 *
 * TODO: This badly needs to be modularized instead of being a soup of "ifs"!
 */
public class XFormsModelSubmission implements XFormsEventTarget, XFormsEventHandlerContainer {

        public final static Logger logger = LoggerFactory.createLogger(XFormsModelSubmission.class);

    private final XFormsContainingDocument containingDocument;
    private final String id;
    private final XFormsModel model;
    private final Element submissionElement;
    private boolean submissionElementExtracted = false;

    // Event handlers
    private final List eventHandlers;

    private String avtActionOrResource; // required unless there is a nested xforms:resource element
    private String resolvedActionOrResource;
    private String method; // required

    private boolean validate = true;
    private boolean relevant = true;
    private boolean serialize = true;

    private String version;
    private boolean indent;
    private String mediatype;
    private String encoding;
    private boolean omitxmldeclaration;
    private Boolean standalone;
    private String cdatasectionelements;

    private String replace = XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL;
    private String replaceInstanceId;
    private String xxfReplaceInstanceId;
    private String separator = ";";
    private String includenamespaceprefixes;

    private String avtXXFormsUsername;
    private String resolvedXXFormsUsername;
    private String avtXXFormsPassword;
    private String resolvedXXFormsPassword;
    private String avtXXFormsReadonly;
    private String resolvedXXFormsReadonly;
    private String avtXXFormsShared;
    private String resolvedXXFormsShared;
    private String avtXXFormsTarget;
    private String resolvedXXFormsTarget;

    private boolean xxfShowProgress;

    private boolean fURLNorewrite;

    public XFormsModelSubmission(XFormsContainingDocument containingDocument, String id, Element submissionElement, XFormsModel model) {
        this.containingDocument = containingDocument;
        this.id = id;
        this.submissionElement = submissionElement;
        this.model = model;

        // Extract event handlers
        eventHandlers = XFormsEventHandlerImpl.extractEventHandlers(containingDocument, this, submissionElement);
    }

    public XFormsContainingDocument getContainingDocument() {
        return containingDocument;
    }

    public Element getSubmissionElement() {
        return submissionElement;
    }


    public boolean isXxfShowProgress() {
        return xxfShowProgress;
    }

    public String getReplace() {
        return replace;
    }

    public String getResolvedXXFormsTarget() {
        return resolvedXXFormsTarget;
    }

    private void extractSubmissionElement() {
        if (!submissionElementExtracted) {

            avtActionOrResource = submissionElement.attributeValue("resource");
            if (avtActionOrResource == null) // @resource has precedence over @action
                avtActionOrResource = submissionElement.attributeValue("action");
            if (avtActionOrResource == null) {
                // TODO: For XForms 1.1, support @resource and nested xforms:resource
                throw new XFormsSubmissionException("xforms:submission: action attribute or resource attribute is missing.",
                        "processing xforms:submission attributes");
            }

            method = submissionElement.attributeValue("method");
            method = Dom4jUtils.qNameToexplodedQName(Dom4jUtils.extractAttributeValueQName(submissionElement, "method"));

            validate = !"false".equals(submissionElement.attributeValue("validate"));
            relevant = !"false".equals(submissionElement.attributeValue("relevant"));
            serialize = !"false".equals(submissionElement.attributeValue("serialize"));

            version = submissionElement.attributeValue("version");

            if (submissionElement.attributeValue("indent") != null) {
                indent = Boolean.valueOf(submissionElement.attributeValue("indent")).booleanValue();
            }
            mediatype = submissionElement.attributeValue("mediatype");
            encoding = submissionElement.attributeValue("encoding");
            if (submissionElement.attributeValue("omitxmldeclaration") != null) {
                omitxmldeclaration = Boolean.valueOf(submissionElement.attributeValue("omit-xml-declaration")).booleanValue();
            }
            if (submissionElement.attributeValue("standalone") != null) {
                standalone = new Boolean(submissionElement.attributeValue("standalone"));
            }

            cdatasectionelements = submissionElement.attributeValue("cdata-section-elements");
            if (submissionElement.attributeValue("replace") != null) {
                replace = submissionElement.attributeValue("replace");

                if (replace.equals("instance")) {
                    replaceInstanceId = XFormsUtils.namespaceId(containingDocument, submissionElement.attributeValue("instance"));
                    xxfReplaceInstanceId = XFormsUtils.namespaceId(containingDocument, submissionElement.attributeValue(XFormsConstants.XXFORMS_INSTANCE_QNAME));
                }
            }
            if (submissionElement.attributeValue("separator") != null) {
                separator = submissionElement.attributeValue("separator");
            }
            includenamespaceprefixes = submissionElement.attributeValue("includenamespaceprefixes");

            // Extension attributes
            avtXXFormsUsername = submissionElement.attributeValue(XFormsConstants.XXFORMS_USERNAME_QNAME);
            avtXXFormsPassword = submissionElement.attributeValue(XFormsConstants.XXFORMS_PASSWORD_QNAME);

            avtXXFormsReadonly = submissionElement.attributeValue(XFormsConstants.XXFORMS_READONLY_ATTRIBUTE_QNAME);
            avtXXFormsShared = submissionElement.attributeValue(XFormsConstants.XXFORMS_SHARED_QNAME);

            avtXXFormsTarget = submissionElement.attributeValue(XFormsConstants.XXFORMS_TARGET_QNAME);

            // Whether we must show progress or not
            xxfShowProgress = !"false".equals(submissionElement.attributeValue(XFormsConstants.XXFORMS_SHOW_PROGRESS_QNAME));

            // Whether or not to rewrite URLs
            fURLNorewrite = XFormsUtils.resolveUrlNorewrite(submissionElement);

            // Remember that we did this
            submissionElementExtracted = true;
        }
    }

    private boolean isMethodOptimizedLocalSubmission() {
        return method.startsWith(XMLUtils.buildExplodedQName(XFormsConstants.XXFORMS_NAMESPACE_URI, ""))
                && (XFormsSubmissionUtils.isGet(method) || XFormsSubmissionUtils.isPost(method) || XFormsSubmissionUtils.isPut(method));
    }

    public String getEffectiveId() {
        return id;
    }

    public LocationData getLocationData() {
        return (LocationData) submissionElement.getData();
    }

    public XFormsEventHandlerContainer getParentContainer(XFormsContainingDocument containingDocument) {
        return model;
    }

    public List getEventHandlers(XFormsContainingDocument containingDocument) {
        return eventHandlers;
    }

    public void performDefaultAction(PipelineContext pipelineContext, XFormsEvent event) {
        final String eventName = event.getEventName();

        if (XFormsEvents.XFORMS_SUBMIT.equals(eventName) || XFormsEvents.XXFORMS_SUBMIT.equals(eventName)) {
            // 11.1 The xforms-submit Event
            // Bubbles: Yes / Cancelable: Yes / Context Info: None

            containingDocument.setGotSubmission(true);

            boolean isDeferredSubmissionSecondPassReplaceAll = false;
            boolean submitDone = false;
            final long submissionStartTime = XFormsServer.logger.isDebugEnabled() ? System.currentTimeMillis() : 0;
            // Result information
            ConnectionResult connectionResult = null;

            // Make sure submission element info is extracted
            extractSubmissionElement();

            try {
                final boolean isReplaceAll = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL);
                final boolean isReplaceInstance = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_INSTANCE);

                final boolean isHandlingOptimizedGet = XFormsUtils.isOptimizeGetAllSubmission() && XFormsSubmissionUtils.isGet(method)
                        && isReplaceAll
                        && avtXXFormsUsername == null; // can't optimize if there are authentication credentials

                final XFormsControls xformsControls = containingDocument.getXFormsControls();

                // Get current node for xforms:submission
                final NodeInfo currentNodeInfo;
                {
                    // TODO FIXME: the submission element is not a control, so we shouldn't use XFormsControls.
                    // "The default value is '/'."
                    final String refAttribute = (submissionElement.attributeValue("ref") != null)
                            ? submissionElement.attributeValue("ref") : "/";
                    xformsControls.resetBindingContext();
                    xformsControls.pushBinding(pipelineContext, refAttribute, null, null, model.getEffectiveId(), null, submissionElement,
                            Dom4jUtils.getNamespaceContextNoDefault(submissionElement));

                    currentNodeInfo = xformsControls.getCurrentSingleNode();

                    // Check that we have a current node and that it is pointing to a document or an element
                    if (currentNodeInfo == null)
                        throw new XFormsSubmissionException("Empty single-node binding on xforms:submission for submission id: " + id, "getting submission single-node binding",
                        XFormsSubmitErrorEvent.ErrorType.NO_DATA);

                    if (!(currentNodeInfo instanceof DocumentInfo || currentNodeInfo.getNodeKind() == org.w3c.dom.Document.ELEMENT_NODE)) {
                        throw new XFormsSubmissionException("xforms:submission: single-node binding must refer to a document node or an element.", "getting submission single-node binding",
                        XFormsSubmitErrorEvent.ErrorType.NO_DATA);
                    }
                }

                // Get instance containing the node to submit
                final XFormsInstance currentInstance = xformsControls.getCurrentInstance();

                // Determine if the instance to submit has one or more bound and relevant upload controls
                boolean hasBoundRelevantUploadControl = false;
                if (serialize) {
                    final List uploadControls = xformsControls.getCurrentControlsState().getUploadControls();
                    if (uploadControls != null) {
                        for (Iterator i = uploadControls.iterator(); i.hasNext();) {
                            final XFormsUploadControl currentControl = (XFormsUploadControl) i.next();
                            if (currentControl.isRelevant()) {
                                final NodeInfo boundNodeInfo = currentControl.getBoundNode();
                                if (currentInstance ==  model.getInstanceForNode(boundNodeInfo)) {
                                    // Found one relevant bound control
                                    hasBoundRelevantUploadControl = true;
                                    break;
                                }
                            }
                        }
                    }
                }

                final boolean isDeferredSubmission = (isReplaceAll && !isHandlingOptimizedGet) || (!isReplaceAll && serialize && hasBoundRelevantUploadControl);
                final boolean isDeferredSubmissionFirstPass = isDeferredSubmission && XFormsEvents.XFORMS_SUBMIT.equals(eventName);
                final boolean isDeferredSubmissionSecondPass = isDeferredSubmission && !isDeferredSubmissionFirstPass; // here we get XXFORMS_SUBMIT
                isDeferredSubmissionSecondPassReplaceAll = isDeferredSubmissionSecondPass && isReplaceAll;

                // "The data model is updated"
                final XFormsModel modelForInstance = currentInstance.getModel(containingDocument);
                {
                    // NOTE: As of 2007-07-30, the spec says this should happen regardless of whether we serialize or not.
                    final XFormsModel.DeferredActionContext deferredActionContext = modelForInstance.getDeferredActionContext();
                    if (deferredActionContext != null) {
                        if (deferredActionContext.rebuild) {
                            modelForInstance.doRebuild(pipelineContext);
                        }
                        if (deferredActionContext.recalculate) {
                            modelForInstance.doRecalculate(pipelineContext);
                        }
                    }
                }

                final Document initialDocumentToSubmit;
                if (serialize && !isDeferredSubmissionSecondPass) {
                    // Create document to submit
                    try {
                        initialDocumentToSubmit = createDocumentToSubmit(currentNodeInfo, currentInstance);

                        // Temporarily change instance document so that we can run validation again
                        modelForInstance.setInstanceDocument(initialDocumentToSubmit,
                                currentInstance.getModelId(), currentInstance.getEffectiveId(), currentInstance.getSourceURI(),
                                currentInstance.getUsername(), currentInstance.getPassword(),
                                currentInstance.isApplicationShared(),
                                currentInstance.getTimeToLive(),
                                currentInstance.getValidation());

                        // Revalidate instance
                        modelForInstance.doRevalidate(pipelineContext);
                        // TODO: Check if the validation state can really change. If so, find a solution.
                        // "no notification events are marked for dispatching due to this operation"

                        // Check that there are no validation errors
                        // NOTE: If the instance is read-only, it can't have MIPs, and can't fail validation/requiredness, so we don't go through the process at all.
                        final boolean instanceSatisfiesValidRequired = currentInstance.isReadOnly() || isDocumentSatisfiesValidRequired(initialDocumentToSubmit);
                        if (!instanceSatisfiesValidRequired) {
                            if (logger.isDebugEnabled()) {
                                final LocationDocumentResult documentResult = new LocationDocumentResult();
                                final TransformerHandler identity = TransformerUtils.getIdentityTransformerHandler();
                                identity.setResult(documentResult);
                                currentInstance.read(identity);
                                final String documentString = Dom4jUtils.domToString(documentResult.getDocument());

                                logger.debug("XForms - submission - instance document or subset thereof cannot be submitted:\n" + documentString);
                            }
                            throw new XFormsSubmissionException("xforms:submission: instance to submit does not satisfy valid and/or required model item properties.",
                                    "checking instance validity", XFormsSubmitErrorEvent.ErrorType.VALIDATION_ERROR);
                        }
                    } finally {
                        // Restore instance document
                        modelForInstance.setInstance(currentInstance, false);
                    }
                } else {
                    initialDocumentToSubmit = null;
                }

                // Deferred submission: end of the first pass
                if (isDeferredSubmissionFirstPass) {

                    // Resolve the target AVT if needed
                    final FunctionLibrary functionLibrary = XFormsContainingDocument.getFunctionLibrary();
                    resolvedXXFormsTarget = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsTarget);

                    // When replace="all", we wait for the submission of an XXFormsSubmissionEvent from the client
                    containingDocument.setClientActiveSubmission(this);
                    return;
                }

                // Evaluate AVTs
                // TODO FIXME: the submission element is not a control, so we shouldn't use XFormsControls.
                {
                    final FunctionLibrary functionLibrary = XFormsContainingDocument.getFunctionLibrary();

                    final String tempActionOrResource = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtActionOrResource);
                    resolvedActionOrResource = XFormsUtils.encodeHRRI(tempActionOrResource, true);

                    resolvedXXFormsUsername = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsUsername);
                    resolvedXXFormsPassword = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsPassword);
                    resolvedXXFormsReadonly = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsReadonly);
                    resolvedXXFormsShared = XFormsUtils.resolveAttributeValueTemplates(pipelineContext, currentNodeInfo, null, functionLibrary, xformsControls, submissionElement, avtXXFormsShared);
                }

                // Check read-only and shared hints
                XFormsInstance.checkSharedHints(submissionElement, resolvedXXFormsReadonly, resolvedXXFormsShared);
                final boolean isReadonlyHint = "true".equals(resolvedXXFormsReadonly);
                final boolean isApplicationSharedHint = "application".equals(resolvedXXFormsShared);
                final long timeToLive = XFormsInstance.getTimeToLive(submissionElement);
                if (isApplicationSharedHint) {
                    if (!XFormsSubmissionUtils.isGet(method))
                        throw new XFormsSubmissionException("xforms:submission: xxforms:shared=\"application\" can be set only with method=\"get\".",
                                "checking read-only and shared hints");
                    if (!isReplaceInstance)
                        throw new XFormsSubmissionException("xforms:submission: xxforms:shared=\"application\" can be set only with replace=\"instance\".",
                                "checking read-only and shared hints");
                } else if (isReadonlyHint) {
                    if (!isReplaceInstance)
                        throw new XFormsSubmissionException("xforms:submission: xxforms:readonly=\"true\" can be \"true\" only with replace=\"instance\".",
                                "checking read-only and shared hints");
                }

                final Document documentToSubmit;
                if (serialize && isDeferredSubmissionSecondPass) {
                    // Handle uploaded files if any
                    final Element filesElement = (event instanceof XXFormsSubmitEvent) ? ((XXFormsSubmitEvent) event).getFilesElement() : null;
                    if (filesElement != null) {
                        for (Iterator i = filesElement.elements().iterator(); i.hasNext();) {
                            final Element parameterElement = (Element) i.next();
                            final String name = parameterElement.element("name").getTextTrim();

                            final XFormsUploadControl uploadControl
                                        = (XFormsUploadControl) containingDocument.getObjectById(pipelineContext, name);

                            // In case of xforms:repeat, the name of the template will not match an existing control
                            if (uploadControl == null)
                                continue;

                            final Element valueElement = parameterElement.element("value");
                            final String value = valueElement.getTextTrim();

                            final String filename;
                            {
                                final Element filenameElement = parameterElement.element("filename");
                                filename = (filenameElement != null) ? filenameElement.getTextTrim() : "";
                            }
                            final String mediatype;
                            {
                                final Element mediatypeElement = parameterElement.element("content-type");
                                mediatype = (mediatypeElement != null) ? mediatypeElement.getTextTrim() : "";
                            }
                            final String size = parameterElement.element("content-length").getTextTrim();

                            if (size.equals("0") && filename.equals("")) {
                                // No file was selected in the UI
                            } else {
                                // A file was selected in the UI (note that the file may be empty)
                                final String paramValueType = Dom4jUtils.qNameToexplodedQName(Dom4jUtils.extractAttributeValueQName(valueElement, XMLConstants.XSI_TYPE_QNAME));

                                // Set value of uploaded file into the instance (will be xs:anyURI or xs:base64Binary)
                                uploadControl.setExternalValue(pipelineContext, value, paramValueType, !isReplaceAll);

                                // Handle filename, mediatype and size if necessary
                                uploadControl.setFilename(pipelineContext, filename);
                                uploadControl.setMediatype(pipelineContext, mediatype);
                                uploadControl.setSize(pipelineContext, size);
                            }
                        }
                    }

                    // Create document to submit
                    try {
                        documentToSubmit = createDocumentToSubmit(currentNodeInfo, currentInstance);

                        // Temporarily change instance document so that we can run validation again
                        modelForInstance.setInstanceDocument(documentToSubmit,
                                currentInstance.getModelId(), currentInstance.getEffectiveId(), currentInstance.getSourceURI(),
                                currentInstance.getUsername(), currentInstance.getPassword(),
                                currentInstance.isApplicationShared(),
                                currentInstance.getTimeToLive(),
                                currentInstance.getValidation());

                        // Revalidate instance
                        modelForInstance.doRevalidate(pipelineContext);
                        // sent out. Check if the validation state can really change. If so, find a
                        // solution.
                        // "no notification events are marked for dispatching due to this operation"

                        // Check that there are no validation errors
                        // NOTE: If the instance is read-only, it can't have MIPs, and can't fail validation/requiredness, so we don't go through the process at all.
                        final boolean instanceSatisfiesValidRequired = currentInstance.isReadOnly() || isDocumentSatisfiesValidRequired(documentToSubmit);
                        if (!instanceSatisfiesValidRequired) {
                            if (logger.isDebugEnabled()) {
                                final LocationDocumentResult documentResult = new LocationDocumentResult();
                                final TransformerHandler identity = TransformerUtils.getIdentityTransformerHandler();
                                identity.setResult(documentResult);
                                currentInstance.read(identity);
                                final String documentString = Dom4jUtils.domToString(documentResult.getDocument());

                                logger.debug("XForms - submission - instance document or subset thereof cannot be submitted:\n" + documentString);
                            }
                            throw new XFormsSubmissionException("xforms:submission: instance to submit does not satisfy valid and/or required model item properties.",
                                    "checking instance validity", XFormsSubmitErrorEvent.ErrorType.VALIDATION_ERROR);
                        }
                    } finally {
                        // Restore instance document
                        modelForInstance.setInstance(currentInstance, false);
                    }
                } else {
                    // Don't recreate document
                    documentToSubmit = initialDocumentToSubmit;
                }

                if (serialize && !isDeferredSubmissionSecondPassReplaceAll) { // we don't want any changes to happen to the document upon xxforms-submit when producing a new document
                    // Fire xforms-submit-serialize

                    // "The event xforms-submit-serialize is dispatched. If the submission-body property of the event
                    // is changed from the initial value of empty string, then the content of the submission-body
                    // property string is used as the submission serialization. Otherwise, the submission serialization
                    // consists of a serialization of the selected instance data according to the rules stated at 11.9
                    // Submission Options."

                    // TODO: pass submission-body attribute
                    containingDocument.dispatchEvent(pipelineContext, new XFormsSubmitSerializeEvent(XFormsModelSubmission.this));
                    // TODO: look at result of submission-body attribute and see if submission body needs to be replaced
                }

                // Serialize
                // To support: application/xml, application/x-www-form-urlencoded, multipart/related, multipart/form-data
                final byte[] messageBody;
                final String queryString;
                final String defaultMediatype;
                {
                    if (method.equals("multipart-post")) {
                        // TODO
                        throw new XFormsSubmissionException("xforms:submission: submission method not yet implemented: " + method, "serializing instance");
                    } else if (method.equals("form-data-post")) {
                        // TODO

//                        final MultipartFormDataBuilder builder = new MultipartFormDataBuilder(, , null);

                        throw new XFormsSubmissionException("xforms:submission: submission method not yet implemented: " + method, "serializing instance");
                    } else if (method.equals("urlencoded-post")) {

                        // Perform "application/x-www-form-urlencoded" serialization
                        queryString = null;
                        messageBody = serialize? createWwwFormUrlEncoded(documentToSubmit).getBytes("UTF-8") : null;// the resulting string is already ASCII
                        defaultMediatype = "application/x-www-form-urlencoded";

                    } else if (XFormsSubmissionUtils.isPost(method) || XFormsSubmissionUtils.isPut(method)) {

                        if (serialize) {
                            // Serialize XML to a stream of bytes
                            try {
                                final Transformer identity = TransformerUtils.getIdentityTransformer();
                                TransformerUtils.applyOutputProperties(identity,
                                        "xml", version, null, null, encoding, omitxmldeclaration, standalone, indent, 4);

                                // TODO: use cdata-section-elements

                                final ByteArrayOutputStream os = new ByteArrayOutputStream();
                                identity.transform(new DocumentSource(documentToSubmit), new StreamResult(os));
                                messageBody = os.toByteArray();
                            } catch (Exception e) {
                                throw new XFormsSubmissionException(e, "xforms:submission: exception while serializing instance to XML.", "serializing instance");
                            }
                        } else {
                            messageBody = null;
                        }
                        queryString = null;
                        defaultMediatype = "application/xml";

                    } else if (XFormsSubmissionUtils.isGet(method) || XFormsSubmissionUtils.isDelete(method)) {

                        // Perform "application/x-www-form-urlencoded" serialization
                        queryString = serialize ? createWwwFormUrlEncoded(documentToSubmit) : null;
                        messageBody = null;
                        defaultMediatype = null;

                    } else {
                        throw new XFormsSubmissionException("xforms:submission: invalid submission method requested: " + method, "serializing instance");
                    }
                }

                final ExternalContext externalContext = (ExternalContext) pipelineContext.getAttribute(PipelineContext.EXTERNAL_CONTEXT);

                // Get URL type
                final String urlType = submissionElement.attributeValue(XMLConstants.FORMATTING_URL_TYPE_QNAME);
                final ExternalContext.Request request = externalContext.getRequest();

                // Find instance to update
                final XFormsInstance replaceInstance;
                if (isReplaceInstance) {
                    if (xxfReplaceInstanceId != null)
                        replaceInstance = containingDocument.findInstance(xxfReplaceInstanceId);
                    else if (replaceInstanceId != null)
                        replaceInstance = model.getInstance(replaceInstanceId);
                    else
                        replaceInstance = currentInstance;
                } else {
                    replaceInstance = null;
                }

                final long externalSubmissionStartTime = XFormsServer.logger.isDebugEnabled() ? System.currentTimeMillis() : 0;
                try {
                    if (isReplaceInstance && resolvedActionOrResource.startsWith("test:")) {
                        // Test action

                        if (messageBody == null)
                            throw new XFormsSubmissionException("Action 'test:': no message body.", "processing submission response");

                        connectionResult = new ConnectionResult(null);
                        connectionResult.resultCode = 200;
                        connectionResult.resultHeaders = new HashMap();
                        connectionResult.lastModified = 0;
                        connectionResult.resultMediaType = "application/xml";
                        connectionResult.dontHandleResponse = false;
                        connectionResult.setResultInputStream(new ByteArrayInputStream(messageBody));

                    } else if (isHandlingOptimizedGet) {
                        // GET with replace="all": we can optimize and tell the client to just load the URL

                        final String actionString = (queryString == null) ? resolvedActionOrResource : resolvedActionOrResource + ((resolvedActionOrResource.indexOf('?') == -1) ? "?" : "") + queryString;
                        final String resultURL = XFormsLoadAction.resolveLoadValue(containingDocument, pipelineContext, submissionElement, true, actionString, null, null, fURLNorewrite, xxfShowProgress);
                        connectionResult = new ConnectionResult(resultURL);
                        connectionResult.dontHandleResponse = true;

                    } else if (!NetUtils.urlHasProtocol(resolvedActionOrResource)
                               && !fURLNorewrite
                               && ((request.getContainerType().equals("portlet") && !"resource".equals(urlType))
                                    || (request.getContainerType().equals("servlet")
                                        && (XFormsUtils.isOptimizeLocalSubmission() || isMethodOptimizedLocalSubmission())
                                        &&  isReplaceAll))) {

                        // This is an "optimized" submission, i.e. one that does not use an actual
                        // protocol handler to access the resource

                        // NOTE: Optimizing with include() for servlets doesn't allow detecting
                        // errors caused by the included resource, so we don't allow this for now.

                        // NOTE: For portlets, paths are served directly by the portlet, NOT as
                        // resources.

                        // Current limitations:
                        // o Portlets cannot access resources outside the portlet except by using absolute URLs (unless f:url-type="resource")
                        // o Servlets cannot access resources on the same server but not in the current application
                        //   except by using absolute URLs

                        final URI resolvedURI = XFormsUtils.resolveXMLBase(submissionElement, resolvedActionOrResource);

                        // NOTE: We don't want any changes to happen to the document upon xxforms-submit when producing
                        // a new document so we don't dispatch xforms-submit-done and pass a null XFormsModelSubmission
                        // in that case
                        connectionResult = XFormsSubmissionUtils.doOptimized(pipelineContext, externalContext,
                                isDeferredSubmissionSecondPassReplaceAll ? null : this, method, resolvedURI.toString(), (mediatype == null) ? defaultMediatype : mediatype, isReplaceAll,
                                messageBody, queryString);

                    } else {
                        // This is a regular remote submission going through a protocol handler

                        // Absolute URLs or absolute paths are allowed to a local servlet
                        String resolvedURL;

                        if (NetUtils.urlHasProtocol(resolvedActionOrResource) || fURLNorewrite) {
                            // Don't touch the URL if it is absolute or if f:url-norewrite="true"
                            resolvedURL = resolvedActionOrResource;
                        } else {
                            // Rewrite URL
                            resolvedURL= XFormsUtils.resolveResourceURL(pipelineContext, submissionElement, resolvedActionOrResource);

                            if (request.getContainerType().equals("portlet") && "resource".equals(urlType) && !NetUtils.urlHasProtocol(resolvedURL)) {
                                // In this case, we have to prepend the complete server path
                                resolvedURL = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() > 0 ? ":" + request.getServerPort() : "") + resolvedURL;
                            }
                        }

                        if (isApplicationSharedHint) {
                            // Get the instance from shared instance cache
                            // This can only happen is method="get" and replace="instance" and xxforms:readonly="true" and xxforms:shared="application"

                            if (XFormsServer.logger.isDebugEnabled())
                                XFormsServer.logger.debug("XForms - submission - using instance from application shared instance cache: " + replaceInstance.getEffectiveId());

                            final URL absoluteResolvedURL = XFormsSubmissionUtils.createAbsoluteURL(resolvedURL, queryString, externalContext);
                            final String absoluteResolvedURLString = absoluteResolvedURL.toExternalForm();

                            final SharedXFormsInstance sharedInstance
                                    = XFormsServerSharedInstancesCache.instance().find(pipelineContext, replaceInstance.getEffectiveId(), replaceInstance.getModelId(), absoluteResolvedURLString, timeToLive, replaceInstance.getValidation());

                            if (XFormsServer.logger.isDebugEnabled())
                                XFormsServer.logger.debug("XForms - submission - replacing instance with read-only instance: " + sharedInstance.getEffectiveId());

                            // Handle new instance and associated events
                            final XFormsModel replaceModel = sharedInstance.getModel(containingDocument);
                            replaceModel.handleNewInstanceDocuments(pipelineContext, sharedInstance);

                            connectionResult = null;
                            submitDone = true;
                        } else {
                            // Perform actual submission
                            connectionResult = XFormsSubmissionUtils.doRegular(externalContext,
                                    method, resolvedURL, resolvedXXFormsUsername, resolvedXXFormsPassword, (mediatype == null) ? defaultMediatype : mediatype,
                                    messageBody, queryString);
                        }
                    }

                    if (connectionResult != null && !connectionResult.dontHandleResponse) {
                        // Handle response
                        if (connectionResult.resultCode >= 200 && connectionResult.resultCode < 300) {// accept any success code (in particular "201 Resource Created")
                            // Sucessful response
                            if (connectionResult.hasContent()) {
                                // There is a body

                                if (isReplaceAll) {
                                    // When we get here, we are in a mode where we need to send the reply
                                    // directly to an external context, if any.

                                    // "the event xforms-submit-done is dispatched"
                                    if (!isDeferredSubmissionSecondPassReplaceAll) // we don't want any changes to happen to the document upon xxforms-submit when producing a new document
                                        containingDocument.dispatchEvent(pipelineContext, new XFormsSubmitDoneEvent(XFormsModelSubmission.this));

                                    final ExternalContext.Response response = externalContext.getResponse();

                                    // Forward headers to response
                                    connectionResult.forwardHeaders(response);

                                    // Forward content to response
                                    NetUtils.copyStream(connectionResult.getResultInputStream(), response.getOutputStream());

                                    // TODO: [#306918] RFE: Must be able to do replace="all" during initialization.
                                    // http://forge.objectweb.org/tracker/index.php?func=detail&aid=306918&group_id=168&atid=350207
                                    // Suggestion is to write either binary or XML to processor output ContentHandler,
                                    // and make sure the code which would output the XHTML+XForms is disabled.

                                } else if (isReplaceInstance) {

                                    if (ProcessorUtils.isXMLContentType(connectionResult.resultMediaType)) {
                                        // Handling of XML media type
                                        try {
                                            // Set new instance document to replace the one submitted

                                            if (replaceInstance == null) {
                                                // Replacement instance was specified but not found
                                                // TODO: XForms 1.1 won't dispatch xforms-binding-exception here, but instead
                                            // will dispatch xforms-submit-error with error-type of target-error.
                                                containingDocument.dispatchEvent(pipelineContext, new XFormsBindingExceptionEvent(XFormsModelSubmission.this));
                                            } else {

                                                // Read stream into Document
                                                final XFormsInstance newInstance;
                                                if (!isReadonlyHint) {
                                                    // Resulting instance is not read-only

                                                    if (XFormsServer.logger.isDebugEnabled())
                                                        XFormsServer.logger.debug("XForms - submission - replacing instance with mutable instance: " + replaceInstance.getEffectiveId());

                                                    // TODO: Sure what default we want. In most cases, we don't use DTDs. Will XML Schema validate as well?
                                                    final boolean validating = false;

                                                    // We don't want XInclude automatically handled for security reasons
                                                    final boolean handleXInclude = false;

                                                    // TODO: Use TransformerUtils.readDom4j() instead?

                                                    final Document resultingInstanceDocument = Dom4jUtils.readDom4j(connectionResult.getResultInputStream(), connectionResult.resourceURI, validating, handleXInclude);
                                                    newInstance = new XFormsInstance(replaceInstance.getModelId(), replaceInstance.getEffectiveId(), resultingInstanceDocument,
                                                            connectionResult.resourceURI, resolvedXXFormsUsername, resolvedXXFormsPassword, false, -1, replaceInstance.getValidation());
                                                } else {
                                                    // Resulting instance is read-only

                                                    if (XFormsServer.logger.isDebugEnabled())
                                                        XFormsServer.logger.debug("XForms - submission - replacing instance with read-only instance: " + replaceInstance.getEffectiveId());

                                                    // TODO: Handle validating and handleXInclude!

                                                    // NOTE: isApplicationSharedHint is always false when get get here. isApplicationSharedHint="true" is handled above.
                                                    final DocumentInfo resultingInstanceDocument = TransformerUtils.readTinyTree(connectionResult.getResultInputStream(), connectionResult.resourceURI);
                                                    newInstance = new SharedXFormsInstance(replaceInstance.getModelId(), replaceInstance.getEffectiveId(), resultingInstanceDocument,
                                                            connectionResult.resourceURI, resolvedXXFormsUsername, resolvedXXFormsPassword, false, -1, replaceInstance.getValidation());
                                                }

                                                // Handle new instance and associated events
                                                final XFormsModel replaceModel = newInstance.getModel(containingDocument);
                                                replaceModel.handleNewInstanceDocuments(pipelineContext, newInstance);

                                                // Notify that submission is done
                                                submitDone = true;
                                            }
                                        } catch (Exception e) {
                                            throw new XFormsSubmissionException(e, "xforms:submission: exception while serializing XML to instance.", "processing instance replacement",
                                            XFormsSubmitErrorEvent.ErrorType.PARSE_ERROR);
                                        }
                                    } else {
                                        // Other media type
                                        throw new XFormsSubmissionException("Body received with non-XML media type for replace=\"instance\": " + connectionResult.resultMediaType, "processing instance replacement",
                                        XFormsSubmitErrorEvent.ErrorType.RESOURCE_ERROR);
                                    }
                                } else if (replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_NONE)) {
                                    // Just notify that processing is terminated
                                    submitDone = true;
                                } else {
                                    throw new XFormsSubmissionException("xforms:submission: invalid replace attribute: " + replace, "processing instance replacement",
                                    XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS);
                                }

                            } else {
                                // There is no body, notify that processing is terminated
                                submitDone = true;
                            }
                        } else if (connectionResult.resultCode == 302 || connectionResult.resultCode == 301) {
                            // Got a redirect

                            final ExternalContext.Response response = externalContext.getResponse();

                            // Forward headers to response
                            connectionResult.forwardHeaders(response);

                            // Forward redirect
                            response.setStatus(connectionResult.resultCode);

                        } else {
                            // Error code received
//                            submitErrorEvent = createErrorEvent(connectionResult, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS);
                            throw new XFormsSubmissionException("xforms:submission for submission id: " + id + ", error code received when submitting instance: " + connectionResult.resultCode, "processing submission response",
                            XFormsSubmitErrorEvent.ErrorType.RESOURCE_ERROR);
                        }
                    }
                } finally {
                    // Clean-up
                    // Log time spent in submission if needed
                    if (XFormsServer.logger.isDebugEnabled()) {
                        final long submissionTime = System.currentTimeMillis() - externalSubmissionStartTime;
                        XFormsServer.logger.debug("XForms - submission - external submission time (including handling returned body): " + submissionTime);
                    }
                }
            } catch (Throwable e) {
                if (isDeferredSubmissionSecondPassReplaceAll && XFormsUtils.isOptimizePostAllSubmission()) {
                    // It doesn't serve any purpose here to dispatch an event, so we just propagate the exception
                    throw new XFormsSubmissionException(e, "Error while processing xforms:submission", "processing submission");
                } else {
                // Any exception will cause an error event to be dispatched
                final XFormsSubmitErrorEvent submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this, resolvedActionOrResource,
                e instanceof XFormsSubmissionException ? ((XFormsSubmissionException) e).getErrorType() : XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS,
                connectionResult);
                    submitErrorEvent.setThrowable(e);
                    containingDocument.dispatchEvent(pipelineContext, submitErrorEvent);
                }
            } finally {
                if (connectionResult != null) {
                    connectionResult.close();
                }
           
                // If submission succeeded, dispatch success event
                if (submitDone && !isDeferredSubmissionSecondPassReplaceAll) { // we don't want any changes to happen to the document upon xxforms-submit when producing a new document
                    containingDocument.dispatchEvent(pipelineContext, new XFormsSubmitDoneEvent(XFormsModelSubmission.this));
                }
                // Log total time spent in submission if needed
                if (XFormsServer.logger.isDebugEnabled()) {
                    final long submissionTime = System.currentTimeMillis() - submissionStartTime;
                    XFormsServer.logger.debug("XForms - submission - total submission time: " + Long.toString(submissionTime));
                }
            }

        } else if (XFormsEvents.XFORMS_BINDING_EXCEPTION.equals(eventName)) {
            // The default action for this event results in the following: Fatal error.
            throw new OXFException("Binding exception.");// TODO: what location information should we provide here?
        }
    }

    private Document createDocumentToSubmit(final NodeInfo currentNodeInfo, final XFormsInstance currentInstance) {

        final Document documentToSubmit;
        if (currentNodeInfo instanceof NodeWrapper) {
            final Node currentNode = (Node) ((NodeWrapper) currentNodeInfo).getUnderlyingNode();

            // "A node from the instance data is selected, based on attributes on the submission
            // element. The indicated node and all nodes for which it is an ancestor are considered for
            // the remainder of the submit process. "
            if (currentNode instanceof Element) {
                // Create subset of document
                documentToSubmit = Dom4jUtils.createDocument((Element) currentNode);
            } else {
                // Use entire instance document
                documentToSubmit = Dom4jUtils.createDocument(currentInstance.getDocument().getRootElement());
            }

            if (relevant) {
                // "Any node which is considered not relevant as defined in 6.1.4 is removed."
                final Node[] nodeToDetach = new Node[1];
                do {
                    // NOTE: This is not very efficient, but at least we avoid NPEs that we would get by
                    // detaching elements within accept(). Should implement a more efficient algorithm to
                    // prune non-relevant nodes.
                    nodeToDetach[0] = null;
                    documentToSubmit.accept(new VisitorSupport() {

                        public final void visit(Element element) {
                            checkInstanceData(element);
                        }

                        public final void visit(Attribute attribute) {
                            checkInstanceData(attribute);
                        }

                        private final void checkInstanceData(Node node) {
                            if (nodeToDetach[0] == null) {
                                // Check "relevant" MIP and remove non-relevant nodes
                                    if (!InstanceData.getInheritedRelevant(node))
                                        nodeToDetach[0] = node;
                            }
                        }
                    });
                    if (nodeToDetach[0] != null)
                        nodeToDetach[0].detach();

                } while (nodeToDetach[0] != null);
            }

            // TODO: handle includenamespaceprefixes
        } else {
            // Submitting read-only instance backed by TinyTree (no MIPs to check)
            if (currentNodeInfo.getNodeKind() == org.w3c.dom.Document.ELEMENT_NODE) {
                documentToSubmit = TransformerUtils.tinyTreeToDom4j2(currentNodeInfo);
            } else {
                documentToSubmit = TransformerUtils.tinyTreeToDom4j2(currentNodeInfo.getRoot());
            }
        }
        return documentToSubmit;
    }

    private String createWwwFormUrlEncoded(final Document document) {

        final StringBuffer sb = new StringBuffer();
        document.accept(new VisitorSupport() {
            public final void visit(Element element) {
                // We only care about elements

                final List children = element.elements();
                if (children == null || children.size() == 0) {
                    // Only consider leaves
                    final String text = element.getText();
                    if (text != null && text.length() > 0) {
                        // Got one!
                        final String localName = element.getName();

                        if (sb.length() > 0)
                            sb.append(separator);

                        try {
                            sb.append(URLEncoder.encode(localName, "utf-8"));
                            sb.append('=');
                            sb.append(URLEncoder.encode(text, "utf-8"));
                            // TODO: check if line breaks will be correcly encoded as "%0D%0A"
                        } catch (UnsupportedEncodingException e) {
                            // Should not happen: utf-8 must be supported
                            throw new OXFException(e);
                        }
                    }
                }
            }
        });

        return sb.toString();
    }

    private boolean isDocumentSatisfiesValidRequired(final Document documentToSubmit) {
        final boolean[] instanceSatisfiesValidRequired = new boolean[]{true};
        if (validate) {
            documentToSubmit.accept(new VisitorSupport() {

                public final void visit(Element element) {
                    final boolean valid = checkInstanceData(element);

                    instanceSatisfiesValidRequired[0] &= valid;

                    if (!valid && XFormsServer.logger.isDebugEnabled()) {
                        XFormsServer.logger.debug("XForms - submission - found invalid element: " + elementToString(element));
                    }
                }

                public final void visit(Attribute attribute) {
                    final boolean valid = checkInstanceData(attribute);

                    instanceSatisfiesValidRequired[0] &= valid;

                    if (!valid && XFormsServer.logger.isDebugEnabled()) {
                        XFormsServer.logger.debug("XForms - submission - found invalid attribute: " + attributeToString(attribute)
                                + " (parent element: " + elementToString(attribute.getParent()) + ")");
                    }
                }

                private final boolean checkInstanceData(Node node) {
                    // Check "valid" MIP
                    if (!InstanceData.getValid(node)) return false;
                    // Check "required" MIP
                    {
                        final boolean isRequired = InstanceData.getRequired(node);
                        if (isRequired) {
                            final String value = XFormsInstance.getValueForNode(node);
                            if (value.length() == 0) {
                                // Required and empty
                                return false;
                            }
                        }
                    }
                    return true;
                }
            });
        }
        return instanceSatisfiesValidRequired[0];
    }

    private static String elementToString(Element element) {
        // Open start tag
        final FastStringBuffer sb = new FastStringBuffer("<");
        sb.append(element.getQualifiedName());

        // Attributes if any
        for (Iterator i = element.attributeIterator(); i.hasNext();) {
            final Attribute currentAttribute = (Attribute) i.next();

            sb.append(' ');
            sb.append(currentAttribute.getQualifiedName());
            sb.append("=\"");
            sb.append(currentAttribute.getValue());
            sb.append('\"');
        }

        // Close start tag
        sb.append('>');

        if (!element.elements().isEmpty()) {
            // Mixed content
            final Object firstChild = element.content().get(0);
            if (firstChild instanceof Text) {
                sb.append(((Text) firstChild).getText());
            }
            sb.append("[...]");
        } else {
            // Not mixed content
            sb.append(element.getText());
        }

        // Close element with end tag
        sb.append("</");
        sb.append(element.getQualifiedName());
        sb.append('>');

        return sb.toString();
    }

    private static String attributeToString(Attribute attribute) {
        final FastStringBuffer sb = new FastStringBuffer(attribute.getQualifiedName());
        sb.append("=\"");
        sb.append(attribute.getValue());
        sb.append('\"');
        return sb.toString();
    }

    public static class ConnectionResult {
        public boolean dontHandleResponse;
        public int resultCode;
        public String resultMediaType;
        public Map resultHeaders;
        public long lastModified;
        public String resourceURI;

        private InputStream resultInputStream;
        private boolean hasContent;

        public ConnectionResult(String resourceURI) {
            this.resourceURI = resourceURI;
        }

        public InputStream getResultInputStream() {
        return resultInputStream;
        }

        public boolean hasContent() {
            return hasContent;
        }

        public void setResultInputStream(final InputStream resultInputStream) throws IOException {
        this.resultInputStream = resultInputStream;
        setHasContentFlag();

        }

        private void setHasContentFlag() throws IOException {
            if (resultInputStream == null) {
                hasContent = false;
            } else {
                if (!resultInputStream.markSupported())
                    this.resultInputStream = new BufferedInputStream(resultInputStream);

                resultInputStream.mark(1);
                hasContent = resultInputStream.read() != -1;
                resultInputStream.reset();
            }
        }

        public void forwardHeaders(ExternalContext.Response response) {
            if (resultHeaders != null) {
                for (Iterator i = resultHeaders.entrySet().iterator(); i.hasNext();) {
                    final Map.Entry currentEntry = (Map.Entry) i.next();
                    final String headerName = (String) currentEntry.getKey();

                    if (headerName != null) {
                        // NOTE: As per the doc, this should always be a List, but for some unknown reason
                        // it appears to be a String sometimes
                        if (currentEntry.getValue() instanceof String) {
                            // Case of String
                            final String headerValue = (String) currentEntry.getValue();
                            forwardHeaderFilter(response, headerName, headerValue);
                        } else {
                            // Case of List
                            final List headerValues = (List) currentEntry.getValue();
                            if (headerValues != null) {
                                for (Iterator j = headerValues.iterator(); j.hasNext();) {
                                    final String headerValue = (String) j.next();
                                    if (headerValue != null) {
                                        forwardHeaderFilter(response, headerName, headerValue);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private void forwardHeaderFilter(ExternalContext.Response response, String headerName, String headerValue) {
            /**
             * Filtering the Transfer-Encoding header
             *
             * We don't pass the Transfer-Encoding header, as the request body is
             * already decoded for us. Passing along the Transfer-Encoding causes a
             * problem if the server sends us chunked data and we send it in the
             * response not chunked but saying in the header that it is chunked.
             *
             * Non-filtering of Content-Encoding header
             *
             * The Content-Encoding has the potential of causing the same problem as
             * the Transfer-Encoding header. It could be an issue if we get data with
             * Content-Encoding: gzip, but pass it along uncompressed but still
             * include the Content-Encoding: gzip. However this does not happen, as
             * the request we send does not contain a Accept-Encoding: gzip,deflate. So
             * filtering the Content-Encoding header is safe here.
             */
            if (!"transfer-encoding".equals(headerName.toLowerCase())) {
                response.addHeader(headerName, headerValue);
            }
        }

        public void close() {}
    }

    private class XFormsSubmissionException extends ValidationException {
   
    private final ErrorType errorType;
   
    public XFormsSubmissionException(final String message, final String description) {
    this(message,description, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS);
    }
   
        public XFormsSubmissionException(final String message, final String description, final XFormsSubmitErrorEvent.ErrorType errorType) {
            super(message, new ExtendedLocationData(XFormsModelSubmission.this.getLocationData(), description,
                    XFormsModelSubmission.this.getSubmissionElement()));
    this.errorType = errorType;
    }
        public XFormsSubmissionException(final Throwable e, final String message, final String description) {
        this(e,message,description, XFormsSubmitErrorEvent.ErrorType.SUBMISSION_IN_PROGRESS);
        }
        public XFormsSubmissionException(final Throwable e, final String message, final String description, final XFormsSubmitErrorEvent.ErrorType errorType) {
            super(message, e, new ExtendedLocationData(XFormsModelSubmission.this.getLocationData(), description,
                    XFormsModelSubmission.this.getSubmissionElement()));
        this.errorType = errorType;
        }
 
                public ErrorType getErrorType() {
        return errorType;
        }
     }
}

--
You receive this message as a subscriber of the [hidden email] mailing list.
To unsubscribe: mailto:[hidden email]
For general help: mailto:[hidden email]?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws
Reply | Threaded
Open this post in threaded view
|

Re: patch: support error-type attribute on xforms-submit-error event

Erik Bruchez
Administrator
Adrian,

Fantastic, we will try to integrate this ASAP.

-Erik

Adrian Baker wrote:

> Fixed version attached.
>
> Adrian Baker
> wrote:file:///E:/dev/Orbeon/Orion/work/src/java/org/orbeon/oxf/xforms/event/events/XFormsSubmitErrorEvent.java
> <imap://[hidden email]:143/fetch%3EUID%3E.INBOX.Orbeon.ops-users%3E15230?header=quotebody&part=1.1.2&filename=XFormsSubmitErrorEvent.java>
>
>> Oops - slight problem with passing the connection result around: it's
>> stream gets closed before it's read in the XFormsSubmitErrorEvent
>> constructor. Will fix and resend.
>>
>> Adrian Baker wrote:
>>> Attached is a fairly straightforward patch to XFormsModelSubmission
>>> et al to support the XForms 1.1 error-type attribute
>>> (http://www.w3.org/TR/xforms11/#submit-evt-submit).
>>>
>>> This event is particularly useful for distinguishing between
>>> submission failures caused by invalid data (user error) or a failure
>>> of the submission target (system error). Previously I had been using
>>> the xforms-submit-serialize to set a flag to identify the two cases,
>>> but came across a case where this was broken by @serialize="false" on
>>> the <xf:submission>, and rather than update my rather fiddly
>>> workaround it was quicker to implement the proper solution.
>>>
>>> Previously the XFormsSubmitError event was being created throughout
>>> the body of the performDefaultAction() method (always just before an
>>> exception is thrown), as well as in a catch block in the bottom. I
>>> felt a bit uneasy about spreading the creation around like this, and
>>> changed this so the event is created solely in the catch block at the
>>> bottom, since there is always an exception thrown if the submission
>>> fails.
>>>
>>> Adrian
>>>
>>> ______________________________________________________________________
>>> This email has been scanned by the MessageLabs Email Security System.
>>> For more information please visit http://www.messagelabs.com/email
>>> ______________________________________________________________________
>>
>> ______________________________________________________________________
>> This email has been scanned by the MessageLabs Email Security System.
>> For more information please visit http://www.messagelabs.com/email
>> ______________________________________________________________________
>

--
Orbeon Forms - Web Forms for the Enterprise Done the Right Way
http://www.orbeon.com/


--
You receive this message as a subscriber of the [hidden email] mailing list.
To unsubscribe: mailto:[hidden email]
For general help: mailto:[hidden email]?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws
Reply | Threaded
Open this post in threaded view
|

Re: patch: support error-type attribute on xforms-submit-error event

Adrian Baker-3
What's the status of this? Is there a tracker item?

Erik Bruchez wrote
Adrian,

Fantastic, we will try to integrate this ASAP.

-Erik

Adrian Baker wrote:
> Fixed version attached.
>
> Adrian Baker
> wrote:file:///E:/dev/Orbeon/Orion/work/src/java/org/orbeon/oxf/xforms/event/events/XFormsSubmitErrorEvent.java
> <imap://ebruchez@mail.scdi.org:143/fetch%3EUID%3E.INBOX.Orbeon.ops-users%3E15230?header=quotebody&part=1.1.2&filename=XFormsSubmitErrorEvent.java> 
>
>> Oops - slight problem with passing the connection result around: it's
>> stream gets closed before it's read in the XFormsSubmitErrorEvent
>> constructor. Will fix and resend.
>>
>> Adrian Baker wrote:
>>> Attached is a fairly straightforward patch to XFormsModelSubmission
>>> et al to support the XForms 1.1 error-type attribute
>>> (http://www.w3.org/TR/xforms11/#submit-evt-submit).
>>>
>>> This event is particularly useful for distinguishing between
>>> submission failures caused by invalid data (user error) or a failure
>>> of the submission target (system error). Previously I had been using
>>> the xforms-submit-serialize to set a flag to identify the two cases,
>>> but came across a case where this was broken by @serialize="false" on
>>> the <xf:submission>, and rather than update my rather fiddly
>>> workaround it was quicker to implement the proper solution.
>>>
>>> Previously the XFormsSubmitError event was being created throughout
>>> the body of the performDefaultAction() method (always just before an
>>> exception is thrown), as well as in a catch block in the bottom. I
>>> felt a bit uneasy about spreading the creation around like this, and
>>> changed this so the event is created solely in the catch block at the
>>> bottom, since there is always an exception thrown if the submission
>>> fails.
>>>
>>> Adrian
>>>
>>> ______________________________________________________________________
>>> This email has been scanned by the MessageLabs Email Security System.
>>> For more information please visit http://www.messagelabs.com/email
>>> ______________________________________________________________________
>>
>> ______________________________________________________________________
>> This email has been scanned by the MessageLabs Email Security System.
>> For more information please visit http://www.messagelabs.com/email
>> ______________________________________________________________________
>


--
Orbeon Forms - Web Forms for the Enterprise Done the Right Way
http://www.orbeon.com/


--
You receive this message as a subscriber of the ops-users@ow2.org mailing list.
To unsubscribe: mailto:ops-users-unsubscribe@ow2.org
For general help: mailto:sympa@ow2.org?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws