<commit_before>/**
 * Copyright (C) 2010 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.submission;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.log4j.Logger;
import org.dom4j.*;
import org.dom4j.io.DocumentSource;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.util.*;
import org.orbeon.oxf.xforms.*;
import org.orbeon.oxf.xforms.analysis.model.Instance;
import org.orbeon.oxf.xforms.event.*;
import org.orbeon.oxf.xforms.event.events.*;
import org.orbeon.oxf.xforms.function.XFormsFunction;
import org.orbeon.oxf.xforms.xbl.Scope;
import org.orbeon.oxf.xforms.xbl.XBLContainer;
import org.orbeon.oxf.xml.NamespaceMapping;
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.saxon.functions.FunctionLibrary;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.Item;
import org.orbeon.saxon.om.NodeInfo;
import org.orbeon.saxon.om.VirtualNode;

import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;

/**
 * Represents an XForms model submission instance.
 *
 * TODO: Refactor handling of serialization to separate classes.
 */
public class XFormsModelSubmission extends XFormsModelSubmissionBase implements XFormsEventTarget, XFormsEventObserver {

    public static final String LOGGING_CATEGORY = "submission";
	public final static Logger logger = LoggerFactory.createLogger(XFormsModelSubmission.class);

    private final org.orbeon.oxf.xforms.analysis.model.Submission staticSubmission;
    private final String id;
    private final Element submissionElement;
    
    private final XBLContainer container;
    private final XFormsContainingDocument containingDocument;

    private final XFormsModel model;
    private boolean submissionElementExtracted = false;

    private String avtActionOrResource; // required unless there is a nested xf:resource element;
    private String avtMethod; // required

    private String avtValidate;
    private String avtRelevant;
    private String avtXXFormsCalculate;
    private String avtXXFormsUploads;
    private String avtXXFormsAnnotate;

    private String avtSerialization;

    private String targetref;// this is an XPath expression when used with replace="instance|text" (other meaning possible post-XForms 1.1 for replace="all")
    private String avtMode;

    private String avtVersion;
    private String avtEncoding;
    private String avtMediatype;
    private String avtIndent;
    private String avtOmitxmldeclaration;
    private String avtStandalone;
//    private String cdatasectionelements;

    private String replace = XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL;
    private String replaceInstanceId;
    private String xxfReplaceInstanceId;
    private String avtSeparator = "&";// XForms 1.1 changes back the default to the ampersand as of February 2009
//    private String includenamespaceprefixes;

    private String avtXXFormsUsername;
    private String avtXXFormsPassword;
    private String avtXXFormsPreemptiveAuthentication;
    private String avtXXFormsDomain;
    private String avtXXFormsReadonly;
    private String avtXXFormsShared;
    private String avtXXFormsCache;
    private String avtXXFormsTarget;
    private String resolvedXXFormsTarget;
    private String avtXXFormsHandleXInclude;

    private boolean xxfShowProgress;

    private boolean fURLNorewrite;
    private String urlType;

    // All the submission types in the order they must be checked
    private final Submission[] submissions;

    public XFormsModelSubmission(XBLContainer container, org.orbeon.oxf.xforms.analysis.model.Submission staticSubmission, XFormsModel model) {
        this.staticSubmission = staticSubmission;

        this.id = staticSubmission.staticId();
        this.submissionElement = staticSubmission.element();

        this.container = container;
        this.containingDocument = container.getContainingDocument();

        this.model = model;

        this.submissions = new Submission[] {
            new EchoSubmission(this),
            new ClientGetAllSubmission(this),
            new FilterPortletSubmission(this),
            new CacheableSubmission(this),
            new LocalPortletSubmission(this),
            new RequestDispatcherSubmission(this),
            new RegularSubmission(this)
        };
    }

    public XFormsContainingDocument containingDocument() {
        return containingDocument;
    }

    public Element getSubmissionElement() {
        return submissionElement;
    }


    public boolean isShowProgress() {
        return xxfShowProgress;
    }

    public boolean isURLNorewrite() {
        return fURLNorewrite;
    }

    public String getUrlType() {
        return urlType;
    }

    public String getReplace() {
        return replace;
    }

    public String getTargetref() {
        return targetref;
    }

    public String getResolvedXXFormsTarget() {
        return resolvedXXFormsTarget;
    }

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

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

            avtMethod = submissionElement.attributeValue("method");
            if (avtMethod == null) {
                // TODO: support XForms 1.1 nested xf:method element
                throw new XFormsSubmissionException(this, "xf:submission: method attribute is missing.",
                        "processing xf:submission attributes");
            }
            avtValidate = submissionElement.attributeValue("validate");
            avtRelevant = submissionElement.attributeValue("relevant");
            avtXXFormsCalculate = submissionElement.attributeValue(XFormsConstants.XXFORMS_CALCULATE_QNAME);
            avtXXFormsUploads = submissionElement.attributeValue(XFormsConstants.XXFORMS_UPLOADS_QNAME);
            avtXXFormsAnnotate = submissionElement.attributeValue(XFormsConstants.XXFORMS_ANNOTATE_QNAME);

            avtSerialization = submissionElement.attributeValue("serialization");

            // @targetref is the new name as of May 2009, and @target is still supported for backward compatibility
            targetref = submissionElement.attributeValue("targetref");
            if (targetref == null)
                targetref = submissionElement.attributeValue(XFormsConstants.TARGET_QNAME);

            avtMode = submissionElement.attributeValue("mode");

            avtVersion = submissionElement.attributeValue("version");

            avtIndent = submissionElement.attributeValue("indent");
            avtMediatype = submissionElement.attributeValue("mediatype");
            avtEncoding = submissionElement.attributeValue("encoding");
            avtOmitxmldeclaration = submissionElement.attributeValue("omit-xml-declaration");
            avtStandalone = submissionElement.attributeValue("standalone");

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

                if (replace.equals("instance")) {
                    replaceInstanceId = submissionElement.attributeValue("instance");
                    xxfReplaceInstanceId = submissionElement.attributeValue(XFormsConstants.XXFORMS_INSTANCE_QNAME);
                }
            }
            if (submissionElement.attributeValue("separator") != null) {
                avtSeparator = submissionElement.attributeValue("separator");
            }
            // TODO
