private void cacheOrStore(XFormsContainingDocument containingDocument, boolean isInitialState) { if (XFormsDocumentCache.instance().isEnabled(containingDocument.getStaticState())) { // Cache the document indentedLogger.logDebug(LOG_TYPE, "Document cache enabled. Storing document in cache."); XFormsDocumentCache.instance().storeDocument(containingDocument); if (isInitialState && containingDocument.getStaticState().isServerStateHandling()) { // Also store document state (used by browser back and <xf:reset>) indentedLogger.logDebug(LOG_TYPE, "Storing initial document state."); storeDocumentState(containingDocument, isInitialState); } } else if (containingDocument.getStaticState().isServerStateHandling()) { // Directly store the document state indentedLogger.logDebug(LOG_TYPE, "Document cache disabled. Storing initial document state."); storeDocumentState(containingDocument, isInitialState); } LifecycleLogger.eventAssumingRequestJava( "xforms", "after cacheOrStore", new String[] { "document cache current size", Integer.toString(XFormsDocumentCache.instance().getCurrentSize()), "document cache max size", Integer.toString(XFormsDocumentCache.instance().getMaxSize()) }); }
/** * Find or restore a document based on an incoming request. * * <p>Implementation: try cache first, then restore from store if not found. * * <p>If found in cache, document is removed from cache. * * @param parameters update parameters * @param isInitialState whether to return the initial state, otherwise return the current state * @param disableUpdates whether to disable updates (for recreating initial document upon browser * back) * @return document, either from cache or from state information */ public XFormsContainingDocument findOrRestoreDocument( RequestParameters parameters, boolean isInitialState, boolean disableUpdates) { // Try cache first unless the initial state is requested if (!isInitialState) { if (XFormsProperties.isCacheDocument()) { // Try to find the document in cache using the UUID // NOTE: If the document has cache.document="false", then it simply won't be found in the // cache, but // we can't know that the property is set to false before trying. final XFormsContainingDocument cachedDocument = XFormsDocumentCache.instance().takeDocument(parameters.getUUID()); if (cachedDocument != null) { // Found in cache indentedLogger.logDebug( LOG_TYPE, "Document cache enabled. Returning document from cache."); return cachedDocument; } indentedLogger.logDebug( LOG_TYPE, "Document cache enabled. Document not found in cache. Retrieving state from store."); } else { indentedLogger.logDebug(LOG_TYPE, "Document cache disabled. Retrieving state from store."); } } else { indentedLogger.logDebug( LOG_TYPE, "Initial document state requested. Retrieving state from store."); } // Must recreate from store return createDocumentFromStore(parameters, isInitialState, disableUpdates); }
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; }
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; }
/** * Called before sending an update response. * * <p>Implementation: update the document's change sequence. * * @param containingDocument containing document * @param ignoreSequence whether to ignore the sequence number */ public void beforeUpdateResponse( XFormsContainingDocument containingDocument, boolean ignoreSequence) { if (containingDocument.isDirtySinceLastRequest()) { // The document is dirty indentedLogger.logDebug(LOG_TYPE, "Document is dirty. Generating new dynamic state."); } else { // The document is not dirty: no real encoding takes place here indentedLogger.logDebug(LOG_TYPE, "Document is not dirty. Keep existing dynamic state."); } // Tell the document to update its state if (!ignoreSequence) containingDocument.updateChangeSequence(); }
/** * Called after an update. * * @param containingDocument document * @param keepDocument whether to keep the document around */ public void afterUpdate(XFormsContainingDocument containingDocument, boolean keepDocument) { if (keepDocument) { // Re-add document to the cache indentedLogger.logDebug(LOG_TYPE, "Keeping document in cache."); cacheOrStore(containingDocument, false); } else { // Don't re-add document to the cache indentedLogger.logDebug(LOG_TYPE, "Not keeping document in cache following error."); // Remove all information about this document from the session final String uuid = containingDocument.getUUID(); removeCacheSessionListener(uuid); removeSessionDocument(uuid); } }
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); } }
private void cacheOrStore(XFormsContainingDocument containingDocument, boolean isInitialState) { if (XFormsDocumentCache.instance().isEnabled(containingDocument.getStaticState())) { // Cache the document indentedLogger.logDebug(LOG_TYPE, "Document cache enabled. Storing document in cache."); XFormsDocumentCache.instance().storeDocument(containingDocument); if (isInitialState && containingDocument.getStaticState().isServerStateHandling()) { // Also store document state (used by browser back and <xf:reset>) indentedLogger.logDebug(LOG_TYPE, "Storing initial document state."); storeDocumentState(containingDocument, isInitialState); } } else if (containingDocument.getStaticState().isServerStateHandling()) { // Directly store the document state indentedLogger.logDebug(LOG_TYPE, "Document cache disabled. Storing initial document state."); storeDocumentState(containingDocument, isInitialState); } }
private Stage2CacheableState readStaticState( PipelineContext pipelineContext, ExternalContext externalContext, IndentedLogger indentedLogger, XFormsStaticState[] staticState) { final StaticStateBits staticStateBits = new StaticStateBits(pipelineContext, externalContext, indentedLogger, null); { final XFormsStaticState cachedState = XFormsStaticStateCache.instance() .getDocument(pipelineContext, staticStateBits.staticStateDigest); if (cachedState != null && cachedState.getMetadata().checkBindingsIncludes()) { // Found static state in cache indentedLogger.logDebug("", "found up-to-date static state by digest in cache"); staticState[0] = cachedState; } else { // Not found static state in cache OR it is out of date, create and initialize static state // object // NOTE: In out of date case, could clone static state and reprocess instead? if (cachedState != null) indentedLogger.logDebug("", "found out-of-date static state by digest in cache"); else indentedLogger.logDebug("", "did not find static state by digest in cache"); staticState[0] = new XFormsStaticState( pipelineContext, staticStateBits.staticStateDocument, staticStateBits.staticStateDigest, staticStateBits.metadata); // Store in cache XFormsStaticStateCache.instance().storeDocument(pipelineContext, staticState[0]); } } // Update input dependencies object return new Stage2CacheableState( staticStateBits.annotatedTemplate, staticStateBits.staticStateDigest); }
private void addCacheSessionListener(final String uuid) { final ExternalContext.Session session = NetUtils.getSession(XFormsStateManager.FORCE_SESSION_CREATION); final Map<String, Object> sessionAttributes = session.getAttributesMap(ExternalContext.Session.APPLICATION_SCOPE); final String listenerSessionKey = getListenerSessionKey(uuid); if (sessionAttributes.get(listenerSessionKey) == null) { // Remove from cache when session expires final ExternalContext.Session.SessionListener listener = new ExternalContext.Session.SessionListener() { public void sessionDestroyed() { indentedLogger.logDebug( LOG_TYPE, "Removing document from cache following session expiration."); // NOTE: This will call onRemoved() on the document, and onRemovedFromCache() on // XFormsStateManager XFormsDocumentCache.instance().removeDocument(uuid); } }; // Add listener try { session.addListener(listener); } catch (IllegalStateException e) { indentedLogger.logInfo(LOG_TYPE, "Unable to add session listener: " + e.getMessage()); // NOTE: This will call onRemoved() on the document, and onRemovedFromCache() on // XFormsStateManager XFormsDocumentCache.instance().removeDocument(uuid); // remove immediately throw e; } // Remember, in session, mapping (UUID -> session listener) sessionAttributes.put(listenerSessionKey, listener); } }
public static void outputResponseDocument( final PipelineContext pipelineContext, final ExternalContext externalContext, final IndentedLogger indentedLogger, final SAXStore annotatedDocument, final XFormsContainingDocument containingDocument, final XMLReceiver xmlReceiver) throws SAXException, IOException { final List<XFormsContainingDocument.Load> loads = containingDocument.getLoadsToRun(); if (containingDocument.isGotSubmissionReplaceAll()) { // 1. Got a submission with replace="all" // NOP: Response already sent out by a submission // TODO: modify XFormsModelSubmission accordingly indentedLogger.logDebug("", "handling response for submission with replace=\"all\""); } else if (loads != null && loads.size() > 0) { // 2. Got at least one xforms:load // Send redirect out // Get first load only final XFormsContainingDocument.Load load = loads.get(0); // Send redirect final String redirectResource = load.getResource(); indentedLogger.logDebug( "", "handling redirect response for xforms:load", "url", redirectResource); // Set isNoRewrite to true, because the resource is either a relative path or already contains // the servlet context externalContext.getResponse().sendRedirect(redirectResource, null, false, false, true); // Still send out a null document to signal that no further processing must take place XMLUtils.streamNullDocument(xmlReceiver); } else { // 3. Regular case: produce an XHTML document out final ElementHandlerController controller = new ElementHandlerController(); // Register handlers on controller (the other handlers are registered by the body handler) { controller.registerHandler( XHTMLHeadHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "head"); controller.registerHandler( XHTMLBodyHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "body"); // Register a handler for AVTs on HTML elements final boolean hostLanguageAVTs = XFormsProperties .isHostLanguageAVTs(); // TODO: this should be obtained per document, but we only // know about this in the extractor if (hostLanguageAVTs) { controller.registerHandler( XXFormsAttributeHandler.class.getName(), XFormsConstants.XXFORMS_NAMESPACE_URI, "attribute"); controller.registerHandler( XHTMLElementHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI); } // Swallow XForms elements that are unknown controller.registerHandler( NullHandler.class.getName(), XFormsConstants.XFORMS_NAMESPACE_URI); controller.registerHandler( NullHandler.class.getName(), XFormsConstants.XXFORMS_NAMESPACE_URI); controller.registerHandler(NullHandler.class.getName(), XFormsConstants.XBL_NAMESPACE_URI); } // Set final output controller.setOutput(new DeferredXMLReceiverImpl(xmlReceiver)); // Set handler context controller.setElementHandlerContext( new HandlerContext( controller, pipelineContext, containingDocument, externalContext, null)); // Process the entire input annotatedDocument.replay( new ExceptionWrapperXMLReceiver(controller, "converting XHTML+XForms document to XHTML")); } containingDocument.afterInitialResponse(); }
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); }
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; }
private void doIt( final PipelineContext pipelineContext, XMLReceiver xmlReceiver, final URIProcessorOutputImpl processorOutput, String outputName) { final ExternalContext externalContext = XFormsUtils.getExternalContext(pipelineContext); final IndentedLogger indentedLogger = XFormsContainingDocument.getIndentedLogger( XFormsToXHTML.logger, XFormsServer.getLogger(), LOGGING_CATEGORY); // ContainingDocument and XFormsState created below final XFormsContainingDocument[] containingDocument = new XFormsContainingDocument[1]; final boolean[] cachedStatus = new boolean[] {false}; final Stage2CacheableState stage2CacheableState; if (TEST_STATE == null) { // Read and try to cache the complete XForms+XHTML document with annotations stage2CacheableState = (Stage2CacheableState) readCacheInputAsObject( pipelineContext, getInputByName(INPUT_ANNOTATED_DOCUMENT), new CacheableInputReader() { public Object read( PipelineContext pipelineContext, ProcessorInput processorInput) { // Compute annotated XForms document + static state document final Stage1CacheableState stage1CacheableState = new Stage1CacheableState(); final Stage2CacheableState stage2CacheableState; final XFormsStaticState[] staticState = new XFormsStaticState[1]; { // Store dependencies container in state before reading ((Stage2TransientState) XFormsToXHTML.this.getState(pipelineContext)) .stage1CacheableState = stage1CacheableState; // Read static state from input stage2CacheableState = readStaticState( pipelineContext, externalContext, indentedLogger, staticState); } // Create containing document and initialize XForms engine // NOTE: Create document here so we can do appropriate analysis of caching // dependencies final XFormsURIResolver uriResolver = new XFormsURIResolver( XFormsToXHTML.this, processorOutput, pipelineContext, INPUT_ANNOTATED_DOCUMENT, URLGenerator.DEFAULT_HANDLE_XINCLUDE); containingDocument[0] = new XFormsContainingDocument( pipelineContext, staticState[0], stage2CacheableState.getAnnotatedTemplate(), uriResolver); // Gather set caching dependencies gatherInputDependencies( pipelineContext, containingDocument[0], indentedLogger, stage1CacheableState); return stage2CacheableState; } @Override public void foundInCache() { cachedStatus[0] = true; } }, false); TEST_STATE = DO_TEST_STATE ? stage2CacheableState : null; } else { stage2CacheableState = TEST_STATE; } try { // Create containing document if not done yet if (containingDocument[0] == null) { assert cachedStatus[0]; // In this case, we found the static state digest and more in the cache, but we must now // create a new XFormsContainingDocument from this information indentedLogger.logDebug( "", "annotated document and static state digest obtained from cache", "digest", stage2CacheableState.getStaticStateDigest()); final XFormsStaticState staticState; { final XFormsStaticState cachedState = XFormsStaticStateCache.instance() .getDocument(pipelineContext, stage2CacheableState.getStaticStateDigest()); if (cachedState != null && cachedState.getMetadata().checkBindingsIncludes()) { // Found static state in cache indentedLogger.logDebug("", "found up-to-date static state by digest in cache"); staticState = cachedState; } else { // Not found static state in cache OR it is out of date, create static state from input // NOTE: In out of date case, could clone static state and reprocess instead? if (cachedState != null) indentedLogger.logDebug("", "found out-of-date static state by digest in cache"); else indentedLogger.logDebug("", "did not find static state by digest in cache"); final StaticStateBits staticStateBits = new StaticStateBits( pipelineContext, externalContext, indentedLogger, stage2CacheableState.getStaticStateDigest()); staticState = new XFormsStaticState( pipelineContext, staticStateBits.staticStateDocument, stage2CacheableState.getStaticStateDigest(), staticStateBits.metadata); // Store in cache XFormsStaticStateCache.instance().storeDocument(pipelineContext, staticState); } } final XFormsURIResolver uriResolver = new XFormsURIResolver( XFormsToXHTML.this, processorOutput, pipelineContext, INPUT_ANNOTATED_DOCUMENT, URLGenerator.DEFAULT_HANDLE_XINCLUDE); containingDocument[0] = new XFormsContainingDocument( pipelineContext, staticState, stage2CacheableState.getAnnotatedTemplate(), uriResolver); } else { assert !cachedStatus[0]; indentedLogger.logDebug( "", "annotated document and static state digest not obtained from cache."); } // Output resulting document if (outputName.equals("document")) { // Normal case where we output XHTML outputResponseDocument( pipelineContext, externalContext, indentedLogger, stage2CacheableState.getAnnotatedTemplate(), containingDocument[0], xmlReceiver); } else { // Output in test mode testOutputResponseState( pipelineContext, containingDocument[0], indentedLogger, xmlReceiver); } // Notify state manager XFormsStateManager.instance().afterInitialResponse(pipelineContext, containingDocument[0]); } catch (Throwable e) { indentedLogger.logDebug("", "throwable caught during initialization."); throw new OXFException(e); } }
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); } }
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; }
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); } } } }
public StaticStateBits( PipelineContext pipelineContext, ExternalContext externalContext, IndentedLogger indentedLogger, String existingStaticStateDigest) { final boolean computeDigest = isLogStaticStateInput || existingStaticStateDigest == null; indentedLogger.startHandleOperation( "", "reading input", "existing digest", existingStaticStateDigest); final TransformerXMLReceiver documentReceiver = TransformerUtils.getIdentityTransformerHandler(); final LocationDocumentResult documentResult = new LocationDocumentResult(); documentReceiver.setResult(documentResult); final XMLUtils.DigestContentHandler digestReceiver = computeDigest ? new XMLUtils.DigestContentHandler("MD5") : null; final XMLReceiver extractorOutput; if (isLogStaticStateInput) { extractorOutput = computeDigest ? new TeeXMLReceiver( documentReceiver, digestReceiver, getDebugReceiver(indentedLogger)) : new TeeXMLReceiver(documentReceiver, getDebugReceiver(indentedLogger)); } else { extractorOutput = computeDigest ? new TeeXMLReceiver(documentReceiver, digestReceiver) : documentReceiver; } // Read the input through the annotator and gather namespace mappings // // Output of annotator is: // // o annotated page template (TODO: this should not include model elements) // o extractor // // Output of extractor is: // // o static state document // o optionally: digest // o optionally: debug output // readInputAsSAX( pipelineContext, INPUT_ANNOTATED_DOCUMENT, new XFormsAnnotatorContentHandler( annotatedTemplate, new XFormsExtractorContentHandler(extractorOutput, metadata), metadata)); this.staticStateDocument = documentResult.getDocument(); this.staticStateDigest = computeDigest ? NumberUtils.toHexString(digestReceiver.getResult()) : null; assert !isLogStaticStateInput || existingStaticStateDigest == null || this.staticStateDigest.equals(existingStaticStateDigest); indentedLogger.endHandleOperation("computed digest", this.staticStateDigest); }
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 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); } } }