예제 #1
0
  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;
  }
예제 #2
0
  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;
  }
  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);
    }
  }
  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);
    }
  }
예제 #5
0
  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);
    }
  }
예제 #6
0
  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);
      }
    }
  }
  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);
        }
      }
    }
  }
예제 #8
0
  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;
  }
예제 #9
0
  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);
  }
예제 #10
0
  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;
  }