//            includenamespaceprefixes = submissionElement.attributeValue("includenamespaceprefixes");

            // Extension attributes
            avtXXFormsUsername = submissionElement.attributeValue(XFormsConstants.XXFORMS_USERNAME_QNAME);
            avtXXFormsPassword = submissionElement.attributeValue(XFormsConstants.XXFORMS_PASSWORD_QNAME);
            avtXXFormsPreemptiveAuthentication = submissionElement.attributeValue(XFormsConstants.XXFORMS_PREEMPTIVE_AUTHENTICATION_QNAME);
            avtXXFormsDomain = submissionElement.attributeValue(XFormsConstants.XXFORMS_DOMAIN_QNAME);

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

            avtXXFormsTarget = submissionElement.attributeValue(XFormsConstants.XXFORMS_TARGET_QNAME);
            avtXXFormsHandleXInclude = submissionElement.attributeValue(XFormsConstants.XXFORMS_XINCLUDE);

            // 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);

            // URL type
            urlType = submissionElement.attributeValue(XMLConstants.FORMATTING_URL_TYPE_QNAME);

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

    public String getId() {
        return id;
    }

    public String getPrefixedId() {
        return XFormsUtils.getPrefixedId(getEffectiveId());
    }

    public Scope scope() {
        return staticSubmission.scope();
    }

    public String getEffectiveId() {
        return XFormsUtils.getRelatedEffectiveId(model.getEffectiveId(), getId());
    }

    public XBLContainer container() {
        return getModel().container();
    }

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

    public XFormsEventObserver parentEventObserver() {
        return model;
    }

    public XFormsModel getModel() {
        return model;
    }

    public void performDefaultAction(XFormsEvent event) {
        final String eventName = event.name();

        if (XFormsEvents.XFORMS_SUBMIT.equals(eventName) || XFormsEvents.XXFORMS_SUBMIT.equals(eventName)) {
            // 11.1 The xforms-submit Event
            // Bubbles: Yes / Cancelable: Yes / Context Info: None
            doSubmit(event);
        } else if (XFormsEvents.XXFORMS_ACTION_ERROR.equals(eventName)) {
            final XXFormsActionErrorEvent ev = (XXFormsActionErrorEvent) event;
            XFormsError.handleNonFatalActionError(this, ev.throwable());
        }
    }

    private void doSubmit(XFormsEvent event) {
        containingDocument.setGotSubmission();

        final IndentedLogger indentedLogger = getIndentedLogger();

        // Variables declared here as they are used in a catch/finally block
        SubmissionParameters p = null;
        String resolvedActionOrResource = null;

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

        Runnable submitDoneOrErrorRunnable = null;
        try {
            try {
                // Big bag of initial runtime parameters
                p = new SubmissionParameters(event.name());

                if (indentedLogger.isDebugEnabled()) {
                    final String message = p.isDeferredSubmissionFirstPass ? "submission first pass" : p.isDeferredSubmissionSecondPass ? "submission second pass" : "submission";
                    indentedLogger.startHandleOperation("", message, "id", getEffectiveId());
                }

                // If a submission requiring a second pass was already set, then we ignore a subsequent submission but
                // issue a warning
                {
                    final XFormsModelSubmission existingSubmission = containingDocument.getClientActiveSubmissionFirstPass();
                    if (p.isDeferredSubmission && existingSubmission != null) {
                        indentedLogger.logWarning("", "another submission requiring a second pass already exists",
                                "existing submission", existingSubmission.getEffectiveId(),
                                "new submission", this.getEffectiveId());
                        return;
                    }
                }

                /* ***** Check for pending uploads ********************************************************************** */

                // We can do this first, because the check just depends on the controls, instance to submit, and pending
                // submissions if any. This does not depend on the actual state of the instance.
                if (p.serialize && p.resolvedXXFormsUploads && XFormsSubmissionUtils.hasBoundRelevantPendingUploadControls(containingDocument, p.refInstance)) {
                    throw new XFormsSubmissionException(this, "xf:submission: instance to submit has at least one pending upload.",
                        "checking pending uploads",
                        new XFormsSubmitErrorEvent(XFormsModelSubmission.this, XFormsSubmitErrorEvent.XXFORMS_PENDING_UPLOADS(), null));
                }

                /* ***** Update data model ****************************************************************************** */

                // "The data model is updated"
                final XFormsModel modelForInstance;
                if (p.refInstance != null) {
                    modelForInstance = p.refInstance.model();
                    if (modelForInstance != null) {
                        // NOTE: XForms 1.1 says that we should rebuild/recalculate the "model containing this submission".
                        // Here, we rebuild/recalculate instead the model containing the submission's single-node binding.
                        // This can be different than the model containing the submission if using e.g. xxf:instance().

                        // NOTE: XForms 1.1 seems to say this should happen regardless of whether we serialize or not. If
                        // the instance is not serialized and if no instance data is otherwise used for the submission,
                        // this seems however unneeded so we optimize out.
                        if (p.resolvedValidate || p.resolvedRelevant || p.resolvedXXFormsCalculate) {
                            // Rebuild impacts validation, relevance and calculated values (set by recalculate)
                            modelForInstance.doRebuild();
                        }
                        if (p.resolvedRelevant || p.resolvedXXFormsCalculate) {
                            // Recalculate impacts relevance and calculated values
                            modelForInstance.doRecalculate(false);
                        }
                    }
                } else {
                    // Case where no instance was found
                    modelForInstance = null;
                }

                /* ***** Handle deferred submission ********************************************************************* */

                // Resolve the target AVT because XFormsServer requires it for deferred submission
                resolvedXXFormsTarget = XFormsUtils.resolveAttributeValueTemplates(containingDocument, p.xpathContext, p.refNodeInfo, avtXXFormsTarget);

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

                    // Create (but abandon) document to submit here because in case of error, an Ajax response will still be produced
                    if (p.serialize) {
                        createDocumentToSubmit(indentedLogger, p.refNodeInfo, p.refInstance, modelForInstance, p.resolvedValidate, p.resolvedRelevant, p.resolvedXXFormsAnnotate);
                    }

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

                /* ***** Submission second pass ************************************************************************* */

                // Compute parameters only needed during second pass
                final SecondPassParameters p2 = new SecondPassParameters(p);
                resolvedActionOrResource = p2.actionOrResource; // in case of exception

                /* ***** Serialization ********************************************************************************** */

                // Get serialization requested from @method and @serialization attributes
                final String requestedSerialization = XFormsSubmissionUtils.getRequestedSerialization(p.serialization, p.resolvedMethod);
                if (requestedSerialization == null)
                    throw new XFormsSubmissionException(this, "xf:submission: invalid submission method requested: " + p.resolvedMethod, "serializing instance");

                final Document documentToSubmit;
                if (p.serialize) {

                    // Check if a submission requires file upload information
                    if (requestedSerialization.startsWith("multipart/")) {
                        // Annotate before re-rooting/pruning
                        XFormsSubmissionUtils.annotateBoundRelevantUploadControls(containingDocument, p.refInstance);
                    }

                    // Create document to submit
                    documentToSubmit = createDocumentToSubmit(indentedLogger, p.refNodeInfo, p.refInstance, modelForInstance, p.resolvedValidate, p.resolvedRelevant, p.resolvedXXFormsAnnotate);

                } else {
                    // Don't recreate document
                    documentToSubmit = null;
                }

                final String overriddenSerializedData;
                if (!p.isDeferredSubmissionSecondPass) {
                    if (p.serialize) {
                        // 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."

                        final XFormsSubmitSerializeEvent serializeEvent = new XFormsSubmitSerializeEvent(XFormsModelSubmission.this, p.refNodeInfo, requestedSerialization);
                        Dispatch.dispatchEvent(serializeEvent);

                        // TODO: rest of submission should happen upon default action of event

                        overriddenSerializedData = serializeEvent.submissionBodyAsString();
                    } else {
                        overriddenSerializedData = null;
                    }
                } else {
                    // Two reasons: 1. We don't want to modify the document state 2. This can be called outside of the document
                    // lock, see XFormsServer.
                    overriddenSerializedData = null;
                }

                // Serialize
                final SerializationParameters sp = new SerializationParameters(p, p2, requestedSerialization, documentToSubmit, overriddenSerializedData);

                /* ***** Submission connection ************************************************************************** */

                // Result information
                SubmissionResult submissionResult = null;

                // Iterate through submissions and run the first match
                for (final Submission submission : submissions) {
                    if (submission.isMatch(p, p2, sp)) {
                        if (indentedLogger.isDebugEnabled())
                            indentedLogger.startHandleOperation("", "connecting", "type", submission.getType());
                        try {
                            submissionResult = submission.connect(p, p2, sp);
                            break;
                        } finally {
                            if (indentedLogger.isDebugEnabled())
                                indentedLogger.endHandleOperation();
                        }
                    }
                }

                /* ***** Submission result processing ******************************************************************* */

                // NOTE: handleSubmissionResult() catches Throwable and returns a Runnable
                if (submissionResult != null)// submissionResult is null in case the submission is running asynchronously, AND when ???
                    submitDoneOrErrorRunnable = handleSubmissionResult(p, p2, submissionResult, true); // true because function context might have changed

            } catch (final Throwable throwable) {
                /* ***** Handle errors ********************************************************************************** */
                final SubmissionParameters pVal = p;
                final String resolvedActionOrResourceVal = resolvedActionOrResource;
                submitDoneOrErrorRunnable = new Runnable() {
                    public void run() {
                        if (pVal != null && pVal.isDeferredSubmissionSecondPass && XFormsProperties.isLocalSubmissionForward(containingDocument)) {
                            // It doesn't serve any purpose here to dispatch an event, so we just propagate the exception
                            throw new XFormsSubmissionException(XFormsModelSubmission.this, throwable, "Error while processing xf:submission", "processing submission");
                        } else {
                            // Any exception will cause an error event to be dispatched
                            sendSubmitError(resolvedActionOrResourceVal, throwable);
                        }
                    }
                };
            }
        } finally {
            // Log total time spent in submission
            if (p != null && indentedLogger.isDebugEnabled()) {
                indentedLogger.endHandleOperation();
            }
        }

        // Execute post-submission code if any
        // This typically dispatches xforms-submit-done/xforms-submit-error, or may throw another exception
        if (submitDoneOrErrorRunnable != null) {
            // We do this outside the above catch block so that if a problem occurs during dispatching xforms-submit-done
            // or xforms-submit-error we don't dispatch xforms-submit-error (which would be illegal).
            // This will also close the connection result if needed.
            submitDoneOrErrorRunnable.run();
        }
    }

    /*
     * Process the response of an asynchronous submission.
     */
    public void doSubmitReplace(SubmissionResult submissionResult) {

        assert submissionResult != null;

        // Big bag of initial runtime parameters
        final SubmissionParameters p = new SubmissionParameters(null);
        final SecondPassParameters p2 = new SecondPassParameters(p);

        final Runnable submitDoneRunnable = handleSubmissionResult(p, p2, submissionResult, false);

        // Execute submit done runnable if any
        if (submitDoneRunnable != null) {
            // Do this outside the handleSubmissionResult catch block so that if a problem occurs during dispatching
            // xforms-submit-done we don't dispatch xforms-submit-error (which would be illegal)
            submitDoneRunnable.run();
        }
    }

    private Runnable handleSubmissionResult(SubmissionParameters p, SecondPassParameters p2, final SubmissionResult submissionResult, boolean initializeXPathContext) {

        assert p != null;
        assert p2 != null;
        assert submissionResult != null;

        Runnable submitDoneOrErrorRunnable = null;
        try {
            final IndentedLogger indentedLogger = getIndentedLogger();
            if (indentedLogger.isDebugEnabled())
                indentedLogger.startHandleOperation("", "handling result");
            try {
                // Get fresh XPath context if requested
                if (initializeXPathContext)
                    p.initializeXPathContext();
                // Process the different types of response
                if (submissionResult.getThrowable() != null) {
                    // Propagate throwable, which might have come from a separate thread
                    submitDoneOrErrorRunnable = new Runnable() {
                        public void run() { sendSubmitError(submissionResult.getThrowable(), submissionResult); }
                    };
                } else {
                    // Replacer provided, perform replacement
                    assert submissionResult.getReplacer() != null;
                    submitDoneOrErrorRunnable = submissionResult.getReplacer().replace(submissionResult.getConnectionResult(), p, p2);
                }
            } finally {
                if (indentedLogger.isDebugEnabled())
                    indentedLogger.endHandleOperation();
            }
        } catch (final Throwable throwable) {
            // Any exception will cause an error event to be dispatched
            submitDoneOrErrorRunnable = new Runnable() {
                public void run() { sendSubmitError(throwable, submissionResult); }
            };
        }

        // Create wrapping runnable to make sure the submission result is closed
        final Runnable finalSubmitDoneOrErrorRunnable = submitDoneOrErrorRunnable;
        return new Runnable() {
            public void run() {
                try {
                    finalSubmitDoneOrErrorRunnable.run();
                } finally {
                    // Close only after the submission result has run
                    submissionResult.close();
                }
            }
        };
    }

    /**
     * Run the given submission callable. This must be a callable for a replace="all" submission.
     *
     * @param callable          callable run
     * @param response          response to write to if needed
     */
    public static void runDeferredSubmission(Callable<SubmissionResult> callable, ExternalContext.Response response) {
        // Run submission
        try {
            final SubmissionResult result = callable.call();
            if (result != null) {
                // Callable did not do all the work, completed it here
                try {
                    if (result.getReplacer() != null) {
                        // Replacer provided, perform replacement
                        if (result.getReplacer() instanceof AllReplacer)
                            AllReplacer.replace(result.getConnectionResult(), response);
                        else
                            assert result.getReplacer() instanceof NoneReplacer;
                    } else if (result.getThrowable() != null) {
                        // Propagate throwable, which might have come from a separate thread
                        throw new OXFException(result.getThrowable());
                    } else {
                        // Should not happen
                    }
                } finally {
                    result.close();
                }
            }
        } catch (Exception e) {
            // Something bad happened
            throw new OXFException(e);
        }
    }

    public Runnable sendSubmitDone(final ConnectionResult connectionResult) {
        return new Runnable() {
            public void run() {
                // After a submission, the context might have changed
                model.resetAndEvaluateVariables();

                Dispatch.dispatchEvent(new XFormsSubmitDoneEvent(XFormsModelSubmission.this, connectionResult));
            }
        };
    }

    private void sendSubmitError(Throwable throwable, SubmissionResult submissionResult) {

        // After a submission, the context might have changed
        model.resetAndEvaluateVariables();

        // Try to get error event from exception
        XFormsSubmitErrorEvent submitErrorEvent = null;
        if (throwable instanceof XFormsSubmissionException) {
            final XFormsSubmissionException submissionException = (XFormsSubmissionException) throwable;
            submitErrorEvent = submissionException.getXFormsSubmitErrorEvent();
        }

        // If no event obtained, create default event
        if (submitErrorEvent == null) {
            submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this,
                submitErrorEvent.XXFORMS_INTERNAL_ERROR(), submissionResult.getConnectionResult());
        }

        // Dispatch event
        submitErrorEvent.logThrowable(throwable);
        Dispatch.dispatchEvent(submitErrorEvent);
    }

    private void sendSubmitError(String resolvedActionOrResource, Throwable throwable) {

        // After a submission, the context might have changed
        model.resetAndEvaluateVariables();

        // Try to get error event from exception
        XFormsSubmitErrorEvent submitErrorEvent = null;
        if (throwable instanceof XFormsSubmissionException) {
            final XFormsSubmissionException submissionException = (XFormsSubmissionException) throwable;
            submitErrorEvent = submissionException.getXFormsSubmitErrorEvent();
        }

        // If no event obtained, create default event
        if (submitErrorEvent == null) {
            submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this, scala.Option.apply(resolvedActionOrResource),
                submitErrorEvent.XXFORMS_INTERNAL_ERROR(), 0);
        }

        // Dispatch event
        submitErrorEvent.logThrowable(throwable);
        Dispatch.dispatchEvent(submitErrorEvent);
    }

    public Replacer getReplacer(ConnectionResult connectionResult, SubmissionParameters p) throws IOException {

        // NOTE: This can be called from other threads so it must NOT modify the XFCD or submission

        if (connectionResult != null) {
            // Handle response
            final Replacer replacer;
            if (connectionResult.dontHandleResponse) {
                // Always return a replacer even if it does nothing, this way we don't have to deal with null
                replacer = new NoneReplacer(this, containingDocument);
            } else if (NetUtils.isSuccessCode(connectionResult.statusCode)) {
                // Successful response
                if (connectionResult.hasContent()) {
                    // There is a body

                    // Get replacer
                    if (p.isReplaceAll) {
                        replacer = new AllReplacer(this, containingDocument);
                    } else if (p.isReplaceInstance) {
                        replacer = new InstanceReplacer(this, containingDocument);
                    } else if (p.isReplaceText) {
                        replacer = new TextReplacer(this, containingDocument);
                    } else if (p.isReplaceNone) {
                        replacer = new NoneReplacer(this, containingDocument);
                    } else {
                        throw new XFormsSubmissionException(this, "xf:submission: invalid replace attribute: " + replace, "processing instance replacement",
                                new XFormsSubmitErrorEvent(this, XFormsSubmitErrorEvent.XXFORMS_INTERNAL_ERROR(), connectionResult));
                    }
                } else {
                    // There is no body, notify that processing is terminated
                    if (p.isReplaceInstance || p.isReplaceText) {
                        // XForms 1.1 says it is fine not to have a body, but in most cases you will want to know that
                        // no instance replacement took place
                        final IndentedLogger indentedLogger = getIndentedLogger();
                        indentedLogger.logWarning("", "instance or text replacement did not take place upon successful response because no body was provided.",
                                "submission id", getEffectiveId());
                    }

                    // "For a success response not including a body, submission processing concludes after dispatching
                    // xforms-submit-done"
                    replacer = new NoneReplacer(this, containingDocument);
                }
            } else if (connectionResult.statusCode == 302 || connectionResult.statusCode == 301) {
                // Got a redirect

                // Currently we don't know how to handle a redirect for replace != "all"
                if (!p.isReplaceAll)
                    throw new XFormsSubmissionException(this, "xf:submission for submission id: " + id + ", redirect code received with replace=\"" + replace + "\"", "processing submission response",
                            new XFormsSubmitErrorEvent(this, XFormsSubmitErrorEvent.RESOURCE_ERROR(), connectionResult));

                replacer = new RedirectReplacer(this, containingDocument);

            } else {
                // Error code received
                throw new XFormsSubmissionException(this, "xf:submission for submission id: " + id + ", error code received when submitting instance: " + connectionResult.statusCode, "processing submission response",
                        new XFormsSubmitErrorEvent(this, XFormsSubmitErrorEvent.RESOURCE_ERROR(), connectionResult));
            }

            return replacer;
        } else {
            return null;
        }
    }

    public class SubmissionParameters {

        // @replace attribute
        final boolean isReplaceAll = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL);
        final boolean isReplaceInstance = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_INSTANCE);
        final boolean isReplaceText = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_TEXT);
        final boolean isReplaceNone = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_NONE);

        // Current node for xf:submission and instance containing the node to submit
        NodeInfo refNodeInfo;
        XFormsInstance refInstance;
        Item submissionElementContextItem;

        final String resolvedMethod;
        final String actualHttpMethod;
        final String resolvedMediatype;
        final String serialization;
        final boolean serialize;

        final boolean resolvedValidate;
        final boolean resolvedRelevant;
        final boolean resolvedXXFormsCalculate;
        final boolean resolvedXXFormsUploads;
        final String resolvedXXFormsAnnotate;

        final boolean isHandlingClientGetAll;

        // XPath function library and namespace mappings
        final FunctionLibrary functionLibrary = XFormsContainingDocument.getFunctionLibrary();
        final NamespaceMapping namespaceMapping = container.getNamespaceMappings(submissionElement);

        // XPath context
        XPathCache.XPathContext xpathContext;

        final boolean isNoscript;
        final boolean isAllowDeferredSubmission;

        final boolean isPossibleDeferredSubmission;
        final boolean isDeferredSubmission;
        final boolean isDeferredSubmissionFirstPass;
        final boolean isDeferredSubmissionSecondPass;

        public void initializeXPathContext() {

            final BindingContext bindingContext; {
                model.resetAndEvaluateVariables();
                final XFormsContextStack contextStack = model.getContextStack();
                contextStack.pushBinding(getSubmissionElement(), getEffectiveId(), model.getResolutionScope());
                bindingContext = contextStack.getCurrentBindingContext();
            }

            final XFormsFunction.Context functionContext = model.getContextStack().getFunctionContext(getEffectiveId());

            refNodeInfo = (NodeInfo) bindingContext.getSingleItem();
            submissionElementContextItem = bindingContext.contextItem();
            // NOTE: Current instance may be null if the document submitted is not part of an instance
            refInstance = bindingContext.instanceOrNull();
            xpathContext = new XPathCache.XPathContext(namespaceMapping, bindingContext.getInScopeVariables(), functionLibrary, functionContext, null, getLocationData());
        }

        public SubmissionParameters(String eventName) {

            initializeXPathContext();

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

            if (!(refNodeInfo instanceof DocumentInfo || refNodeInfo.getNodeKind() == org.w3c.dom.Node.ELEMENT_NODE)) {
                throw new XFormsSubmissionException(XFormsModelSubmission.this, "xf:submission: single-node binding must refer to a document node or an element.", "getting submission single-node binding",
                        new XFormsSubmitErrorEvent(XFormsModelSubmission.this, XFormsSubmitErrorEvent.NO_DATA(), null));
            }

            {
                // Resolved method AVT
                final String resolvedMethodQName = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtMethod);
                resolvedMethod = Dom4jUtils.qNameToExplodedQName(Dom4jUtils.extractTextValueQName(namespaceMapping.mapping, resolvedMethodQName, true));

                // Get actual method based on the method attribute
                actualHttpMethod = XFormsSubmissionUtils.getActualHttpMethod(resolvedMethod);

                // Get mediatype
                resolvedMediatype = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo, avtMediatype);

                // Serialization
                serialization = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo, avtSerialization);
                if (serialization != null) {
                    serialize = !serialization.equals("none");
                } else {
                    // For backward compatibility only, support @serialize if there is no @serialization attribute (was in early XForms 1.1 draft)
                    serialize = !"false".equals(submissionElement.attributeValue("serialize"));
                }

                // Resolve validate and relevant AVTs
                final String resolvedValidateString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtValidate);
                // "The default value is "false" if the value of serialization is "none" and "true" otherwise"
                resolvedValidate = serialize && !"false".equals(resolvedValidateString);

                final String resolvedRelevantString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtRelevant);
                // "The default value is "false" if the value of serialization is "none" and "true" otherwise"
                resolvedRelevant = serialize && !"false".equals(resolvedRelevantString);

                final String resolvedCalculateString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtXXFormsCalculate);
                resolvedXXFormsCalculate = serialize && !"false".equals(resolvedCalculateString);

                final String resolvedUploadsString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtXXFormsUploads);
                resolvedXXFormsUploads = serialize && !"false".equals(resolvedUploadsString);

                final String resolvedAnnotateString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtXXFormsAnnotate);
                resolvedXXFormsAnnotate = serialize ? resolvedAnnotateString : null;
            }

            isHandlingClientGetAll = XFormsProperties.isOptimizeGetAllSubmission(containingDocument) && actualHttpMethod.equals("GET")
                    && isReplaceAll
                    && (resolvedMediatype == null || !resolvedMediatype.startsWith(NetUtils.APPLICATION_SOAP_XML)) // can't let SOAP requests be handled by the browser
                    && avtXXFormsUsername == null // can't optimize if there are authentication credentials
                    && avtXXFormsTarget == null   // can't optimize if there is a target
                    && Dom4jUtils.elements(getSubmissionElement(), XFormsConstants.XFORMS_HEADER_QNAME).size() == 0; // can't optimize if there are headers specified

            // In noscript mode, or in "Ajax portlet" mode, there is no deferred submission process
            // Also don't allow deferred submissions when the incoming method is a GET. This is an indirect way of
            // allowing things like using the XForms engine to generate a PDF with an HTTP GET.

            // NOTE: Method can be null e.g. in a portlet render request
            final String method = NetUtils.getExternalContext().getRequest().getMethod();

            isNoscript = containingDocument.getStaticState().isNoscript();
            isAllowDeferredSubmission = !isNoscript && !(method != null && method.equals("GET"));

            isPossibleDeferredSubmission = isReplaceAll && !isHandlingClientGetAll && !containingDocument.isInitializing();
            isDeferredSubmission = isAllowDeferredSubmission && isPossibleDeferredSubmission;
            isDeferredSubmissionFirstPass = isDeferredSubmission && XFormsEvents.XFORMS_SUBMIT.equals(eventName);
            isDeferredSubmissionSecondPass = isDeferredSubmission && !isDeferredSubmissionFirstPass; // here we get XXFORMS_SUBMIT
        }
    }

    public class SecondPassParameters {

        // This mostly consists of AVTs that can be evaluated only during the second pass of the submission

        final String actionOrResource;
        final String mode;
        final String version;
        final String encoding;
        final String separator;
        final boolean indent;
        final boolean omitxmldeclaration;
        final Boolean standalone;
        final Connection.Credentials credentials;
        final boolean isReadonly;
        final boolean isCache;
        final long timeToLive;
        final boolean isHandleXInclude;

        final boolean isAsynchronous;

        public SecondPassParameters(SubmissionParameters p) {
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtActionOrResource);
                if (temp == null) {
                    // This can be null if, e.g. you have an AVT like resource="{()}"
                    throw new XFormsSubmissionException(XFormsModelSubmission.this, "xf:submission: mandatory resource or action evaluated to an empty sequence for attribute value: " + avtActionOrResource,
                            "resolving resource URI");
                }
                actionOrResource = NetUtils.encodeHRRI(temp, true);
                // TODO: see if we can resolve xml:base early to detect absolute URLs early as well
//                actionOrResource = XFormsUtils.resolveXMLBase(containingDocument, getSubmissionElement(), NetUtils.encodeHRRI(temp, true)).toString();
            }

            mode = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtMode);
            version = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtVersion);
            separator = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtSeparator);

            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtEncoding);
                encoding = (temp != null) ? temp : "UTF-8";
            }
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtIndent);
                indent = Boolean.valueOf(temp);
            }
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtOmitxmldeclaration);
                omitxmldeclaration = Boolean.valueOf(temp);
            }
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtStandalone);
                standalone = (temp != null) ? Boolean.valueOf(temp) : null;
            }

            final String username = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsUsername);
            final String password = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsPassword);
            final String preemptiveAuthentication = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsPreemptiveAuthentication);
            final String domain = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsDomain);

            if (username == null)
                credentials = null;
            else
                credentials = new Connection.Credentials(username, password, preemptiveAuthentication, domain);

            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsReadonly);
                isReadonly = (temp != null) ? Boolean.valueOf(temp) : false;
            }

            if (avtXXFormsCache != null) {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsCache);
                // New attribute
                isCache = Boolean.valueOf(temp);
            } else {
                // For backward compatibility
                isCache = "application".equals(XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsShared));<commit_msg>Forward JSESSIONID when xxf:username="", fix #1267<commit_after>/**
 * Copyright (C) 2010 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.submission;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.log4j.Logger;
