private void cacheOrStore(XFormsContainingDocument containingDocument, boolean isInitialState) {
    if (XFormsDocumentCache.instance().isEnabled(containingDocument.getStaticState())) {
      // Cache the document
      indentedLogger.logDebug(LOG_TYPE, "Document cache enabled. Storing document in cache.");
      XFormsDocumentCache.instance().storeDocument(containingDocument);

      if (isInitialState && containingDocument.getStaticState().isServerStateHandling()) {
        // Also store document state (used by browser back and <xf:reset>)
        indentedLogger.logDebug(LOG_TYPE, "Storing initial document state.");
        storeDocumentState(containingDocument, isInitialState);
      }
    } else if (containingDocument.getStaticState().isServerStateHandling()) {
      // Directly store the document state
      indentedLogger.logDebug(LOG_TYPE, "Document cache disabled. Storing initial document state.");
      storeDocumentState(containingDocument, isInitialState);
    }
    LifecycleLogger.eventAssumingRequestJava(
        "xforms",
        "after cacheOrStore",
        new String[] {
          "document cache current size",
              Integer.toString(XFormsDocumentCache.instance().getCurrentSize()),
          "document cache max size", Integer.toString(XFormsDocumentCache.instance().getMaxSize())
        });
  }
  /**
   * Find or restore a document based on an incoming request.
   *
   * <p>Implementation: try cache first, then restore from store if not found.
   *
   * <p>If found in cache, document is removed from cache.
   *
   * @param parameters update parameters
   * @param isInitialState whether to return the initial state, otherwise return the current state
   * @param disableUpdates whether to disable updates (for recreating initial document upon browser
   *     back)
   * @return document, either from cache or from state information
   */
  public XFormsContainingDocument findOrRestoreDocument(
      RequestParameters parameters, boolean isInitialState, boolean disableUpdates) {

    // Try cache first unless the initial state is requested
    if (!isInitialState) {
      if (XFormsProperties.isCacheDocument()) {
        // Try to find the document in cache using the UUID
        // NOTE: If the document has cache.document="false", then it simply won't be found in the
        // cache, but
        // we can't know that the property is set to false before trying.
        final XFormsContainingDocument cachedDocument =
            XFormsDocumentCache.instance().takeDocument(parameters.getUUID());
        if (cachedDocument != null) {
          // Found in cache
          indentedLogger.logDebug(
              LOG_TYPE, "Document cache enabled. Returning document from cache.");
          return cachedDocument;
        }
        indentedLogger.logDebug(
            LOG_TYPE,
            "Document cache enabled. Document not found in cache. Retrieving state from store.");
      } else {
        indentedLogger.logDebug(LOG_TYPE, "Document cache disabled. Retrieving state from store.");
      }
    } else {
      indentedLogger.logDebug(
          LOG_TYPE, "Initial document state requested. Retrieving state from store.");
    }

    // Must recreate from store
    return createDocumentFromStore(parameters, isInitialState, disableUpdates);
  }
  private Object deserializeInstance(
      IndentedLogger indentedLogger,
      boolean isReadonly,
      boolean isHandleXInclude,
      ConnectionResult connectionResult)
      throws Exception {
    final Object resultingDocument;

    // Create resulting instance whether entire instance is replaced or not, because this:
    // 1. Wraps a Document within a DocumentInfo if needed
    // 2. Performs text nodes adjustments if needed
    try {
      if (!isReadonly) {
        // Resulting instance must not be read-only

        // TODO: What about configuring validation? And what default to choose?
        resultingDocument =
            TransformerUtils.readDom4j(
                connectionResult.getResponseInputStream(),
                connectionResult.resourceURI,
                isHandleXInclude,
                true);

        if (indentedLogger.isDebugEnabled())
          indentedLogger.logDebug("", "deserializing to mutable instance");
      } else {
        // Resulting instance must be read-only

        // TODO: What about configuring validation? And what default to choose?
        // NOTE: isApplicationSharedHint is always false when get get here.
        // isApplicationSharedHint="true" is handled above.
        resultingDocument =
            TransformerUtils.readTinyTree(
                XPathCache.getGlobalConfiguration(),
                connectionResult.getResponseInputStream(),
                connectionResult.resourceURI,
                isHandleXInclude,
                true);

        if (indentedLogger.isDebugEnabled())
          indentedLogger.logDebug("", "deserializing to read-only instance");
      }
    } catch (Exception e) {
      throw new XFormsSubmissionException(
          submission,
          e,
          "xforms:submission: exception while reading XML response.",
          "processing instance replacement",
          new XFormsSubmitErrorEvent(
              containingDocument,
              submission,
              XFormsSubmitErrorEvent.ErrorType.PARSE_ERROR,
              connectionResult));
    }

    return resultingDocument;
  }
  private XFormsContainingDocument createDocumentFromStore(
      RequestParameters parameters, boolean isInitialState, boolean disableUpdates) {

    final boolean isServerState = parameters.getEncodedClientStaticState() == null;

    final XFormsState xformsState;
    if (isServerState) {

      // State must be found by UUID in the store
      final ExternalContext externalContext = NetUtils.getExternalContext();
      final XFormsStateStore stateStore = XFormsStateStoreFactory.instance(externalContext);

      if (indentedLogger.isDebugEnabled())
        indentedLogger.logDebug(
            LOG_TYPE,
            "Getting document state from store.",
            "current cache size",
            Integer.toString(XFormsDocumentCache.instance().getCurrentSize()),
            "current store size",
            Long.toString(stateStore.getCurrentSize()),
            "max store size",
            Long.toString(stateStore.getMaxSize()));

      final ExternalContext.Session session =
          externalContext.getRequest().getSession(XFormsStateManager.FORCE_SESSION_CREATION);

      xformsState = stateStore.findState(session, parameters.getUUID(), isInitialState);

      if (xformsState == null) {
        // 2014-11-12: This means that 1. We had a valid incoming session and 2. we obtained a lock
        // on the
        // document, yet we didn't find it. This means that somehow state was not placed into or
        // expired from
        // the state store.
        throw new SessionExpiredException(
            "Unable to retrieve XForms engine state. Unable to process incoming request.");
      }
    } else {
      // State comes directly with request
      xformsState =
          new XFormsState(
              scala.Option.<String>apply(null),
              parameters.getEncodedClientStaticState(),
              DynamicState.apply(parameters.getEncodedClientDynamicState()));
    }

    // Create document
    final XFormsContainingDocument document =
        new XFormsContainingDocument(xformsState, disableUpdates);
    assert isServerState
        ? document.getStaticState().isServerStateHandling()
        : document.getStaticState().isClientStateHandling();
    return document;
  }
  /**
   * Called before sending an update response.
   *
   * <p>Implementation: update the document's change sequence.
   *
   * @param containingDocument containing document
   * @param ignoreSequence whether to ignore the sequence number
   */
  public void beforeUpdateResponse(
      XFormsContainingDocument containingDocument, boolean ignoreSequence) {
    if (containingDocument.isDirtySinceLastRequest()) {
      // The document is dirty
      indentedLogger.logDebug(LOG_TYPE, "Document is dirty. Generating new dynamic state.");
    } else {
      // The document is not dirty: no real encoding takes place here
      indentedLogger.logDebug(LOG_TYPE, "Document is not dirty. Keep existing dynamic state.");
    }

    // Tell the document to update its state
    if (!ignoreSequence) containingDocument.updateChangeSequence();
  }
  /**
   * Called after an update.
   *
   * @param containingDocument document
   * @param keepDocument whether to keep the document around
   */
  public void afterUpdate(XFormsContainingDocument containingDocument, boolean keepDocument) {
    if (keepDocument) {
      // Re-add document to the cache
      indentedLogger.logDebug(LOG_TYPE, "Keeping document in cache.");
      cacheOrStore(containingDocument, false);
    } else {
      // Don't re-add document to the cache
      indentedLogger.logDebug(LOG_TYPE, "Not keeping document in cache following error.");

      // Remove all information about this document from the session
      final String uuid = containingDocument.getUUID();
      removeCacheSessionListener(uuid);
      removeSessionDocument(uuid);
    }
  }
  public void execute(
      XFormsActionInterpreter actionInterpreter,
      PropertyContext propertyContext,
      String targetId,
      XFormsEventObserver eventObserver,
      Element actionElement,
      XBLBindings.Scope actionScope,
      boolean hasOverriddenContext,
      Item overriddenContext) {

    final XFormsContainingDocument containingDocument = actionInterpreter.getContainingDocument();

    final String controlIdAttributeValue =
        XFormsUtils.namespaceId(containingDocument, actionElement.attributeValue("control"));
    if (controlIdAttributeValue == null)
      throw new OXFException("Missing mandatory 'control' attribute on xforms:control element.");

    final String resolvedControlStaticId;
    {
      // Resolve AVT
      resolvedControlStaticId =
          actionInterpreter.resolveAVTProvideValue(
              propertyContext, actionElement, controlIdAttributeValue, true);
      if (resolvedControlStaticId == null) return;
    }

    final Object controlObject =
        actionInterpreter.resolveEffectiveControl(
            propertyContext, actionElement, resolvedControlStaticId);
    if (controlObject instanceof XFormsControl) {
      // Dispatch event to control object
      containingDocument.dispatchEvent(
          propertyContext,
          new XFormsFocusEvent(containingDocument, (XFormsEventTarget) controlObject));
    } else {
      // "If there is a null search result for the target object and the source object is an XForms
      // action such as
      // dispatch, send, setfocus, setindex or toggle, then the action is terminated with no
      // effect."
      final IndentedLogger indentedLogger = actionInterpreter.getIndentedLogger();
      if (indentedLogger.isDebugEnabled())
        indentedLogger.logDebug(
            "xforms:setfocus",
            "control does not refer to an existing control element, ignoring action",
            "control id",
            resolvedControlStaticId);
    }
  }
  private void cacheOrStore(XFormsContainingDocument containingDocument, boolean isInitialState) {
    if (XFormsDocumentCache.instance().isEnabled(containingDocument.getStaticState())) {
      // Cache the document
      indentedLogger.logDebug(LOG_TYPE, "Document cache enabled. Storing document in cache.");
      XFormsDocumentCache.instance().storeDocument(containingDocument);

      if (isInitialState && containingDocument.getStaticState().isServerStateHandling()) {
        // Also store document state (used by browser back and <xf:reset>)
        indentedLogger.logDebug(LOG_TYPE, "Storing initial document state.");
        storeDocumentState(containingDocument, isInitialState);
      }

    } else if (containingDocument.getStaticState().isServerStateHandling()) {
      // Directly store the document state
      indentedLogger.logDebug(LOG_TYPE, "Document cache disabled. Storing initial document state.");
      storeDocumentState(containingDocument, isInitialState);
    }
  }
  private Stage2CacheableState readStaticState(
      PipelineContext pipelineContext,
      ExternalContext externalContext,
      IndentedLogger indentedLogger,
      XFormsStaticState[] staticState) {

    final StaticStateBits staticStateBits =
        new StaticStateBits(pipelineContext, externalContext, indentedLogger, null);

    {
      final XFormsStaticState cachedState =
          XFormsStaticStateCache.instance()
              .getDocument(pipelineContext, staticStateBits.staticStateDigest);
      if (cachedState != null && cachedState.getMetadata().checkBindingsIncludes()) {
        // Found static state in cache
        indentedLogger.logDebug("", "found up-to-date static state by digest in cache");

        staticState[0] = cachedState;
      } else {
        // Not found static state in cache OR it is out of date, create and initialize static state
        // object
        // NOTE: In out of date case, could clone static state and reprocess instead?
        if (cachedState != null)
          indentedLogger.logDebug("", "found out-of-date static state by digest in cache");
        else indentedLogger.logDebug("", "did not find static state by digest in cache");

        staticState[0] =
            new XFormsStaticState(
                pipelineContext,
                staticStateBits.staticStateDocument,
                staticStateBits.staticStateDigest,
                staticStateBits.metadata);

        // Store in cache
        XFormsStaticStateCache.instance().storeDocument(pipelineContext, staticState[0]);
      }
    }

    // Update input dependencies object
    return new Stage2CacheableState(
        staticStateBits.annotatedTemplate, staticStateBits.staticStateDigest);
  }
  private void addCacheSessionListener(final String uuid) {

    final ExternalContext.Session session =
        NetUtils.getSession(XFormsStateManager.FORCE_SESSION_CREATION);

    final Map<String, Object> sessionAttributes =
        session.getAttributesMap(ExternalContext.Session.APPLICATION_SCOPE);
    final String listenerSessionKey = getListenerSessionKey(uuid);
    if (sessionAttributes.get(listenerSessionKey) == null) {

      // Remove from cache when session expires
      final ExternalContext.Session.SessionListener listener =
          new ExternalContext.Session.SessionListener() {
            public void sessionDestroyed() {
              indentedLogger.logDebug(
                  LOG_TYPE, "Removing document from cache following session expiration.");
              // NOTE: This will call onRemoved() on the document, and onRemovedFromCache() on
              // XFormsStateManager
              XFormsDocumentCache.instance().removeDocument(uuid);
            }
          };

      // Add listener
      try {
        session.addListener(listener);
      } catch (IllegalStateException e) {
        indentedLogger.logInfo(LOG_TYPE, "Unable to add session listener: " + e.getMessage());
        // NOTE: This will call onRemoved() on the document, and onRemovedFromCache() on
        // XFormsStateManager
        XFormsDocumentCache.instance().removeDocument(uuid); // remove immediately
        throw e;
      }
      // Remember, in session, mapping (UUID -> session listener)
      sessionAttributes.put(listenerSessionKey, listener);
    }
  }
  public static void outputResponseDocument(
      final PipelineContext pipelineContext,
      final ExternalContext externalContext,
      final IndentedLogger indentedLogger,
      final SAXStore annotatedDocument,
      final XFormsContainingDocument containingDocument,
      final XMLReceiver xmlReceiver)
      throws SAXException, IOException {

    final List<XFormsContainingDocument.Load> loads = containingDocument.getLoadsToRun();
    if (containingDocument.isGotSubmissionReplaceAll()) {
      // 1. Got a submission with replace="all"

      // NOP: Response already sent out by a submission
      // TODO: modify XFormsModelSubmission accordingly
      indentedLogger.logDebug("", "handling response for submission with replace=\"all\"");
    } else if (loads != null && loads.size() > 0) {
      // 2. Got at least one xforms:load

      // Send redirect out

      // Get first load only
      final XFormsContainingDocument.Load load = loads.get(0);

      // Send redirect
      final String redirectResource = load.getResource();
      indentedLogger.logDebug(
          "", "handling redirect response for xforms:load", "url", redirectResource);
      // Set isNoRewrite to true, because the resource is either a relative path or already contains
      // the servlet context
      externalContext.getResponse().sendRedirect(redirectResource, null, false, false, true);

      // Still send out a null document to signal that no further processing must take place
      XMLUtils.streamNullDocument(xmlReceiver);
    } else {
      // 3. Regular case: produce an XHTML document out

      final ElementHandlerController controller = new ElementHandlerController();

      // Register handlers on controller (the other handlers are registered by the body handler)
      {
        controller.registerHandler(
            XHTMLHeadHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "head");
        controller.registerHandler(
            XHTMLBodyHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "body");

        // Register a handler for AVTs on HTML elements
        final boolean hostLanguageAVTs =
            XFormsProperties
                .isHostLanguageAVTs(); // TODO: this should be obtained per document, but we only
        // know about this in the extractor
        if (hostLanguageAVTs) {
          controller.registerHandler(
              XXFormsAttributeHandler.class.getName(),
              XFormsConstants.XXFORMS_NAMESPACE_URI,
              "attribute");
          controller.registerHandler(
              XHTMLElementHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI);
        }

        // Swallow XForms elements that are unknown
        controller.registerHandler(
            NullHandler.class.getName(), XFormsConstants.XFORMS_NAMESPACE_URI);
        controller.registerHandler(
            NullHandler.class.getName(), XFormsConstants.XXFORMS_NAMESPACE_URI);
        controller.registerHandler(NullHandler.class.getName(), XFormsConstants.XBL_NAMESPACE_URI);
      }

      // Set final output
      controller.setOutput(new DeferredXMLReceiverImpl(xmlReceiver));
      // Set handler context
      controller.setElementHandlerContext(
          new HandlerContext(
              controller, pipelineContext, containingDocument, externalContext, null));
      // Process the entire input
      annotatedDocument.replay(
          new ExceptionWrapperXMLReceiver(controller, "converting XHTML+XForms document to XHTML"));
    }

    containingDocument.afterInitialResponse();
  }
  public void execute(
      XFormsActionInterpreter actionInterpreter,
      Element actionElement,
      Scope actionScope,
      boolean hasOverriddenContext,
      Item overriddenContext) {

    final IndentedLogger indentedLogger = actionInterpreter.indentedLogger();
    final XFormsContainingDocument containingDocument = actionInterpreter.containingDocument();
    final XFormsContextStack contextStack = actionInterpreter.actionXPathContext();

    final String atAttribute = actionElement.attributeValue("at");
    final String originAttribute = actionElement.attributeValue("origin");
    final String contextAttribute = actionElement.attributeValue(XFormsConstants.CONTEXT_QNAME);

    // Extension: allow position to be an AVT
    final String resolvedPositionAttribute =
        actionInterpreter.resolveAVT(actionElement, "position");

    // Extension: xxf:default="true" AVT requires that recalculate apply default values on the
    // inserted nodes.
    final boolean setRequireDefaultValues =
        "true"
            .equals(
                actionInterpreter.resolveAVT(
                    actionElement, XFormsConstants.XXFORMS_DEFAULTS_QNAME));

    // "2. The Node Set Binding node-set is determined."
    final List<Item> collectionToBeUpdated;
    {
      final BindingContext currentBindingContext = contextStack.getCurrentBindingContext();
      collectionToBeUpdated =
          currentBindingContext.newBind()
              ? currentBindingContext.nodeset()
              : XFormsConstants.EMPTY_ITEM_LIST;
    }
    final boolean isEmptyNodesetBinding =
        collectionToBeUpdated == null || collectionToBeUpdated.size() == 0;

    // "1. The insert context is determined."

    // "The insert action is terminated with no effect if [...] a. The context attribute is not
    // given and the Node
    // Set Binding node-set is the empty node-set."
    if (contextAttribute == null && isEmptyNodesetBinding) {
      if (indentedLogger.isDebugEnabled())
        indentedLogger.logDebug("xf:insert", "context is empty, terminating");
      return;
    }

    // Handle insert context (with @context attribute)
    final Item insertContextItem;
    if (hasOverriddenContext) {
      // "If the result is an empty nodeset or not a nodeset, then the insert action is terminated
      // with no effect. "
      if (overriddenContext == null || !(overriddenContext instanceof NodeInfo)) {
        if (indentedLogger.isDebugEnabled())
          indentedLogger.logDebug(
              "xf:insert", "overridden context is an empty nodeset or not a nodeset, terminating");
        return;
      } else {
        insertContextItem = overriddenContext;
      }
    } else {
      insertContextItem = contextStack.getCurrentBindingContext().getSingleItem();
    }

    // "The insert action is terminated with no effect if [...] b. The context attribute is given,
    // the insert
    // context does not evaluate to an element node and the Node Set Binding node-set is the empty
    // node-set."
    // NOTE: In addition we support inserting into a context which is a document node
    if (contextAttribute != null
        && isEmptyNodesetBinding
        && !DataModel.isElement(insertContextItem)
        && !DataModel.isDocument(insertContextItem)) {
      if (indentedLogger.isDebugEnabled())
        indentedLogger.logDebug(
            "xf:insert",
            "insert context is not an element node and binding node-set is empty, terminating");
      return;
    }

    // "3. The origin node-set is determined."
    final List<Item> originObjects;
    {
      if (originAttribute == null) {
        originObjects = null;
      } else {
        // There is an @origin attribute

        // "If the origin attribute is given, the origin node-set is the result of the evaluation of
        // the
        // origin attribute in the insert context."

        originObjects =
            actionInterpreter.evaluateKeepItems(
                actionElement, Collections.singletonList(insertContextItem), 1, originAttribute);

        // "The insert action is terminated with no effect if the origin node-set is the empty
        // node-set."
        if (originObjects.size() == 0) {
          if (indentedLogger.isDebugEnabled())
            indentedLogger.logDebug("xf:insert", "origin node-set is empty, terminating");
          return;
        }
      }
    }

    // "4. The insert location node is determined."
    int insertionIndex;
    {
      if (isEmptyNodesetBinding) {
        // "If the Node Set Binding node-set empty, then this attribute is ignored"
        insertionIndex = 0;
      } else if (atAttribute == null) {
        // "If the attribute is not given, then the default is the size of the Node Set Binding
        // node-set"
        insertionIndex = collectionToBeUpdated.size();
      } else {
        // "a. The evaluation context node is the first node in document order from the Node Set
        // Binding
        // node-set, the context size is the size of the Node Set Binding node-set, and the context
        // position is 1."

        // "b. The return value is processed according to the rules of the XPath function round()"
        final String insertionIndexString =
            actionInterpreter.evaluateAsString(
                actionElement, collectionToBeUpdated, 1, "round(" + atAttribute + ")");

        // "c. If the result is in the range 1 to the Node Set Binding node-set size, then the
        // insert
        // location is equal to the result. If the result is non-positive, then the insert location
        // is
        // 1. Otherwise, the result is NaN or exceeds the Node Set Binding node-set size, so the
        // insert
        // location is the Node Set Binding node-set size."

        // Don't think we will get NaN with XPath 2.0...
        insertionIndex =
            "NaN".equals(insertionIndexString)
                ? collectionToBeUpdated.size()
                : Integer.parseInt(insertionIndexString);

        // Adjust index to be in range
        if (insertionIndex > collectionToBeUpdated.size())
          insertionIndex = collectionToBeUpdated.size();

        if (insertionIndex < 1) insertionIndex = 1;
      }
    }

    final String normalizedPosition;
    {
      if (resolvedPositionAttribute == null) {
        // Default value
        normalizedPosition = "after";
      } else if ("after".equals(resolvedPositionAttribute)
          || "before".equals(resolvedPositionAttribute)) {
        // Specified value
        normalizedPosition = resolvedPositionAttribute;
      } else {
        // Invalid value
        if (indentedLogger.isInfoEnabled())
          indentedLogger.logWarning(
              "xf:insert",
              "invalid position attribute, defaulting to \"after\"",
              "value",
              resolvedPositionAttribute);

        normalizedPosition = "after";
      }
    }

    doInsert(
        containingDocument,
        indentedLogger,
        normalizedPosition,
        collectionToBeUpdated,
        (NodeInfo) insertContextItem,
        originObjects,
        insertionIndex,
        true,
        true,
        setRequireDefaultValues);
  }
  private XFormsContainingDocument createDocumentFromStore(
      RequestParameters parameters, boolean isInitialState, boolean disableUpdates) {

    final boolean isServerState = parameters.getEncodedClientStaticState() == null;

    final XFormsState xformsState;
    if (isServerState) {
      // State must be found by UUID in the store
      final ExternalContext externalContext = NetUtils.getExternalContext();
      final XFormsStateStore stateStore = XFormsStateStoreFactory.instance(externalContext);

      if (indentedLogger.isDebugEnabled())
        indentedLogger.logDebug(
            LOG_TYPE,
            "Getting document state from store.",
            "current cache size",
            Integer.toString(XFormsDocumentCache.instance().getCurrentSize()),
            "current store size",
            Long.toString(stateStore.getCurrentSize()),
            "max store size",
            Long.toString(stateStore.getMaxSize()));

      final ExternalContext.Session session =
          externalContext.getRequest().getSession(XFormsStateManager.FORCE_SESSION_CREATION);

      xformsState = stateStore.findState(session, parameters.getUUID(), isInitialState);

      if (xformsState == null) {
        // Oops, we couldn't find the state in the store

        final String UNABLE_TO_RETRIEVE_XFORMS_STATE_MESSAGE =
            "Unable to retrieve XForms engine state.";
        final String PLEASE_RELOAD_PAGE_MESSAGE =
            "Please reload the current page. Note that you will lose any unsaved changes.";

        // Produce exception
        final ExternalContext.Session currentSession =
            externalContext.getRequest().getSession(false);
        final String message;
        if (currentSession == null || currentSession.isNew()) {
          // This means that no session is currently existing, or a session exists but it is newly
          // created
          message = "Your session has expired. " + PLEASE_RELOAD_PAGE_MESSAGE;
        } else {
          // There is a session and it is still known by the client
          message = UNABLE_TO_RETRIEVE_XFORMS_STATE_MESSAGE + " " + PLEASE_RELOAD_PAGE_MESSAGE;
        }
        indentedLogger.logError("", message);
        throw new OXFException(message);
      }
    } else {
      // State comes directly with request
      xformsState =
          new XFormsState(
              scala.Option.<String>apply(null),
              parameters.getEncodedClientStaticState(),
              DynamicState.apply(parameters.getEncodedClientDynamicState()));
    }

    // Create document
    final XFormsContainingDocument document =
        new XFormsContainingDocument(xformsState, disableUpdates);
    assert isServerState
        ? document.getStaticState().isServerStateHandling()
        : document.getStaticState().isClientStateHandling();
    return document;
  }
  private void doIt(
      final PipelineContext pipelineContext,
      XMLReceiver xmlReceiver,
      final URIProcessorOutputImpl processorOutput,
      String outputName) {

    final ExternalContext externalContext = XFormsUtils.getExternalContext(pipelineContext);
    final IndentedLogger indentedLogger =
        XFormsContainingDocument.getIndentedLogger(
            XFormsToXHTML.logger, XFormsServer.getLogger(), LOGGING_CATEGORY);

    // ContainingDocument and XFormsState created below
    final XFormsContainingDocument[] containingDocument = new XFormsContainingDocument[1];
    final boolean[] cachedStatus = new boolean[] {false};

    final Stage2CacheableState stage2CacheableState;
    if (TEST_STATE == null) {

      // Read and try to cache the complete XForms+XHTML document with annotations
      stage2CacheableState =
          (Stage2CacheableState)
              readCacheInputAsObject(
                  pipelineContext,
                  getInputByName(INPUT_ANNOTATED_DOCUMENT),
                  new CacheableInputReader() {
                    public Object read(
                        PipelineContext pipelineContext, ProcessorInput processorInput) {

                      // Compute annotated XForms document + static state document
                      final Stage1CacheableState stage1CacheableState = new Stage1CacheableState();
                      final Stage2CacheableState stage2CacheableState;
                      final XFormsStaticState[] staticState = new XFormsStaticState[1];
                      {
                        // Store dependencies container in state before reading
                        ((Stage2TransientState) XFormsToXHTML.this.getState(pipelineContext))
                                .stage1CacheableState =
                            stage1CacheableState;

                        // Read static state from input
                        stage2CacheableState =
                            readStaticState(
                                pipelineContext, externalContext, indentedLogger, staticState);
                      }

                      // Create containing document and initialize XForms engine
                      // NOTE: Create document here so we can do appropriate analysis of caching
                      // dependencies
                      final XFormsURIResolver uriResolver =
                          new XFormsURIResolver(
                              XFormsToXHTML.this,
                              processorOutput,
                              pipelineContext,
                              INPUT_ANNOTATED_DOCUMENT,
                              URLGenerator.DEFAULT_HANDLE_XINCLUDE);
                      containingDocument[0] =
                          new XFormsContainingDocument(
                              pipelineContext,
                              staticState[0],
                              stage2CacheableState.getAnnotatedTemplate(),
                              uriResolver);

                      // Gather set caching dependencies
                      gatherInputDependencies(
                          pipelineContext,
                          containingDocument[0],
                          indentedLogger,
                          stage1CacheableState);

                      return stage2CacheableState;
                    }

                    @Override
                    public void foundInCache() {
                      cachedStatus[0] = true;
                    }
                  },
                  false);

      TEST_STATE = DO_TEST_STATE ? stage2CacheableState : null;

    } else {
      stage2CacheableState = TEST_STATE;
    }

    try {
      // Create containing document if not done yet
      if (containingDocument[0] == null) {
        assert cachedStatus[0];
        // In this case, we found the static state digest and more in the cache, but we must now
        // create a new XFormsContainingDocument from this information
        indentedLogger.logDebug(
            "",
            "annotated document and static state digest obtained from cache",
            "digest",
            stage2CacheableState.getStaticStateDigest());

        final XFormsStaticState staticState;
        {
          final XFormsStaticState cachedState =
              XFormsStaticStateCache.instance()
                  .getDocument(pipelineContext, stage2CacheableState.getStaticStateDigest());
          if (cachedState != null && cachedState.getMetadata().checkBindingsIncludes()) {
            // Found static state in cache
            indentedLogger.logDebug("", "found up-to-date static state by digest in cache");

            staticState = cachedState;
          } else {
            // Not found static state in cache OR it is out of date, create static state from input
            // NOTE: In out of date case, could clone static state and reprocess instead?
            if (cachedState != null)
              indentedLogger.logDebug("", "found out-of-date static state by digest in cache");
            else indentedLogger.logDebug("", "did not find static state by digest in cache");

            final StaticStateBits staticStateBits =
                new StaticStateBits(
                    pipelineContext,
                    externalContext,
                    indentedLogger,
                    stage2CacheableState.getStaticStateDigest());
            staticState =
                new XFormsStaticState(
                    pipelineContext,
                    staticStateBits.staticStateDocument,
                    stage2CacheableState.getStaticStateDigest(),
                    staticStateBits.metadata);

            // Store in cache
            XFormsStaticStateCache.instance().storeDocument(pipelineContext, staticState);
          }
        }

        final XFormsURIResolver uriResolver =
            new XFormsURIResolver(
                XFormsToXHTML.this,
                processorOutput,
                pipelineContext,
                INPUT_ANNOTATED_DOCUMENT,
                URLGenerator.DEFAULT_HANDLE_XINCLUDE);
        containingDocument[0] =
            new XFormsContainingDocument(
                pipelineContext,
                staticState,
                stage2CacheableState.getAnnotatedTemplate(),
                uriResolver);
      } else {
        assert !cachedStatus[0];
        indentedLogger.logDebug(
            "", "annotated document and static state digest not obtained from cache.");
      }

      // Output resulting document
      if (outputName.equals("document")) {
        // Normal case where we output XHTML
        outputResponseDocument(
            pipelineContext,
            externalContext,
            indentedLogger,
            stage2CacheableState.getAnnotatedTemplate(),
            containingDocument[0],
            xmlReceiver);
      } else {
        // Output in test mode
        testOutputResponseState(
            pipelineContext, containingDocument[0], indentedLogger, xmlReceiver);
      }

      // Notify state manager
      XFormsStateManager.instance().afterInitialResponse(pipelineContext, containingDocument[0]);

    } catch (Throwable e) {
      indentedLogger.logDebug("", "throwable caught during initialization.");
      throw new OXFException(e);
    }
  }
  public Runnable replace(
      final ConnectionResult connectionResult,
      XFormsModelSubmission.SubmissionParameters p,
      XFormsModelSubmission.SecondPassParameters p2) {

    // Set new instance document to replace the one submitted

    final XFormsInstance replaceInstanceNoTargetref =
        submission.findReplaceInstanceNoTargetref(p.refInstance);
    if (replaceInstanceNoTargetref == null) {

      // Replacement instance or node was specified but not found
      //
      // Not sure what's the right thing to do with 1.1, but this could be done
      // as part of the model's static analysis if the instance value is not
      // obtained through AVT, and dynamically otherwise.
      //
      // Another option would be to dispatch, at runtime, an xxforms-binding-error event.
      // xforms-submit-error is
      // consistent with targetref, so might be better.

      throw new XFormsSubmissionException(
          submission,
          "instance attribute doesn't point to an existing instance for replace=\"instance\".",
          "processing instance attribute",
          new XFormsSubmitErrorEvent(
              containingDocument,
              submission,
              XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
              connectionResult));
    } else {

      final NodeInfo destinationNodeInfo =
          submission.evaluateTargetRef(
              p.xpathContext, replaceInstanceNoTargetref, p.submissionElementContextItem);

      if (destinationNodeInfo == null) {
        // Throw target-error

        // XForms 1.1: "If the processing of the targetref attribute fails,
        // then submission processing ends after dispatching the event
        // xforms-submit-error with an error-type of target-error."
        throw new XFormsSubmissionException(
            submission,
            "targetref attribute doesn't point to an element for replace=\"instance\".",
            "processing targetref attribute",
            new XFormsSubmitErrorEvent(
                containingDocument,
                submission,
                XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
                connectionResult));
      }

      // This is the instance which is effectively going to be updated
      final XFormsInstance updatedInstance =
          containingDocument.getInstanceForNode(destinationNodeInfo);
      if (updatedInstance == null) {
        throw new XFormsSubmissionException(
            submission,
            "targetref attribute doesn't point to an element in an existing instance for replace=\"instance\".",
            "processing targetref attribute",
            new XFormsSubmitErrorEvent(
                containingDocument,
                submission,
                XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
                connectionResult));
      }

      // Whether the destination node is the root element of an instance
      final boolean isDestinationRootElement =
          updatedInstance.instanceRoot().isSameNodeInfo(destinationNodeInfo);
      if (p2.isReadonly && !isDestinationRootElement) {
        // Only support replacing the root element of an instance when using a shared instance
        throw new XFormsSubmissionException(
            submission,
            "targetref attribute must point to instance root element when using read-only instance replacement.",
            "processing targetref attribute",
            new XFormsSubmitErrorEvent(
                containingDocument,
                submission,
                XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
                connectionResult));
      }

      final IndentedLogger detailsLogger = getDetailsLogger(p, p2);

      // Obtain root element to insert
      if (detailsLogger.isDebugEnabled())
        detailsLogger.logDebug(
            "",
            p2.isReadonly
                ? "replacing instance with read-only instance"
                : "replacing instance with mutable instance",
            "instance",
            updatedInstance.getEffectiveId());

      // Perform insert/delete. This will dispatch xforms-insert/xforms-delete events.
      // "the replacement is performed by an XForms action that performs some
      // combination of node insertion and deletion operations that are
      // performed by the insert action (10.3 The insert Element) and the
      // delete action"

      // NOTE: As of 2009-03-18 decision, XForms 1.1 specifies that deferred event handling flags
      // are set instead of
      // performing RRRR directly.
      final DocumentInfo newDocumentInfo =
          wrappedDocumentInfo != null
              ? wrappedDocumentInfo
              : XFormsInstance.createDocumentInfo(
                  resultingDocumentOrDocumentInfo, updatedInstance.instance().exposeXPathTypes());

      if (isDestinationRootElement) {
        // Optimized insertion for instance root element replacement

        // Update the instance (this also marks it as modified)
        updatedInstance.update(
            Option.<InstanceCaching>apply(instanceCaching), newDocumentInfo, p2.isReadonly);

        final NodeInfo newDocumentRootElement = updatedInstance.instanceRoot();

        // Call this directly, since we are not using insert/delete here
        updatedInstance.model().markStructuralChange(updatedInstance);

        // Dispatch xforms-delete event
        // NOTE: Do NOT dispatch so we are compatible with the regular root element replacement
        // (see below). In the future, we might want to dispatch this, especially if
        // XFormsInsertAction dispatches xforms-delete when removing the root element
        // updatedInstance.getXBLContainer(containingDocument).dispatchEvent(pipelineContext, new
        // XFormsDeleteEvent(updatedInstance, Collections.singletonList(destinationNodeInfo), 1));

        // Dispatch xforms-insert event
        // NOTE: use the root node as insert location as it seems to make more sense than pointing
        // to the earlier root element
        Dispatch.dispatchEvent(
            new XFormsInsertEvent(
                containingDocument,
                updatedInstance,
                Collections.singletonList((Item) newDocumentRootElement),
                null,
                newDocumentRootElement.getDocumentRoot(),
                "after"));

      } else {
        // Generic insertion

        updatedInstance.markModified();
        final NodeInfo newDocumentRootElement = DataModel.firstChildElement(newDocumentInfo);

        final List<NodeInfo> destinationCollection = Collections.singletonList(destinationNodeInfo);

        // Perform the insertion

        // Insert before the target node, so that the position of the inserted node
        // wrt its parent does not change after the target node is removed
        // This will also mark a structural change
        XFormsInsertAction.doInsert(
            containingDocument,
            detailsLogger,
            "before",
            destinationCollection,
            destinationNodeInfo.getParent(),
            Collections.singletonList(newDocumentRootElement),
            1,
            false,
            true);

        if (!destinationNodeInfo
            .getParent()
            .isSameNodeInfo(destinationNodeInfo.getDocumentRoot())) {
          // The node to replace is NOT a root element

          // Perform the deletion of the selected node
          XFormsDeleteAction.doDelete(
              containingDocument, detailsLogger, destinationCollection, 1, true);
        }

        // Update model instance
        // NOTE: The inserted node NodeWrapper.index might be out of date at this point because:
        // * doInsert() dispatches an event which might itself change the instance
        // * doDelete() does as well
        // Does this mean that we should check that the node is still where it should be?
      }

      // Dispatch xforms-submit-done
      return submission.sendSubmitDone(connectionResult);
    }
  }
  public static List<NodeInfo> doInsert(
      XFormsContainingDocument containingDocument,
      IndentedLogger indentedLogger,
      String positionAttribute,
      List collectionToBeUpdated,
      NodeInfo insertContextNodeInfo,
      List<Item> originItems,
      int insertionIndex,
      boolean doClone,
      boolean doDispatch,
      boolean requireDefaultValues) {

    final boolean isEmptyNodesetBinding =
        collectionToBeUpdated == null || collectionToBeUpdated.size() == 0;

    final NodeInfo insertLocationNodeInfo;
    if (isEmptyNodesetBinding) {
      // Insert INTO a node

      // "If the Node Set Binding node-set is not specified or empty, the insert location node is
      // the insert
      // context node."

      // "a. If the Node Set Binding node-set is not specified or empty, the target location depends
      // on the
      // node type of the cloned node. If the cloned node is an attribute, then the target location
      // is before
      // the first attribute of the insert location node. If the cloned node is not an attribute,
      // then the
      // target location is before the first child of the insert location node."

      insertLocationNodeInfo = insertContextNodeInfo;
    } else {
      // Insert BEFORE or AFTER a node
      insertLocationNodeInfo = (NodeInfo) collectionToBeUpdated.get(insertionIndex - 1);
    }

    // Identify the instance that actually changes
    final XFormsInstance modifiedInstanceOrNull =
        (containingDocument != null)
            ? containingDocument.getInstanceForNode(insertLocationNodeInfo)
            : null;

    // NOTE: The check on `hasAnyCalculationBind` is not optimal: we should check whether
    // specifically there are any xxf:default which can touch this
    // instance, ideally.
    // NOTE: We do this test here so that we don't unnecessarily annotate nodes.
    final boolean applyDefaults =
        requireDefaultValues
            && modifiedInstanceOrNull != null
            && modifiedInstanceOrNull.model().staticModel().hasDefaultValueBind()
            && containingDocument
                .getXPathDependencies()
                .hasAnyCalculationBind(
                    modifiedInstanceOrNull.model().staticModel(),
                    modifiedInstanceOrNull.getPrefixedId());

    // "3. The origin node-set is determined."
    // "5. Each node in the origin node-set is cloned in the order it appears in the origin
    // node-set."
    final List<Node> sourceNodes;
    final List<Node> clonedNodes;
    {
      final List<Node> clonedNodesTemp;
      if (originItems == null) {
        // There are no explicitly specified origin objects, use node from Node Set Binding node-set

        // "If the origin attribute is not given and the Node Set Binding node-set is empty, then
        // the origin
        // node-set is the empty node-set. [...] The insert action is terminated with no effect if
        // the
        // origin node-set is the empty node-set."

        if (isEmptyNodesetBinding) {
          if (indentedLogger != null && indentedLogger.isDebugEnabled())
            indentedLogger.logDebug(
                "xf:insert", "origin node-set from node-set binding is empty, terminating");
          return Collections.emptyList();
        }

        // "Otherwise, if the origin attribute is not given, then the origin node-set consists of
        // the last
        // node of the Node Set Binding node-set."
        final Node singleSourceNode =
            XFormsUtils.getNodeFromNodeInfoConvert(
                (NodeInfo) collectionToBeUpdated.get(collectionToBeUpdated.size() - 1));
        // TODO: check namespace handling might be incorrect. Should use
        // copyElementCopyParentNamespaces() instead?
        final Node singleClonedNode = Dom4jUtils.createCopy(singleSourceNode);

        sourceNodes = Collections.singletonList(singleSourceNode);
        clonedNodesTemp = Collections.singletonList(singleClonedNode);
      } else {
        // There are explicitly specified origin objects

        // "The insert action is terminated with no effect if the origin node-set is the empty
        // node-set."
        if (originItems.size() == 0) {
          if (indentedLogger != null && indentedLogger.isDebugEnabled())
            indentedLogger.logDebug("xf:insert", "origin node-set is empty, terminating");
          return Collections.emptyList();
        }

        // "Each node in the origin node-set is cloned in the order it appears in the origin
        // node-set."

        sourceNodes = new ArrayList<Node>(originItems.size()); // set to max possible size
        clonedNodesTemp = new ArrayList<Node>(originItems.size());

        for (final Object currentObject : originItems) {
          if (currentObject instanceof NodeInfo) {
            // This is the regular case covered by XForms 1.1 / XPath 1.0

            // NOTE: Don't clone nodes if doClone == false
            final Node sourceNode =
                XFormsUtils.getNodeFromNodeInfoConvert((NodeInfo) currentObject);
            final Node clonedNode =
                doClone
                    ? (sourceNode instanceof Element)
                        ? ((Element) sourceNode).createCopy()
                        : (Node) sourceNode.clone()
                    : sourceNode;

            sourceNodes.add(sourceNode);
            clonedNodesTemp.add(clonedNode);

          } else if (currentObject instanceof AtomicValue) {
            // This is an extension: support sequences containing atomic values

            // Convert the result to a text node
            final String stringValue = ((Item) currentObject).getStringValue();
            final Text textNode = DocumentFactory.createText(stringValue);

            sourceNodes.add(
                null); // there is no source node for this cloned node, it's a source item
            clonedNodesTemp.add(textNode);
          } else throw new IllegalStateException();
        }
      }

      // Remove instance data from cloned nodes and perform Document node adjustment
      for (int i = 0; i < clonedNodesTemp.size(); i++) {
        final Node clonedNodeTemp = clonedNodesTemp.get(i);

        if (clonedNodeTemp instanceof Element) {
          // Element node
          if (applyDefaults) InstanceDataOps.setRequireDefaultValueRecursively(clonedNodeTemp);
          else InstanceDataOps.removeRecursively(clonedNodeTemp);
          clonedNodeTemp.detach();
        } else if (clonedNodeTemp instanceof Attribute) {
          // Attribute node
          if (applyDefaults) InstanceDataOps.setRequireDefaultValueRecursively(clonedNodeTemp);
          else InstanceDataOps.removeRecursively(clonedNodeTemp);
          clonedNodeTemp.detach();
        } else if (clonedNodeTemp instanceof Document) {
          // Document node
          final Element clonedNodeTempRootElement = clonedNodeTemp.getDocument().getRootElement();

          if (clonedNodeTempRootElement == null) {
            // Can be null in rare cases of documents without root element
            clonedNodesTemp.set(
                i, null); // we support having a null node further below, so set this to null
          } else {
            if (applyDefaults)
              InstanceDataOps.setRequireDefaultValueRecursively(clonedNodeTempRootElement);
            else InstanceDataOps.removeRecursively(clonedNodeTempRootElement);
            // We can never really insert a document into anything at this point, but we assume that
            // this means the root element
            clonedNodesTemp.set(i, clonedNodeTempRootElement.detach());
          }
        } else {
          // Other nodes
          clonedNodeTemp.detach();
        }
      }
      clonedNodes = clonedNodesTemp;
    }

    // "6. The target location of each cloned node or nodes is determined"
    // "7. The cloned node or nodes are inserted in the order they were cloned at their target
    // location
    // depending on their node type."

    // Find actual insertion point and insert
    final int insertLocationIndexWithinParentBeforeUpdate;
    final List<Node> insertedNodes;
    final String beforeAfterInto;
    if (isEmptyNodesetBinding) {
      // Insert INTO a node

      insertLocationIndexWithinParentBeforeUpdate =
          findNodeIndexRewrapIfNeeded(insertLocationNodeInfo);

      final Node insertLocationNode =
          XFormsUtils.getNodeFromNodeInfo(insertContextNodeInfo, CANNOT_INSERT_READONLY_MESSAGE);
      insertedNodes = doInsert(insertLocationNode, clonedNodes, modifiedInstanceOrNull, doDispatch);
      beforeAfterInto = "into";

      // Normalize text nodes if needed to respect XPath 1.0 constraint
      {
        boolean hasTextNode = false;
        for (Node clonedNode : clonedNodes) {
          hasTextNode |= clonedNode != null && (clonedNode instanceof Text);
        }
        if (hasTextNode) Dom4jUtils.normalizeTextNodes(insertLocationNode);
      }
    } else {
      // Insert BEFORE or AFTER a node

      insertLocationIndexWithinParentBeforeUpdate =
          findNodeIndexRewrapIfNeeded(insertLocationNodeInfo);

      final Node insertLocationNode =
          XFormsUtils.getNodeFromNodeInfo(insertLocationNodeInfo, CANNOT_INSERT_READONLY_MESSAGE);
      final Document insertLocationNodeDocument = insertLocationNode.getDocument();
      if (insertLocationNodeDocument != null
          && insertLocationNodeDocument.getRootElement() == insertLocationNode) {

        // "c. if insert location node is the root element of an instance, then that instance root
        // element
        // location is the target location. If there is more than one cloned node to insert, only
        // the
        // first node that does not cause a conflict is considered."

        insertedNodes =
            doInsert(
                insertLocationNode.getDocument(), clonedNodes, modifiedInstanceOrNull, doDispatch);
        beforeAfterInto = positionAttribute; // TODO: ideally normalize to "into document node"?

        // NOTE: Don't need to normalize text nodes in this case, as no new text node is inserted
      } else {
        // "d. Otherwise, the target location is immediately before or after the insert location
        // node, based on the position attribute setting or its default."

        if (insertLocationNode instanceof Attribute) {
          // Special case for "next to an attribute"

          // NOTE: In XML, attributes are unordered. dom4j handles them as a list so has order, but
          // the XForms spec shouldn't rely on attribute order. We could try to keep the order, but
          // it
          // is harder as we have to deal with removing duplicate attributes and find a reasonable
          // insertion strategy.

          // TODO: Don't think we should even do this now in XForms 1.1
          insertedNodes =
              doInsert(
                  insertLocationNode.getParent(), clonedNodes, modifiedInstanceOrNull, doDispatch);

        } else {
          // Other node types
          final Element parentNode = insertLocationNode.getParent();
          final List<Node> siblingElements = parentNode.content();
          final int actualIndex = siblingElements.indexOf(insertLocationNode);

          // Prepare insertion of new element
          final int actualInsertionIndex;
          if ("before".equals(positionAttribute)) {
            actualInsertionIndex = actualIndex;
          } else {
            // "after"
            actualInsertionIndex = actualIndex + 1;
          }

          // "7. The cloned node or nodes are inserted in the order they were cloned at their target
          // location depending on their node type."

          boolean hasTextNode = false;
          int addIndex = 0;
          insertedNodes = new ArrayList<Node>(clonedNodes.size());
          for (Node clonedNode : clonedNodes) {

            if (clonedNode != null) { // NOTE: we allow passing some null nodes so we check on null
              if (!(clonedNode instanceof Attribute || clonedNode instanceof Namespace)) {
                // Element, text, comment, processing instruction node
                siblingElements.add(actualInsertionIndex + addIndex, clonedNode);
                insertedNodes.add(clonedNode);
                hasTextNode |= clonedNode instanceof Text;
                addIndex++;
              } else {
                // We never insert attributes or namespace nodes as siblings
                if (indentedLogger != null && indentedLogger.isDebugEnabled())
                  indentedLogger.logDebug(
                      "xf:insert",
                      "skipping insertion of node as sibling in element content",
                      "type",
                      Node$.MODULE$.nodeTypeName(clonedNode),
                      "node",
                      clonedNode instanceof Attribute
                          ? Dom4jUtils.attributeToDebugString((Attribute) clonedNode)
                          : clonedNode.toString());
              }
            }
          }

          // Normalize text nodes if needed to respect XPath 1.0 constraint
          if (hasTextNode) Dom4jUtils.normalizeTextNodes(parentNode);
        }

        beforeAfterInto = positionAttribute;
      }
    }

    // Whether some nodes were inserted
    final boolean didInsertNodes = insertedNodes != null && insertedNodes.size() > 0;

    // Log stuff
    if (indentedLogger != null && indentedLogger.isDebugEnabled()) {
      if (didInsertNodes)
        indentedLogger.logDebug(
            "xf:insert",
            "inserted nodes",
            "count",
            Integer.toString(insertedNodes.size()),
            "instance",
            (modifiedInstanceOrNull != null) ? modifiedInstanceOrNull.getEffectiveId() : null);
      else indentedLogger.logDebug("xf:insert", "no node inserted");
    }

    // "XForms Actions that change the tree structure of instance data result in setting all four
    // flags to true"
    if (didInsertNodes && modifiedInstanceOrNull != null) {
      // NOTE: Can be null if document into which delete is performed is not in an instance, e.g. in
      // a variable
      modifiedInstanceOrNull.markModified();
      modifiedInstanceOrNull
          .model()
          .markStructuralChange(
              scala.Option.<XFormsInstance>apply(modifiedInstanceOrNull),
              FlaggedDefaultsStrategy$.MODULE$);
    }

    // Gather list of modified nodes
    final List<NodeInfo> insertedNodeInfos;
    if (didInsertNodes && modifiedInstanceOrNull != null) {
      // Instance can be null if document into which delete is performed is not in an instance, e.g.
      // in a variable
      final DocumentWrapper documentWrapper =
          (DocumentWrapper) modifiedInstanceOrNull.documentInfo();
      insertedNodeInfos = new ArrayList<NodeInfo>(insertedNodes.size());
      for (Node insertedNode : insertedNodes)
        insertedNodeInfos.add(documentWrapper.wrap(insertedNode));
    } else {
      insertedNodeInfos = Collections.emptyList();
    }

    // "4. If the insert is successful, the event xforms-insert is dispatched."
    // XFormsInstance handles index and repeat items updates
    if (doDispatch && didInsertNodes && modifiedInstanceOrNull != null) {

      // Adjust insert location node and before/after/into in case the root element was replaced
      final NodeInfo adjustedInsertLocationNodeInfo;
      final String adjustedBeforeAfterInto;

      final NodeInfo parent =
          insertedNodeInfos.get(0).getNodeKind() == org.w3c.dom.Node.ELEMENT_NODE
              ? insertedNodeInfos.get(0).getParent()
              : null;
      if (parent != null && parent.equals(parent.getDocumentRoot())) {
        // Node was inserted under document node
        adjustedInsertLocationNodeInfo = parent.getDocumentRoot();
        adjustedBeforeAfterInto = "into";
      } else {
        adjustedInsertLocationNodeInfo = rewrapIfNeeded(insertLocationNodeInfo);
        adjustedBeforeAfterInto = beforeAfterInto;
      }

      Dispatch.dispatchEvent(
          new XFormsInsertEvent(
              modifiedInstanceOrNull,
              insertedNodeInfos,
              originItems,
              adjustedInsertLocationNodeInfo,
              adjustedBeforeAfterInto,
              insertLocationIndexWithinParentBeforeUpdate));
    }

    return insertedNodeInfos;
  }
  private void gatherInputDependencies(
      PipelineContext pipelineContext,
      XFormsContainingDocument containingDocument,
      IndentedLogger indentedLogger,
      Stage1CacheableState stage1CacheableState) {

    final String forwardSubmissionHeaders =
        XFormsProperties.getForwardSubmissionHeaders(containingDocument);

    // Add static instance source dependencies for top-level models
    // TODO: check all models/instances
    final XFormsStaticState staticState = containingDocument.getStaticState();
    for (final Model model :
        staticState.getModelsForScope(staticState.getXBLBindings().getTopLevelScope())) {
      for (final Instance instance : model.instancesMap().values()) {
        if (instance.dependencyURL() != null) {

          final String resolvedDependencyURL =
              XFormsUtils.resolveServiceURL(
                  pipelineContext,
                  containingDocument,
                  instance.element(),
                  instance.dependencyURL(),
                  ExternalContext.Response.REWRITE_MODE_ABSOLUTE);

          if (!instance.isCacheHint()) {
            stage1CacheableState.addReference(
                null,
                resolvedDependencyURL,
                instance.xxformsUsername(),
                instance.xxformsPassword(),
                instance.xxformsPassword(),
                forwardSubmissionHeaders);

            if (indentedLogger.isDebugEnabled())
              indentedLogger.logDebug(
                  "",
                  "adding document cache dependency for non-cacheable instance",
                  "instance URI",
                  resolvedDependencyURL);

          } else {
            // Don't add the dependency as we don't want the instance URI to be hit
            // For all practical purposes, globally shared instances must remain constant!
            if (indentedLogger.isDebugEnabled())
              indentedLogger.logDebug(
                  "",
                  "not adding document cache dependency for cacheable instance",
                  "instance URI",
                  resolvedDependencyURL);
          }
        }
      }
    }

    // Set caching dependencies if the input was actually read
    // TODO: check all models/instances
    // Q: should use static dependency information instead? what about schema imports and instance
    // replacements?
    for (final XFormsModel currentModel : containingDocument.getModels()) {
      // Add schema dependencies
      final String[] schemaURIs = currentModel.getSchemaURIs();
      // TODO: We should also use dependencies computed in XFormsModelSchemaValidator.SchemaInfo
      if (schemaURIs != null) {
        for (final String currentSchemaURI : schemaURIs) {
          if (indentedLogger.isDebugEnabled())
            indentedLogger.logDebug(
                "", "adding document cache dependency for schema", "schema URI", currentSchemaURI);

          stage1CacheableState.addReference(
              null,
              currentSchemaURI,
              null,
              null,
              null,
              forwardSubmissionHeaders); // TODO: support username / password on schema refs
        }
      }
    }
    // TODO: Add @src attributes from controls? Not used often.

    // Set caching dependencies for XBL inclusions
    {
      final XFormsStaticState.Metadata metadata = containingDocument.getStaticState().getMetadata();
      final Set<String> includes = metadata.getBindingsIncludes();
      if (includes != null) {
        for (final String include : includes) {
          stage1CacheableState.addReference(null, "oxf:" + include, null, null, null, null);
        }
      }
    }
  }
    public StaticStateBits(
        PipelineContext pipelineContext,
        ExternalContext externalContext,
        IndentedLogger indentedLogger,
        String existingStaticStateDigest) {

      final boolean computeDigest = isLogStaticStateInput || existingStaticStateDigest == null;

      indentedLogger.startHandleOperation(
          "", "reading input", "existing digest", existingStaticStateDigest);

      final TransformerXMLReceiver documentReceiver =
          TransformerUtils.getIdentityTransformerHandler();
      final LocationDocumentResult documentResult = new LocationDocumentResult();
      documentReceiver.setResult(documentResult);

      final XMLUtils.DigestContentHandler digestReceiver =
          computeDigest ? new XMLUtils.DigestContentHandler("MD5") : null;
      final XMLReceiver extractorOutput;
      if (isLogStaticStateInput) {
        extractorOutput =
            computeDigest
                ? new TeeXMLReceiver(
                    documentReceiver, digestReceiver, getDebugReceiver(indentedLogger))
                : new TeeXMLReceiver(documentReceiver, getDebugReceiver(indentedLogger));
      } else {
        extractorOutput =
            computeDigest ? new TeeXMLReceiver(documentReceiver, digestReceiver) : documentReceiver;
      }

      // Read the input through the annotator and gather namespace mappings
      //
      // Output of annotator is:
      //
      // o annotated page template (TODO: this should not include model elements)
      // o extractor
      //
      // Output of extractor is:
      //
      // o static state document
      // o optionally: digest
      // o optionally: debug output
      //
      readInputAsSAX(
          pipelineContext,
          INPUT_ANNOTATED_DOCUMENT,
          new XFormsAnnotatorContentHandler(
              annotatedTemplate,
              new XFormsExtractorContentHandler(extractorOutput, metadata),
              metadata));

      this.staticStateDocument = documentResult.getDocument();
      this.staticStateDigest =
          computeDigest ? NumberUtils.toHexString(digestReceiver.getResult()) : null;

      assert !isLogStaticStateInput
          || existingStaticStateDigest == null
          || this.staticStateDigest.equals(existingStaticStateDigest);

      indentedLogger.endHandleOperation("computed digest", this.staticStateDigest);
    }
  public void replace(
      PropertyContext propertyContext,
      ConnectionResult connectionResult,
      XFormsModelSubmission.SubmissionParameters p,
      XFormsModelSubmission.SecondPassParameters p2) {

    // Set new instance document to replace the one submitted

    final XFormsInstance replaceInstanceNoTargetref =
        submission.findReplaceInstanceNoTargetref(p.refInstance);
    if (replaceInstanceNoTargetref == null) {

      // Replacement instance or node was specified but not found
      //
      // Not sure what's the right thing to do with 1.1, but this could be done
      // as part of the model's static analysis if the instance value is not
      // obtained through AVT, and dynamically otherwise. However, in the dynamic
      // case, I think that this should be a (currently non-specified by XForms)
      // xforms-binding-error.

      submission
          .getXBLContainer(containingDocument)
          .dispatchEvent(
              propertyContext, new XFormsBindingExceptionEvent(containingDocument, submission));
    } else {

      final NodeInfo destinationNodeInfo =
          submission.evaluateTargetRef(
              propertyContext,
              p.xpathContext,
              replaceInstanceNoTargetref,
              p.submissionElementContextItem);

      if (destinationNodeInfo == null) {
        // Throw target-error

        // XForms 1.1: "If the processing of the targetref attribute fails,
        // then submission processing ends after dispatching the event
        // xforms-submit-error with an error-type of target-error."

        throw new XFormsSubmissionException(
            submission,
            "targetref attribute doesn't point to an element for replace=\"instance\".",
            "processing targetref attribute",
            new XFormsSubmitErrorEvent(
                containingDocument,
                propertyContext,
                submission,
                XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
                connectionResult));
      }

      // This is the instance which is effectively going to be updated
      final XFormsInstance updatedInstance =
          containingDocument.getInstanceForNode(destinationNodeInfo);
      if (updatedInstance == null) {
        throw new XFormsSubmissionException(
            submission,
            "targetref attribute doesn't point to an element in an existing instance for replace=\"instance\".",
            "processing targetref attribute",
            new XFormsSubmitErrorEvent(
                containingDocument,
                propertyContext,
                submission,
                XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
                connectionResult));
      }

      // Whether the destination node is the root element of an instance
      final boolean isDestinationRootElement =
          updatedInstance.getInstanceRootElementInfo().isSameNodeInfo(destinationNodeInfo);
      if (p2.isReadonly && !isDestinationRootElement) {
        // Only support replacing the root element of an instance when using a shared instance
        throw new XFormsSubmissionException(
            submission,
            "targetref attribute must point to instance root element when using read-only instance replacement.",
            "processing targetref attribute",
            new XFormsSubmitErrorEvent(
                containingDocument,
                propertyContext,
                submission,
                XFormsSubmitErrorEvent.ErrorType.TARGET_ERROR,
                connectionResult));
      }

      final IndentedLogger detailsLogger = getDetailsLogger(p, p2);

      // Obtain root element to insert
      final NodeInfo newDocumentRootElement;
      final XFormsInstance newInstance;
      {
        // Create resulting instance whether entire instance is replaced or not, because this:
        // 1. Wraps a Document within a DocumentInfo if needed
        // 2. Performs text nodes adjustments if needed
        if (!p2.isReadonly) {
          // Resulting instance must not be read-only

          if (detailsLogger.isDebugEnabled())
            detailsLogger.logDebug(
                "",
                "replacing instance with mutable instance",
                "instance",
                updatedInstance.getEffectiveId());

          newInstance =
              new XFormsInstance(
                  updatedInstance.getEffectiveModelId(),
                  updatedInstance.getId(),
                  (Document) resultingDocument,
                  connectionResult.resourceURI,
                  resultingRequestBodyHash,
                  p2.username,
                  p2.password,
                  p2.isCache,
                  p2.timeToLive,
                  updatedInstance.getValidation(),
                  p2.isHandleXInclude,
                  XFormsProperties.isExposeXPathTypes(containingDocument));
        } else {
          // Resulting instance must be read-only

          if (detailsLogger.isDebugEnabled())
            detailsLogger.logDebug(
                "",
                "replacing instance with read-only instance",
                "instance",
                updatedInstance.getEffectiveId());

          newInstance =
              new ReadonlyXFormsInstance(
                  updatedInstance.getEffectiveModelId(),
                  updatedInstance.getId(),
                  (DocumentInfo) resultingDocument,
                  connectionResult.resourceURI,
                  resultingRequestBodyHash,
                  p2.username,
                  p2.password,
                  p2.isCache,
                  p2.timeToLive,
                  updatedInstance.getValidation(),
                  p2.isHandleXInclude,
                  XFormsProperties.isExposeXPathTypes(containingDocument));
        }
        newDocumentRootElement = newInstance.getInstanceRootElementInfo();
      }

      // Perform insert/delete. This will dispatch xforms-insert/xforms-delete events.
      // "the replacement is performed by an XForms action that performs some
      // combination of node insertion and deletion operations that are
      // performed by the insert action (10.3 The insert Element) and the
      // delete action"

      if (isDestinationRootElement) {
        // Optimized insertion for instance root element replacement

        // Handle new instance and associated event markings
        final XFormsModel replaceModel = newInstance.getModel(containingDocument);
        replaceModel.handleUpdatedInstance(propertyContext, newInstance, newDocumentRootElement);

        // Dispatch xforms-delete event
        // NOTE: Do NOT dispatch so we are compatible with the regular root element replacement
        // (see below). In the future, we might want to dispatch this, especially if
        // XFormsInsertAction dispatches xforms-delete when removing the root element
        // updatedInstance.getXBLContainer(containingDocument).dispatchEvent(pipelineContext, new
        // XFormsDeleteEvent(updatedInstance, Collections.singletonList(destinationNodeInfo), 1));

        // Dispatch xforms-insert event
        // NOTE: use the root node as insert location as it seems to make more sense than pointing
        // to the earlier root element
        newInstance
            .getXBLContainer(containingDocument)
            .dispatchEvent(
                propertyContext,
                new XFormsInsertEvent(
                    containingDocument,
                    newInstance,
                    Collections.singletonList((Item) newDocumentRootElement),
                    null,
                    newDocumentRootElement.getDocumentRoot(),
                    "after",
                    null,
                    null,
                    true));

      } else {
        // Generic insertion

        final List<NodeInfo> destinationCollection = Collections.singletonList(destinationNodeInfo);

        // Perform the insertion

        // Insert before the target node, so that the position of the inserted node
        // wrt its parent does not change after the target node is removed
        final List insertedNode =
            XFormsInsertAction.doInsert(
                propertyContext,
                containingDocument,
                detailsLogger,
                "before",
                destinationCollection,
                destinationNodeInfo.getParent(),
                Collections.singletonList(newDocumentRootElement),
                1,
                false,
                true);

        if (!destinationNodeInfo
            .getParent()
            .isSameNodeInfo(destinationNodeInfo.getDocumentRoot())) {
          // The node to replace is NOT a root element

          // Perform the deletion of the selected node
          XFormsDeleteAction.doDelete(
              propertyContext, containingDocument, detailsLogger, destinationCollection, 1, true);
        }

        // Perform model instance update
        // Handle new instance and associated event markings
        // NOTE: The inserted node NodeWrapper.index might be out of date at this point because:
        // * doInsert() dispatches an event which might itself change the instance
        // * doDelete() does as well
        // Does this mean that we should check that the node is still where it should be?
        final XFormsModel updatedModel = updatedInstance.getModel(containingDocument);
        updatedModel.handleUpdatedInstance(
            propertyContext, updatedInstance, (NodeInfo) insertedNode.get(0));
      }

      // Dispatch xforms-submit-done
      dispatchSubmitDone(propertyContext, connectionResult);
    }
  }
  public void execute(
      XFormsActionInterpreter actionInterpreter,
      Element actionElement,
      Scope actionScope,
      boolean hasOverriddenContext,
      Item overriddenContext) {

    final XFormsContainingDocument containingDocument = actionInterpreter.containingDocument();

    // Resolve all attributes as AVTs
    final String dialogStaticOrEffectiveId = actionInterpreter.resolveAVT(actionElement, "dialog");
    final String neighborEffectiveId;
    {
      final String neighborStaticOrEffectiveId =
          actionInterpreter.resolveAVT(actionElement, "neighbor");
      final XFormsControl neighbor =
          (XFormsControl)
              ((neighborStaticOrEffectiveId != null)
                  ? actionInterpreter.resolveOrFindByEffectiveId(
                      actionElement, neighborStaticOrEffectiveId)
                  : null);
      neighborEffectiveId = (neighbor != null) ? neighbor.getEffectiveId() : null;
    }
    final boolean constrainToViewport;
    {
      final String constrain = actionInterpreter.resolveAVT(actionElement, "constrain");
      constrainToViewport = !"false".equals(constrain);
    }

    if (dialogStaticOrEffectiveId != null) {
      // Dispatch xxforms-dialog-open event to dialog
      final Object controlObject =
          actionInterpreter.resolveOrFindByEffectiveId(actionElement, dialogStaticOrEffectiveId);
      if (controlObject instanceof XXFormsDialogControl) {
        final XXFormsDialogControl targetDialog = (XXFormsDialogControl) controlObject;

        // Remove focus if any
        containingDocument.setClientFocusEffectiveControlId(null);

        final XFormsEvent newEvent =
            new XXFormsDialogOpenEvent(
                containingDocument, targetDialog, neighborEffectiveId, constrainToViewport);
        addContextAttributes(actionInterpreter, actionElement, newEvent);
        targetDialog.getXBLContainer(containingDocument).dispatchEvent(newEvent);

        // Check if form author has set focus while dialog was opening, and if not focus on dialog
        final String currentFocusEffectiveId =
            containingDocument.getClientFocusControlEffectiveId();
        if (currentFocusEffectiveId == null && targetDialog.isVisible())
          targetDialog
              .getXBLContainer()
              .dispatchEvent(new XFormsFocusEvent(containingDocument, targetDialog));

      } else {
        final IndentedLogger indentedLogger = actionInterpreter.indentedLogger();
        if (indentedLogger.isDebugEnabled())
          indentedLogger.logDebug(
              "xxforms:show",
              "dialog does not refer to an existing xxforms:dialog element, ignoring action",
              "dialog id",
              dialogStaticOrEffectiveId);
      }
    }
  }