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