import org.dom4j.*;
import org.dom4j.io.DocumentSource;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.util.*;
import org.orbeon.oxf.xforms.*;
import org.orbeon.oxf.xforms.analysis.model.Instance;
import org.orbeon.oxf.xforms.event.*;
import org.orbeon.oxf.xforms.event.events.*;
import org.orbeon.oxf.xforms.function.XFormsFunction;
import org.orbeon.oxf.xforms.xbl.Scope;
import org.orbeon.oxf.xforms.xbl.XBLContainer;
import org.orbeon.oxf.xml.NamespaceMapping;
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.saxon.functions.FunctionLibrary;
import org.orbeon.saxon.om.DocumentInfo;
import org.orbeon.saxon.om.Item;
import org.orbeon.saxon.om.NodeInfo;
import org.orbeon.saxon.om.VirtualNode;

import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;

/**
 * Represents an XForms model submission instance.
 *
 * TODO: Refactor handling of serialization to separate classes.
 */
public class XFormsModelSubmission extends XFormsModelSubmissionBase implements XFormsEventTarget, XFormsEventObserver {

    public static final String LOGGING_CATEGORY = "submission";
	public final static Logger logger = LoggerFactory.createLogger(XFormsModelSubmission.class);

    private final org.orbeon.oxf.xforms.analysis.model.Submission staticSubmission;
    private final String id;
    private final Element submissionElement;

