/** * Copyright (C) 2010 Orbeon, Inc. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.xforms.action.actions; import org.orbeon.dom.Element; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.externalcontext.ExternalContext; import org.orbeon.oxf.util.IndentedLogger; import org.orbeon.oxf.util.NetUtils; import org.orbeon.oxf.xforms.BindingContext; import org.orbeon.oxf.xforms.XFormsConstants; import org.orbeon.oxf.xforms.XFormsContainingDocument; import org.orbeon.oxf.xforms.XFormsUtils; import org.orbeon.oxf.xforms.action.XFormsAction; import org.orbeon.oxf.xforms.action.XFormsActionInterpreter; import org.orbeon.oxf.xforms.model.DataModel; import org.orbeon.oxf.xforms.xbl.Scope; import org.orbeon.oxf.xml.XMLConstants; import org.orbeon.saxon.om.Item; /** * 10.1.8 The load Element */ public class XFormsLoadAction extends XFormsAction { public void execute(XFormsActionInterpreter actionInterpreter, Element actionElement, Scope actionScope, boolean hasOverriddenContext, Item overriddenContext) { final XFormsContainingDocument containingDocument = actionInterpreter.containingDocument(); final String resourceAttributeValue = actionElement.attributeValue(XFormsConstants.RESOURCE_QNAME); final String showAttribute; { final String rawShowAttribute = actionInterpreter.resolveAVT(actionElement, "show"); showAttribute = (rawShowAttribute == null) ? "replace" : rawShowAttribute; if (!("replace".equals(showAttribute) || "new".equals(showAttribute))) throw new OXFException("Invalid value for 'show' attribute on xf:load element: " + showAttribute); } final boolean doReplace = "replace".equals(showAttribute); final String target = actionInterpreter.resolveAVT(actionElement, XFormsConstants.XXFORMS_TARGET_QNAME); final String urlType = actionInterpreter.resolveAVT(actionElement, XMLConstants.FORMATTING_URL_TYPE_QNAME); final boolean urlNorewrite = XFormsUtils.resolveUrlNorewrite(actionElement); final boolean isShowProgress = !"false".equals(actionInterpreter.resolveAVT(actionElement, XFormsConstants.XXFORMS_SHOW_PROGRESS_QNAME)); // "If both are present, the action has no effect." final BindingContext bindingContext = actionInterpreter.actionXPathContext().getCurrentBindingContext(); if (bindingContext.newBind() && resourceAttributeValue != null) return; if (bindingContext.newBind()) { // Use single-node binding final String tempValue = DataModel.getValue(bindingContext.getSingleItem()); if (tempValue != null) { final String encodedValue = NetUtils.encodeHRRI(tempValue, true); resolveStoreLoadValue(containingDocument, actionElement, doReplace, encodedValue, target, urlType, urlNorewrite, isShowProgress); } else { // The action is a NOP if it's not bound to a node } // NOTE: We are supposed to throw an xforms-link-error in case of failure. Can we do it? } else if (resourceAttributeValue != null) { // Use resource attribute // NOP if there is an AVT but no context node if (bindingContext.singleItemOpt().isEmpty() && XFormsUtils.maybeAVT(resourceAttributeValue)) return; // Resolve AVT final String resolvedResource = actionInterpreter.resolveAVTProvideValue(actionElement, resourceAttributeValue); if (resolvedResource == null) { final IndentedLogger indentedLogger = actionInterpreter.indentedLogger(); if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug("xf:load", "resource AVT returned an empty sequence, ignoring action", "resource", resourceAttributeValue); return; } final String encodedResource = NetUtils.encodeHRRI(resolvedResource, true); resolveStoreLoadValue(containingDocument, actionElement, doReplace, encodedResource, target, urlType, urlNorewrite, isShowProgress); // NOTE: We are supposed to throw an xforms-link-error in case of failure. Can we do it? } else { // "Either the single node binding attributes, pointing to a URI in the instance // data, or the linking attributes are required." throw new OXFException("Missing 'resource' or 'ref' attribute on xf:load element."); } } public static void resolveStoreLoadValue(XFormsContainingDocument containingDocument, Element currentElement, boolean doReplace, String value, String target, String urlType, boolean urlNorewrite, boolean isShowProgress) { final String externalURL; if (value.startsWith("#") || urlNorewrite) { // Keep value unchanged if it's just a fragment or if we are explicitly disabling rewriting // TODO: Not clear what happens in portlet mode: does norewrite make any sense? externalURL = value; } else { // URL must be resolved if ("resource".equals(urlType)) { // Load as resource URL externalURL = XFormsUtils.resolveResourceURL(containingDocument, currentElement, value, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE); } else { // Load as render URL // Cases for `show="replace"` and `render` URLs: // // 1. Servlet in non-embedded mode // 1. Upon initialization // - URL is rewritten to absolute URL // - `XFormsToXHTML` calls `getResponse.sendRedirect()` // - just use the absolute `location` URL without any further processing // 2. Upon Ajax request // - URL is rewritten to absolute URL // - `xxf:load` sent to client in Ajax response // - client does `window.location.href = ...` or `window.open()` // 2. Servlet in embedded mode (with client proxy portlet or embedding API) // 1. Upon initialization // - URL is first rewritten to a absolute path without context (resolving of `resolveXMLBase`) // - URL is not WSRP-encoded // - `XFormsToXHTML` calls `getResponse.sendRedirect()` // - `ServletExternalContext.getResponse.sendRedirect()` // - rewrite the path to an absolute path including context // - add `orbeon-embeddable=true` // - embedding client performs HTTP redirect // 2. Upon Ajax request // - URL is WSRP-encoded // - `xxf:load` sent to client in Ajax response // - client does `window.location.href = ...` or `window.open()` // 3. Portlet // 1. Upon initialization // - not handled (https://github.com/orbeon/orbeon-forms/issues/2617) // 2. Upon Ajax // - perform a "two-pass load" // - URL is first rewritten to a absolute path without context (resolving of `resolveXMLBase`) // - URL is not WSRP-encoded // - `XFormsServer` adds a server event with `xxforms-load` dispatched to `#document` // - client performs form submission (`action`) which includes server events // - server dispatches incoming `xxforms-load` to `XXFormsRootControl` // - `XXFormsRootControl` performs `getResponse.sendRedirect()` // - `OrbeonPortlet` creates `Redirect()` object // - `BufferedPortlet.bufferedProcessAction()` sets new render parameters // - portlet then renders with new path set by the redirection // // Questions/suggestions: // // - why do we handle the Ajax case differently in embedded vs. portlet modes? // - it's unclear which parts of the rewriting must take place here vs. in `sendRedirect()` // - make clearer what's stored with `addLoadToRun()` (`location` is not clear enough) // final boolean skipRewrite; if (! containingDocument.isPortletContainer()) { // Servlet container if (! containingDocument.isEmbedded()) { // Not embedded skipRewrite = false; } else { // Embedded if (containingDocument.isInitializing()) { skipRewrite = true; } else { skipRewrite = false; } } } else { // Portlet container // NOTE: As of 2016-03-17, the initialization case will fail and the portlet will throw an exception. skipRewrite = true; } externalURL = XFormsUtils.resolveRenderURL(containingDocument, currentElement, value, skipRewrite); } } // Force no progress indication if this is a JavaScript URL if (externalURL.startsWith("javascript:")) isShowProgress = false; containingDocument.addLoadToRun(externalURL, target, urlType, doReplace, isShowProgress); } }