Ejemplo n.º 1
0
  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 SubmissionResult connect(
      final PropertyContext propertyContext,
      final XFormsModelSubmission.SubmissionParameters p,
      final XFormsModelSubmission.SecondPassParameters p2,
      final XFormsModelSubmission.SerializationParameters sp)
      throws Exception {
    // Get the instance from shared instance cache
    // This can only happen is method="get" and replace="instance" and xxforms:cache="true" or
    // xxforms:shared="application"

    // Convert URL to string
    final String absoluteResolvedURLString;
    {
      final ExternalContext externalContext = getExternalContext(propertyContext);
      final URL absoluteResolvedURL =
          getResolvedSubmissionURL(
              propertyContext, externalContext, p2.actionOrResource, sp.queryString);
      absoluteResolvedURLString = absoluteResolvedURL.toExternalForm();
    }

    // Compute a hash of the body if needed
    final String requestBodyHash;
    if (sp.messageBody != null) {
      requestBodyHash = SecureUtils.digestBytes(sp.messageBody, "MD5", "hex");
    } else {
      requestBodyHash = null;
    }

    // Parameters to callable
    final String submissionEffectiveId = submission.getEffectiveId();
    final String instanceStaticId;
    final String modelEffectiveId;
    final String validation;
    {
      // Find and check replacement location
      final XFormsInstance updatedInstance = checkInstanceToUpdate(propertyContext, p);

      instanceStaticId = updatedInstance.getId();
      modelEffectiveId = updatedInstance.getEffectiveModelId();
      validation = updatedInstance.getValidation();
    }
    final boolean isReadonly = p2.isReadonly;
    final boolean handleXInclude = p2.isHandleXInclude;
    final long timeToLive = p2.timeToLive;

    // Create new logger as the submission might be asynchronous
    final IndentedLogger submissionLogger =
        new IndentedLogger(containingDocument.getIndentedLogger());

    // Obtain replacer
    // Pass a pseudo connection result which contains information used by getReplacer()
    // We know that we will get an InstanceReplacer
    final ConnectionResult connectionResult =
        createPseudoConnectionResult(absoluteResolvedURLString);
    final InstanceReplacer replacer =
        (InstanceReplacer) submission.getReplacer(propertyContext, connectionResult, p);

    // Try from cache first
    final XFormsInstance cacheResult =
        XFormsServerSharedInstancesCache.instance()
            .findConvertNoLoad(
                propertyContext,
                submissionLogger,
                instanceStaticId,
                modelEffectiveId,
                absoluteResolvedURLString,
                requestBodyHash,
                isReadonly,
                handleXInclude,
                XFormsProperties.isExposeXPathTypes(containingDocument));

    if (cacheResult != null) {
      // Result was immediately available, so return it right away
      // The purpose of this is to avoid starting a new thread in asynchronous mode if the instance
      // is already in cache

      // Here we cheat a bit: instead of calling generically deserialize(), we directly set the
      // instance document
      replacer.setInstance(cacheResult);

      // Return result
      return new SubmissionResult(submissionEffectiveId, replacer, connectionResult);
    } else {
      // Create callable for synchronous or asynchronous loading
      final Callable<SubmissionResult> callable =
          new Callable<SubmissionResult>() {
            public SubmissionResult call() {
              try {
                final XFormsInstance newInstance =
                    XFormsServerSharedInstancesCache.instance()
                        .findConvert(
                            propertyContext,
                            submissionLogger,
                            instanceStaticId,
                            modelEffectiveId,
                            absoluteResolvedURLString,
                            requestBodyHash,
                            isReadonly,
                            handleXInclude,
                            XFormsProperties.isExposeXPathTypes(containingDocument),
                            timeToLive,
                            validation,
                            new XFormsServerSharedInstancesCache.Loader() {
                              public ReadonlyXFormsInstance load(
                                  PropertyContext propertyContext,
                                  String instanceStaticId,
                                  String modelEffectiveId,
                                  String instanceSourceURI,
                                  boolean handleXInclude,
                                  long timeToLive,
                                  String validation) {

                                // Call regular submission
                                SubmissionResult submissionResult = null;
                                try {
                                  // Run regular submission but force synchronous execution and
                                  // readonly result
                                  final XFormsModelSubmission.SecondPassParameters updatedP2 =
                                      p2.amend(false, true);
                                  submissionResult =
                                      new RegularSubmission(submission)
                                          .connect(propertyContext, p, updatedP2, sp);

                                  // Check if the connection returned a throwable
                                  final Throwable throwable = submissionResult.getThrowable();
                                  if (throwable != null) {
                                    // Propagate
                                    throw new ThrowableWrapper(
                                        throwable, submissionResult.getConnectionResult());
                                  } else {
                                    // There was no throwable
                                    // We know that RegularSubmission returns a Replacer with an
                                    // instance document
                                    final DocumentInfo documentInfo =
                                        (DocumentInfo)
                                            ((InstanceReplacer) submissionResult.getReplacer())
                                                .getResultingDocument();

                                    // Create new shared instance
                                    return new ReadonlyXFormsInstance(
                                        modelEffectiveId,
                                        instanceStaticId,
                                        documentInfo,
                                        instanceSourceURI,
                                        updatedP2.username,
                                        updatedP2.password,
                                        true,
                                        timeToLive,
                                        validation,
                                        handleXInclude,
                                        XFormsProperties.isExposeXPathTypes(containingDocument));
                                  }
                                } catch (ThrowableWrapper throwableWrapper) {
                                  // In case we just threw it above, just propagate
                                  throw throwableWrapper;
                                } catch (Throwable throwable) {
                                  // Exceptions are handled further down
                                  throw new ThrowableWrapper(
                                      throwable,
                                      (submissionResult != null)
                                          ? submissionResult.getConnectionResult()
                                          : null);
                                }
                              }
                            });

                // Here we cheat a bit: instead of calling generically deserialize(), we directly
                // set the DocumentInfo
                replacer.setInstance(newInstance);

                // Return result
                return new SubmissionResult(submissionEffectiveId, replacer, connectionResult);
              } catch (ThrowableWrapper throwableWrapper) {
                // The ThrowableWrapper was thrown within the inner load() method above
                return new SubmissionResult(
                    submissionEffectiveId,
                    throwableWrapper.getThrowable(),
                    throwableWrapper.getConnectionResult());
              } catch (Throwable throwable) {
                // Any other throwable
                return new SubmissionResult(submissionEffectiveId, throwable, null);
              }
            }
          };

      // Submit the callable
      // This returns null if the execution is asynchronous
      return submitCallable(p, p2, callable);
    }
  }