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 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();
  }
  private class StaticStateBits {

    private final boolean isLogStaticStateInput =
        XFormsProperties.getDebugLogging().contains("html-static-state");

    public final XFormsStaticState.Metadata metadata = new XFormsStaticState.Metadata();
    public final SAXStore annotatedTemplate = new SAXStore();

    public final Document staticStateDocument;
    public final String staticStateDigest;

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

    private XMLReceiver getDebugReceiver(final IndentedLogger indentedLogger) {
      final TransformerXMLReceiver identity = TransformerUtils.getIdentityTransformerHandler();
      final StringBuilderWriter writer = new StringBuilderWriter();
      identity.setResult(new StreamResult(writer));

      return new ForwardingXMLReceiver(identity) {
        @Override
        public void endDocument() throws SAXException {
          super.endDocument();
          // Log out at end of document
          indentedLogger.logDebug("", "static state input", "input", writer.toString());
        }
      };
    }
  }