    private final XBLContainer container;
    private final XFormsContainingDocument containingDocument;

    private final XFormsModel model;
    private boolean submissionElementExtracted = false;

    private String avtActionOrResource; // required unless there is a nested xf:resource element;
    private String avtMethod; // required

    private String avtValidate;
    private String avtRelevant;
    private String avtXXFormsCalculate;
    private String avtXXFormsUploads;
    private String avtXXFormsAnnotate;

    private String avtSerialization;

    private String targetref;// this is an XPath expression when used with replace="instance|text" (other meaning possible post-XForms 1.1 for replace="all")
    private String avtMode;

    private String avtVersion;
    private String avtEncoding;
    private String avtMediatype;
    private String avtIndent;
    private String avtOmitxmldeclaration;
    private String avtStandalone;
//    private String cdatasectionelements;

    private String replace = XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL;
    private String replaceInstanceId;
    private String xxfReplaceInstanceId;
    private String avtSeparator = "&";// XForms 1.1 changes back the default to the ampersand as of February 2009
//    private String includenamespaceprefixes;

    private String avtXXFormsUsername;
    private String avtXXFormsPassword;
    private String avtXXFormsPreemptiveAuthentication;
    private String avtXXFormsDomain;
    private String avtXXFormsReadonly;
    private String avtXXFormsShared;
    private String avtXXFormsCache;
    private String avtXXFormsTarget;
    private String resolvedXXFormsTarget;
    private String avtXXFormsHandleXInclude;

