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