Ejemplo n.º 1
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);
    }
  }
Ejemplo n.º 2
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);
  }