    private boolean xxfShowProgress;

    private boolean fURLNorewrite;
    private String urlType;

    // All the submission types in the order they must be checked
    private final Submission[] submissions;

    public XFormsModelSubmission(XBLContainer container, org.orbeon.oxf.xforms.analysis.model.Submission staticSubmission, XFormsModel model) {
        this.staticSubmission = staticSubmission;

        this.id = staticSubmission.staticId();
        this.submissionElement = staticSubmission.element();

        this.container = container;
        this.containingDocument = container.getContainingDocument();

        this.model = model;

        this.submissions = new Submission[] {
            new EchoSubmission(this),
            new ClientGetAllSubmission(this),
            new FilterPortletSubmission(this),
            new CacheableSubmission(this),
            new LocalPortletSubmission(this),
            new RequestDispatcherSubmission(this),
            new RegularSubmission(this)
        };
    }

    public XFormsContainingDocument containingDocument() {
        return containingDocument;
    }

    public Element getSubmissionElement() {
        return submissionElement;
    }


    public boolean isShowProgress() {
        return xxfShowProgress;
    }

    public boolean isURLNorewrite() {
        return fURLNorewrite;
    }

    public String getUrlType() {
        return urlType;
    }

    public String getReplace() {
        return replace;
    }

    public String getTargetref() {
        return targetref;
    }

    public String getResolvedXXFormsTarget() {
        return resolvedXXFormsTarget;
    }

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

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

            avtMethod = submissionElement.attributeValue("method");
            if (avtMethod == null) {
                // TODO: support XForms 1.1 nested xf:method element
                throw new XFormsSubmissionException(this, "xf:submission: method attribute is missing.",
                        "processing xf:submission attributes");
            }
            avtValidate = submissionElement.attributeValue("validate");
            avtRelevant = submissionElement.attributeValue("relevant");
            avtXXFormsCalculate = submissionElement.attributeValue(XFormsConstants.XXFORMS_CALCULATE_QNAME);
            avtXXFormsUploads = submissionElement.attributeValue(XFormsConstants.XXFORMS_UPLOADS_QNAME);
            avtXXFormsAnnotate = submissionElement.attributeValue(XFormsConstants.XXFORMS_ANNOTATE_QNAME);

            avtSerialization = submissionElement.attributeValue("serialization");

            // @targetref is the new name as of May 2009, and @target is still supported for backward compatibility
            targetref = submissionElement.attributeValue("targetref");
            if (targetref == null)
                targetref = submissionElement.attributeValue(XFormsConstants.TARGET_QNAME);

            avtMode = submissionElement.attributeValue("mode");

            avtVersion = submissionElement.attributeValue("version");

            avtIndent = submissionElement.attributeValue("indent");
            avtMediatype = submissionElement.attributeValue("mediatype");
            avtEncoding = submissionElement.attributeValue("encoding");
            avtOmitxmldeclaration = submissionElement.attributeValue("omit-xml-declaration");
            avtStandalone = submissionElement.attributeValue("standalone");

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

                if (replace.equals("instance")) {
                    replaceInstanceId = submissionElement.attributeValue("instance");
                    xxfReplaceInstanceId = submissionElement.attributeValue(XFormsConstants.XXFORMS_INSTANCE_QNAME);
                }
            }
            if (submissionElement.attributeValue("separator") != null) {
                avtSeparator = submissionElement.attributeValue("separator");
            }
            // TODO
//            includenamespaceprefixes = submissionElement.attributeValue("includenamespaceprefixes");

            // Extension attributes
            avtXXFormsUsername = submissionElement.attributeValue(XFormsConstants.XXFORMS_USERNAME_QNAME);
            avtXXFormsPassword = submissionElement.attributeValue(XFormsConstants.XXFORMS_PASSWORD_QNAME);
            avtXXFormsPreemptiveAuthentication = submissionElement.attributeValue(XFormsConstants.XXFORMS_PREEMPTIVE_AUTHENTICATION_QNAME);
            avtXXFormsDomain = submissionElement.attributeValue(XFormsConstants.XXFORMS_DOMAIN_QNAME);

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

            avtXXFormsTarget = submissionElement.attributeValue(XFormsConstants.XXFORMS_TARGET_QNAME);
            avtXXFormsHandleXInclude = submissionElement.attributeValue(XFormsConstants.XXFORMS_XINCLUDE);

            // 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);

            // URL type
            urlType = submissionElement.attributeValue(XMLConstants.FORMATTING_URL_TYPE_QNAME);

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

    public String getId() {
        return id;
    }

    public String getPrefixedId() {
        return XFormsUtils.getPrefixedId(getEffectiveId());
    }

    public Scope scope() {
        return staticSubmission.scope();
    }

    public String getEffectiveId() {
        return XFormsUtils.getRelatedEffectiveId(model.getEffectiveId(), getId());
    }

    public XBLContainer container() {
        return getModel().container();
    }

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

    public XFormsEventObserver parentEventObserver() {
        return model;
    }

    public XFormsModel getModel() {
        return model;
    }

    public void performDefaultAction(XFormsEvent event) {
        final String eventName = event.name();

        if (XFormsEvents.XFORMS_SUBMIT.equals(eventName) || XFormsEvents.XXFORMS_SUBMIT.equals(eventName)) {
            // 11.1 The xforms-submit Event
            // Bubbles: Yes / Cancelable: Yes / Context Info: None
            doSubmit(event);
        } else if (XFormsEvents.XXFORMS_ACTION_ERROR.equals(eventName)) {
            final XXFormsActionErrorEvent ev = (XXFormsActionErrorEvent) event;
            XFormsError.handleNonFatalActionError(this, ev.throwable());
        }
    }

    private void doSubmit(XFormsEvent event) {
        containingDocument.setGotSubmission();

        final IndentedLogger indentedLogger = getIndentedLogger();

        // Variables declared here as they are used in a catch/finally block
        SubmissionParameters p = null;
        String resolvedActionOrResource = null;

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

        Runnable submitDoneOrErrorRunnable = null;
        try {
            try {
                // Big bag of initial runtime parameters
                p = new SubmissionParameters(event.name());

                if (indentedLogger.isDebugEnabled()) {
                    final String message = p.isDeferredSubmissionFirstPass ? "submission first pass" : p.isDeferredSubmissionSecondPass ? "submission second pass" : "submission";
                    indentedLogger.startHandleOperation("", message, "id", getEffectiveId());
                }

                // If a submission requiring a second pass was already set, then we ignore a subsequent submission but
                // issue a warning
                {
                    final XFormsModelSubmission existingSubmission = containingDocument.getClientActiveSubmissionFirstPass();
                    if (p.isDeferredSubmission && existingSubmission != null) {
                        indentedLogger.logWarning("", "another submission requiring a second pass already exists",
                                "existing submission", existingSubmission.getEffectiveId(),
                                "new submission", this.getEffectiveId());
                        return;
                    }
                }

                /* ***** Check for pending uploads ********************************************************************** */

                // We can do this first, because the check just depends on the controls, instance to submit, and pending
                // submissions if any. This does not depend on the actual state of the instance.
                if (p.serialize && p.resolvedXXFormsUploads && XFormsSubmissionUtils.hasBoundRelevantPendingUploadControls(containingDocument, p.refInstance)) {
                    throw new XFormsSubmissionException(this, "xf:submission: instance to submit has at least one pending upload.",
                        "checking pending uploads",
                        new XFormsSubmitErrorEvent(XFormsModelSubmission.this, XFormsSubmitErrorEvent.XXFORMS_PENDING_UPLOADS(), null));
                }

                /* ***** Update data model ****************************************************************************** */

                // "The data model is updated"
                final XFormsModel modelForInstance;
                if (p.refInstance != null) {
                    modelForInstance = p.refInstance.model();
                    if (modelForInstance != null) {
                        // NOTE: XForms 1.1 says that we should rebuild/recalculate the "model containing this submission".
                        // Here, we rebuild/recalculate instead the model containing the submission's single-node binding.
                        // This can be different than the model containing the submission if using e.g. xxf:instance().

                        // NOTE: XForms 1.1 seems to say this should happen regardless of whether we serialize or not. If
                        // the instance is not serialized and if no instance data is otherwise used for the submission,
                        // this seems however unneeded so we optimize out.
                        if (p.resolvedValidate || p.resolvedRelevant || p.resolvedXXFormsCalculate) {
                            // Rebuild impacts validation, relevance and calculated values (set by recalculate)
                            modelForInstance.doRebuild();
                        }
                        if (p.resolvedRelevant || p.resolvedXXFormsCalculate) {
                            // Recalculate impacts relevance and calculated values
                            modelForInstance.doRecalculate(false);
                        }
                    }
                } else {
                    // Case where no instance was found
                    modelForInstance = null;
                }

                /* ***** Handle deferred submission ********************************************************************* */

                // Resolve the target AVT because XFormsServer requires it for deferred submission
                resolvedXXFormsTarget = XFormsUtils.resolveAttributeValueTemplates(containingDocument, p.xpathContext, p.refNodeInfo, avtXXFormsTarget);

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

                    // Create (but abandon) document to submit here because in case of error, an Ajax response will still be produced
                    if (p.serialize) {
                        createDocumentToSubmit(indentedLogger, p.refNodeInfo, p.refInstance, modelForInstance, p.resolvedValidate, p.resolvedRelevant, p.resolvedXXFormsAnnotate);
                    }

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

                /* ***** Submission second pass ************************************************************************* */

                // Compute parameters only needed during second pass
                final SecondPassParameters p2 = new SecondPassParameters(p);
                resolvedActionOrResource = p2.actionOrResource; // in case of exception

                /* ***** Serialization ********************************************************************************** */

                // Get serialization requested from @method and @serialization attributes
                final String requestedSerialization = XFormsSubmissionUtils.getRequestedSerialization(p.serialization, p.resolvedMethod);
                if (requestedSerialization == null)
                    throw new XFormsSubmissionException(this, "xf:submission: invalid submission method requested: " + p.resolvedMethod, "serializing instance");

                final Document documentToSubmit;
                if (p.serialize) {

                    // Check if a submission requires file upload information
                    if (requestedSerialization.startsWith("multipart/")) {
                        // Annotate before re-rooting/pruning
                        XFormsSubmissionUtils.annotateBoundRelevantUploadControls(containingDocument, p.refInstance);
                    }

                    // Create document to submit
                    documentToSubmit = createDocumentToSubmit(indentedLogger, p.refNodeInfo, p.refInstance, modelForInstance, p.resolvedValidate, p.resolvedRelevant, p.resolvedXXFormsAnnotate);

                } else {
                    // Don't recreate document
                    documentToSubmit = null;
                }

                final String overriddenSerializedData;
                if (!p.isDeferredSubmissionSecondPass) {
                    if (p.serialize) {
                        // 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."

                        final XFormsSubmitSerializeEvent serializeEvent = new XFormsSubmitSerializeEvent(XFormsModelSubmission.this, p.refNodeInfo, requestedSerialization);
                        Dispatch.dispatchEvent(serializeEvent);

                        // TODO: rest of submission should happen upon default action of event

                        overriddenSerializedData = serializeEvent.submissionBodyAsString();
                    } else {
                        overriddenSerializedData = null;
                    }
                } else {
                    // Two reasons: 1. We don't want to modify the document state 2. This can be called outside of the document
                    // lock, see XFormsServer.
                    overriddenSerializedData = null;
                }

                // Serialize
                final SerializationParameters sp = new SerializationParameters(p, p2, requestedSerialization, documentToSubmit, overriddenSerializedData);

                /* ***** Submission connection ************************************************************************** */

                // Result information
                SubmissionResult submissionResult = null;

                // Iterate through submissions and run the first match
                for (final Submission submission : submissions) {
                    if (submission.isMatch(p, p2, sp)) {
                        if (indentedLogger.isDebugEnabled())
                            indentedLogger.startHandleOperation("", "connecting", "type", submission.getType());
                        try {
                            submissionResult = submission.connect(p, p2, sp);
                            break;
                        } finally {
                            if (indentedLogger.isDebugEnabled())
                                indentedLogger.endHandleOperation();
                        }
                    }
                }

                /* ***** Submission result processing ******************************************************************* */

                // NOTE: handleSubmissionResult() catches Throwable and returns a Runnable
                if (submissionResult != null)// submissionResult is null in case the submission is running asynchronously, AND when ???
                    submitDoneOrErrorRunnable = handleSubmissionResult(p, p2, submissionResult, true); // true because function context might have changed

            } catch (final Throwable throwable) {
                /* ***** Handle errors ********************************************************************************** */
                final SubmissionParameters pVal = p;
                final String resolvedActionOrResourceVal = resolvedActionOrResource;
                submitDoneOrErrorRunnable = new Runnable() {
                    public void run() {
                        if (pVal != null && pVal.isDeferredSubmissionSecondPass && XFormsProperties.isLocalSubmissionForward(containingDocument)) {
                            // It doesn't serve any purpose here to dispatch an event, so we just propagate the exception
                            throw new XFormsSubmissionException(XFormsModelSubmission.this, throwable, "Error while processing xf:submission", "processing submission");
                        } else {
                            // Any exception will cause an error event to be dispatched
                            sendSubmitError(resolvedActionOrResourceVal, throwable);
                        }
                    }
                };
            }
        } finally {
            // Log total time spent in submission
            if (p != null && indentedLogger.isDebugEnabled()) {
                indentedLogger.endHandleOperation();
            }
        }

        // Execute post-submission code if any
        // This typically dispatches xforms-submit-done/xforms-submit-error, or may throw another exception
        if (submitDoneOrErrorRunnable != null) {
            // We do this outside the above catch block so that if a problem occurs during dispatching xforms-submit-done
            // or xforms-submit-error we don't dispatch xforms-submit-error (which would be illegal).
            // This will also close the connection result if needed.
            submitDoneOrErrorRunnable.run();
        }
    }

    /*
     * Process the response of an asynchronous submission.
     */
    public void doSubmitReplace(SubmissionResult submissionResult) {

        assert submissionResult != null;

        // Big bag of initial runtime parameters
        final SubmissionParameters p = new SubmissionParameters(null);
        final SecondPassParameters p2 = new SecondPassParameters(p);

        final Runnable submitDoneRunnable = handleSubmissionResult(p, p2, submissionResult, false);

        // Execute submit done runnable if any
        if (submitDoneRunnable != null) {
            // Do this outside the handleSubmissionResult catch block so that if a problem occurs during dispatching
            // xforms-submit-done we don't dispatch xforms-submit-error (which would be illegal)
            submitDoneRunnable.run();
        }
    }

    private Runnable handleSubmissionResult(SubmissionParameters p, SecondPassParameters p2, final SubmissionResult submissionResult, boolean initializeXPathContext) {

        assert p != null;
        assert p2 != null;
        assert submissionResult != null;

        Runnable submitDoneOrErrorRunnable = null;
        try {
            final IndentedLogger indentedLogger = getIndentedLogger();
            if (indentedLogger.isDebugEnabled())
                indentedLogger.startHandleOperation("", "handling result");
            try {
                // Get fresh XPath context if requested
                if (initializeXPathContext)
                    p.initializeXPathContext();
                // Process the different types of response
                if (submissionResult.getThrowable() != null) {
                    // Propagate throwable, which might have come from a separate thread
                    submitDoneOrErrorRunnable = new Runnable() {
                        public void run() { sendSubmitError(submissionResult.getThrowable(), submissionResult); }
                    };
                } else {
                    // Replacer provided, perform replacement
                    assert submissionResult.getReplacer() != null;
                    submitDoneOrErrorRunnable = submissionResult.getReplacer().replace(submissionResult.getConnectionResult(), p, p2);
                }
            } finally {
                if (indentedLogger.isDebugEnabled())
                    indentedLogger.endHandleOperation();
            }
        } catch (final Throwable throwable) {
            // Any exception will cause an error event to be dispatched
            submitDoneOrErrorRunnable = new Runnable() {
                public void run() { sendSubmitError(throwable, submissionResult); }
            };
        }

        // Create wrapping runnable to make sure the submission result is closed
        final Runnable finalSubmitDoneOrErrorRunnable = submitDoneOrErrorRunnable;
        return new Runnable() {
            public void run() {
                try {
                    finalSubmitDoneOrErrorRunnable.run();
                } finally {
                    // Close only after the submission result has run
                    submissionResult.close();
                }
            }
        };
    }

    /**
     * Run the given submission callable. This must be a callable for a replace="all" submission.
     *
     * @param callable          callable run
     * @param response          response to write to if needed
     */
    public static void runDeferredSubmission(Callable<SubmissionResult> callable, ExternalContext.Response response) {
        // Run submission
        try {
            final SubmissionResult result = callable.call();
            if (result != null) {
                // Callable did not do all the work, completed it here
                try {
                    if (result.getReplacer() != null) {
                        // Replacer provided, perform replacement
                        if (result.getReplacer() instanceof AllReplacer)
                            AllReplacer.replace(result.getConnectionResult(), response);
                        else
                            assert result.getReplacer() instanceof NoneReplacer;
                    } else if (result.getThrowable() != null) {
                        // Propagate throwable, which might have come from a separate thread
                        throw new OXFException(result.getThrowable());
                    } else {
                        // Should not happen
                    }
                } finally {
                    result.close();
                }
            }
        } catch (Exception e) {
            // Something bad happened
            throw new OXFException(e);
        }
    }

    public Runnable sendSubmitDone(final ConnectionResult connectionResult) {
        return new Runnable() {
            public void run() {
                // After a submission, the context might have changed
                model.resetAndEvaluateVariables();

                Dispatch.dispatchEvent(new XFormsSubmitDoneEvent(XFormsModelSubmission.this, connectionResult));
            }
        };
    }

    private void sendSubmitError(Throwable throwable, SubmissionResult submissionResult) {

        // After a submission, the context might have changed
        model.resetAndEvaluateVariables();

        // Try to get error event from exception
        XFormsSubmitErrorEvent submitErrorEvent = null;
        if (throwable instanceof XFormsSubmissionException) {
            final XFormsSubmissionException submissionException = (XFormsSubmissionException) throwable;
            submitErrorEvent = submissionException.getXFormsSubmitErrorEvent();
        }

        // If no event obtained, create default event
        if (submitErrorEvent == null) {
            submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this,
                submitErrorEvent.XXFORMS_INTERNAL_ERROR(), submissionResult.getConnectionResult());
        }

        // Dispatch event
        submitErrorEvent.logThrowable(throwable);
        Dispatch.dispatchEvent(submitErrorEvent);
    }

    private void sendSubmitError(String resolvedActionOrResource, Throwable throwable) {

        // After a submission, the context might have changed
        model.resetAndEvaluateVariables();

        // Try to get error event from exception
        XFormsSubmitErrorEvent submitErrorEvent = null;
        if (throwable instanceof XFormsSubmissionException) {
            final XFormsSubmissionException submissionException = (XFormsSubmissionException) throwable;
            submitErrorEvent = submissionException.getXFormsSubmitErrorEvent();
        }

        // If no event obtained, create default event
        if (submitErrorEvent == null) {
            submitErrorEvent = new XFormsSubmitErrorEvent(XFormsModelSubmission.this, scala.Option.apply(resolvedActionOrResource),
                submitErrorEvent.XXFORMS_INTERNAL_ERROR(), 0);
        }

        // Dispatch event
        submitErrorEvent.logThrowable(throwable);
        Dispatch.dispatchEvent(submitErrorEvent);
    }

    public Replacer getReplacer(ConnectionResult connectionResult, SubmissionParameters p) throws IOException {

        // NOTE: This can be called from other threads so it must NOT modify the XFCD or submission

        if (connectionResult != null) {
            // Handle response
            final Replacer replacer;
            if (connectionResult.dontHandleResponse) {
                // Always return a replacer even if it does nothing, this way we don't have to deal with null
                replacer = new NoneReplacer(this, containingDocument);
            } else if (NetUtils.isSuccessCode(connectionResult.statusCode)) {
                // Successful response
                if (connectionResult.hasContent()) {
                    // There is a body

                    // Get replacer
                    if (p.isReplaceAll) {
                        replacer = new AllReplacer(this, containingDocument);
                    } else if (p.isReplaceInstance) {
                        replacer = new InstanceReplacer(this, containingDocument);
                    } else if (p.isReplaceText) {
                        replacer = new TextReplacer(this, containingDocument);
                    } else if (p.isReplaceNone) {
                        replacer = new NoneReplacer(this, containingDocument);
                    } else {
                        throw new XFormsSubmissionException(this, "xf:submission: invalid replace attribute: " + replace, "processing instance replacement",
                                new XFormsSubmitErrorEvent(this, XFormsSubmitErrorEvent.XXFORMS_INTERNAL_ERROR(), connectionResult));
                    }
                } else {
                    // There is no body, notify that processing is terminated
                    if (p.isReplaceInstance || p.isReplaceText) {
                        // XForms 1.1 says it is fine not to have a body, but in most cases you will want to know that
                        // no instance replacement took place
                        final IndentedLogger indentedLogger = getIndentedLogger();
                        indentedLogger.logWarning("", "instance or text replacement did not take place upon successful response because no body was provided.",
                                "submission id", getEffectiveId());
                    }

                    // "For a success response not including a body, submission processing concludes after dispatching
                    // xforms-submit-done"
                    replacer = new NoneReplacer(this, containingDocument);
                }
            } else if (connectionResult.statusCode == 302 || connectionResult.statusCode == 301) {
                // Got a redirect

                // Currently we don't know how to handle a redirect for replace != "all"
                if (!p.isReplaceAll)
                    throw new XFormsSubmissionException(this, "xf:submission for submission id: " + id + ", redirect code received with replace=\"" + replace + "\"", "processing submission response",
                            new XFormsSubmitErrorEvent(this, XFormsSubmitErrorEvent.RESOURCE_ERROR(), connectionResult));

                replacer = new RedirectReplacer(this, containingDocument);

            } else {
                // Error code received
                throw new XFormsSubmissionException(this, "xf:submission for submission id: " + id + ", error code received when submitting instance: " + connectionResult.statusCode, "processing submission response",
                        new XFormsSubmitErrorEvent(this, XFormsSubmitErrorEvent.RESOURCE_ERROR(), connectionResult));
            }

            return replacer;
        } else {
            return null;
        }
    }

    public class SubmissionParameters {

        // @replace attribute
        final boolean isReplaceAll = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_ALL);
        final boolean isReplaceInstance = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_INSTANCE);
        final boolean isReplaceText = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_TEXT);
        final boolean isReplaceNone = replace.equals(XFormsConstants.XFORMS_SUBMIT_REPLACE_NONE);

        // Current node for xf:submission and instance containing the node to submit
        NodeInfo refNodeInfo;
        XFormsInstance refInstance;
        Item submissionElementContextItem;

        final String resolvedMethod;
        final String actualHttpMethod;
        final String resolvedMediatype;
        final String serialization;
        final boolean serialize;

        final boolean resolvedValidate;
        final boolean resolvedRelevant;
        final boolean resolvedXXFormsCalculate;
        final boolean resolvedXXFormsUploads;
        final String resolvedXXFormsAnnotate;

        final boolean isHandlingClientGetAll;

        // XPath function library and namespace mappings
        final FunctionLibrary functionLibrary = XFormsContainingDocument.getFunctionLibrary();
        final NamespaceMapping namespaceMapping = container.getNamespaceMappings(submissionElement);

        // XPath context
        XPathCache.XPathContext xpathContext;

        final boolean isNoscript;
        final boolean isAllowDeferredSubmission;

        final boolean isPossibleDeferredSubmission;
        final boolean isDeferredSubmission;
        final boolean isDeferredSubmissionFirstPass;
        final boolean isDeferredSubmissionSecondPass;

        public void initializeXPathContext() {

            final BindingContext bindingContext; {
                model.resetAndEvaluateVariables();
                final XFormsContextStack contextStack = model.getContextStack();
                contextStack.pushBinding(getSubmissionElement(), getEffectiveId(), model.getResolutionScope());
                bindingContext = contextStack.getCurrentBindingContext();
            }

            final XFormsFunction.Context functionContext = model.getContextStack().getFunctionContext(getEffectiveId());

            refNodeInfo = (NodeInfo) bindingContext.getSingleItem();
            submissionElementContextItem = bindingContext.contextItem();
            // NOTE: Current instance may be null if the document submitted is not part of an instance
            refInstance = bindingContext.instanceOrNull();
            xpathContext = new XPathCache.XPathContext(namespaceMapping, bindingContext.getInScopeVariables(), functionLibrary, functionContext, null, getLocationData());
        }

        public SubmissionParameters(String eventName) {

            initializeXPathContext();

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

            if (!(refNodeInfo instanceof DocumentInfo || refNodeInfo.getNodeKind() == org.w3c.dom.Node.ELEMENT_NODE)) {
                throw new XFormsSubmissionException(XFormsModelSubmission.this, "xf:submission: single-node binding must refer to a document node or an element.", "getting submission single-node binding",
                        new XFormsSubmitErrorEvent(XFormsModelSubmission.this, XFormsSubmitErrorEvent.NO_DATA(), null));
            }

            {
                // Resolved method AVT
                final String resolvedMethodQName = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtMethod);
                resolvedMethod = Dom4jUtils.qNameToExplodedQName(Dom4jUtils.extractTextValueQName(namespaceMapping.mapping, resolvedMethodQName, true));

                // Get actual method based on the method attribute
                actualHttpMethod = XFormsSubmissionUtils.getActualHttpMethod(resolvedMethod);

                // Get mediatype
                resolvedMediatype = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo, avtMediatype);

                // Serialization
                serialization = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo, avtSerialization);
                if (serialization != null) {
                    serialize = !serialization.equals("none");
                } else {
                    // For backward compatibility only, support @serialize if there is no @serialization attribute (was in early XForms 1.1 draft)
                    serialize = !"false".equals(submissionElement.attributeValue("serialize"));
                }

                // Resolve validate and relevant AVTs
                final String resolvedValidateString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtValidate);
                // "The default value is "false" if the value of serialization is "none" and "true" otherwise"
                resolvedValidate = serialize && !"false".equals(resolvedValidateString);

                final String resolvedRelevantString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtRelevant);
                // "The default value is "false" if the value of serialization is "none" and "true" otherwise"
                resolvedRelevant = serialize && !"false".equals(resolvedRelevantString);

                final String resolvedCalculateString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtXXFormsCalculate);
                resolvedXXFormsCalculate = serialize && !"false".equals(resolvedCalculateString);

                final String resolvedUploadsString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtXXFormsUploads);
                resolvedXXFormsUploads = serialize && !"false".equals(resolvedUploadsString);

                final String resolvedAnnotateString = XFormsUtils.resolveAttributeValueTemplates(containingDocument, xpathContext, refNodeInfo , avtXXFormsAnnotate);
                resolvedXXFormsAnnotate = serialize ? resolvedAnnotateString : null;
            }

            isHandlingClientGetAll = XFormsProperties.isOptimizeGetAllSubmission(containingDocument) && actualHttpMethod.equals("GET")
                    && isReplaceAll
                    && (resolvedMediatype == null || !resolvedMediatype.startsWith(NetUtils.APPLICATION_SOAP_XML)) // can't let SOAP requests be handled by the browser
                    && avtXXFormsUsername == null // can't optimize if there are authentication credentials
                    && avtXXFormsTarget == null   // can't optimize if there is a target
                    && Dom4jUtils.elements(getSubmissionElement(), XFormsConstants.XFORMS_HEADER_QNAME).size() == 0; // can't optimize if there are headers specified

            // In noscript mode, or in "Ajax portlet" mode, there is no deferred submission process
            // Also don't allow deferred submissions when the incoming method is a GET. This is an indirect way of
            // allowing things like using the XForms engine to generate a PDF with an HTTP GET.

            // NOTE: Method can be null e.g. in a portlet render request
            final String method = NetUtils.getExternalContext().getRequest().getMethod();

            isNoscript = containingDocument.getStaticState().isNoscript();
            isAllowDeferredSubmission = !isNoscript && !(method != null && method.equals("GET"));

            isPossibleDeferredSubmission = isReplaceAll && !isHandlingClientGetAll && !containingDocument.isInitializing();
            isDeferredSubmission = isAllowDeferredSubmission && isPossibleDeferredSubmission;
            isDeferredSubmissionFirstPass = isDeferredSubmission && XFormsEvents.XFORMS_SUBMIT.equals(eventName);
            isDeferredSubmissionSecondPass = isDeferredSubmission && !isDeferredSubmissionFirstPass; // here we get XXFORMS_SUBMIT
        }
    }

    public class SecondPassParameters {

        // This mostly consists of AVTs that can be evaluated only during the second pass of the submission

        final String actionOrResource;
        final String mode;
        final String version;
        final String encoding;
        final String separator;
        final boolean indent;
        final boolean omitxmldeclaration;
        final Boolean standalone;
        final Connection.Credentials credentials;
        final boolean isReadonly;
        final boolean isCache;
        final long timeToLive;
        final boolean isHandleXInclude;

        final boolean isAsynchronous;

        public SecondPassParameters(SubmissionParameters p) {
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtActionOrResource);
                if (temp == null) {
                    // This can be null if, e.g. you have an AVT like resource="{()}"
                    throw new XFormsSubmissionException(XFormsModelSubmission.this, "xf:submission: mandatory resource or action evaluated to an empty sequence for attribute value: " + avtActionOrResource,
                            "resolving resource URI");
                }
                actionOrResource = NetUtils.encodeHRRI(temp, true);
                // TODO: see if we can resolve xml:base early to detect absolute URLs early as well
//                actionOrResource = XFormsUtils.resolveXMLBase(containingDocument, getSubmissionElement(), NetUtils.encodeHRRI(temp, true)).toString();
            }

            mode = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtMode);
            version = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtVersion);
            separator = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtSeparator);

            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtEncoding);
                encoding = (temp != null) ? temp : "UTF-8";
            }
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtIndent);
                indent = Boolean.valueOf(temp);
            }
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtOmitxmldeclaration);
                omitxmldeclaration = Boolean.valueOf(temp);
            }
            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtStandalone);
                standalone = (temp != null) ? Boolean.valueOf(temp) : null;
            }

            final String username = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsUsername);
            final String password = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsPassword);
            final String preemptiveAuthentication = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsPreemptiveAuthentication);
            final String domain = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsDomain);

            if (username == null || "".equals(username))
                credentials = null;
            else
                credentials = new Connection.Credentials(username, password, preemptiveAuthentication, domain);

            {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsReadonly);
                isReadonly = (temp != null) ? Boolean.valueOf(temp) : false;
            }

            if (avtXXFormsCache != null) {
                final String temp = XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsCache);
                // New attribute
                isCache = Boolean.valueOf(temp);
            } else {
                // For backward compatibility
                isCache = "application".equals(XFormsUtils.resolveAttributeValueTemplates(containingDocument,  p.xpathContext, p.refNodeInfo, avtXXFormsShared));
{'fraction_non_alphanumeric': 0.08118879526303803, 'fraction_numerical': 0.0014699488623423945, 'mean_word_length': 2.352990177362813, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 30}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7662200', 'n_tokens_mistral': 23146, 'n_tokens_neox': 21792, 'n_words': 6033}