de.betterform.xml.xforms.action.LoadAction.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.xml.xforms.action.LoadAction.java

Source

/*
 * Copyright (c) 2012. betterFORM Project - http://www.betterform.de
 * Licensed under the terms of BSD License
 */

package de.betterform.xml.xforms.action;

import de.betterform.connector.ConnectorFactory;
import de.betterform.connector.URIResolver;
import de.betterform.xml.config.Config;
import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.events.BetterFormEventNames;
import de.betterform.xml.events.XFormsEventNames;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.ns.NamespaceResolver;
import de.betterform.xml.xforms.Initializer;
import de.betterform.xml.xforms.XFormsConstants;
import de.betterform.xml.xforms.XFormsProcessor;
import de.betterform.xml.xforms.exception.XFormsException;
import de.betterform.xml.xforms.model.Instance;
import de.betterform.xml.xforms.model.Model;
import de.betterform.xml.xforms.model.submission.AttributeOrValueChild;
import de.betterform.xml.xforms.ui.RepeatItem;
import de.betterform.xml.xpath.impl.saxon.XPathUtil;
import de.betterform.xml.xslt.TransformerService;
import de.betterform.xml.xslt.impl.CachingTransformerService;
import net.sf.saxon.dom.NodeWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Implements the action as defined in <code>10.1.8 The load Element</code>.
 *
 * @author Ulrich Nicolas Liss&eacute;, Nick van den Bleeken, Joern Turner, Lars Windauer
 * @version $Id: LoadAction.java 3477 2008-08-19 09:26:47Z joern $
 */
public class LoadAction extends AbstractBoundAction {
    private static final Log LOGGER = LogFactory.getLog(LoadAction.class);
    private AttributeOrValueChild resource;
    private String showAttribute = null;
    private String targetAttribute = null;

    /**
     * Creates a load action implementation.
     *
     * @param element the element.
     * @param model   the context model.
     */
    public LoadAction(Element element, Model model) {
        super(element, model);
    }

    // lifecycle methods

    /**
     * Performs element init.
     */
    public void init() throws XFormsException {
        super.init();

        this.resource = new AttributeOrValueChild(this.element, this.model, RESOURCE_ATTRIBUTE);
        this.resource.init();

        this.showAttribute = getXFormsAttribute(SHOW_ATTRIBUTE);
        if (this.showAttribute == null) {
            // default
            this.showAttribute = "replace";
        }

        this.targetAttribute = getXFormsAttribute(TARGETID_ATTRIBUTE);

    }

    // implementation of 'de.betterform.xml.xforms.action.XFormsAction'

    /**
     * Performs the <code>load</code> action.
     *
     * @throws XFormsException if an error occurred during <code>load</code>
     *                         processing.
     */
    public void perform() throws XFormsException {
        String bindAttribute = getXFormsAttribute(BIND_ATTRIBUTE);
        String refAttribute = getXFormsAttribute(REF_ATTRIBUTE);
        boolean includeCSS = true;
        boolean includeScript = true;

        Element extension = DOMUtil.findFirstChildNS(this.element, NamespaceConstants.XFORMS_NS, EXTENSION);
        if (extension != null) {
            includeCSS = ("true".equalsIgnoreCase(getXFormsAttribute(extension, "includeCSS"))) ? true : false;
            includeScript = ("true".equalsIgnoreCase(getXFormsAttribute(extension, "includeScript"))) ? true
                    : false;
        }

        if (this.resource.isAvailable() && (bindAttribute != null || refAttribute != null)) {
            // issue warning
            getLogger().warn(this
                    + " perform: since both binding and linking attributes are present this action has no effect");
            return;
        }

        // obtain relative URI
        String relativeURI;
        String absoluteURI = null;
        try {
            //todo: needs refactoring
            updateXPathContext();
            if ("none".equals(this.showAttribute)) {
                //todo: dirty dirty dirty
                String evaluatedTarget = evalAttributeValueTemplates(this.targetAttribute, this.element);
                //                Element targetElem = this.container.getElementById(evaluatedTarget);
                Element targetElem = getTargetElement(evaluatedTarget);
                destroyembeddedModels(targetElem);
                DOMUtil.removeAllChildren(targetElem);
                HashMap map = new HashMap();

                map.put("show", this.showAttribute);
                if (isRepeated()) {
                    map.put("nameTarget", evaluatedTarget);
                    String idForNamedElement = getXFormsAttribute(getTargetElement(evaluatedTarget), "id");
                    map.put("xlinkTarget", idForNamedElement);

                } else {
                    map.put("xlinkTarget", evaluatedTarget);
                }

                this.container.dispatch(this.target, BetterFormEventNames.LOAD_URI, map);
                return;
            }

            if (this.resource.isAvailable()) {
                relativeURI = this.resource.getValue();
            } else {

                if (this.nodeset == null || this.nodeset.size() == 0) {
                    getLogger().warn(this + " perform: nodeset '" + getLocationPath() + "' is empty");
                    return;
                }

                final Instance instance = this.model.getInstance(getInstanceId());
                relativeURI = instance.getNodeValue(XPathUtil.getAsNode(this.nodeset, 1));
            }

            // resolve uri
            absoluteURI = this.container.getConnectorFactory().getAbsoluteURI(relativeURI, this.element).toString();

            HashMap map = new HashMap();
            handleEmbedding(absoluteURI, map, includeCSS, includeScript);

        } catch (XFormsException e) {
            LOGGER.error("Error performing xf:load action at: " + DOMUtil.getCanonicalPath(this.getElement()));
            throw new XFormsException(
                    "Error performing xf:load action at: " + DOMUtil.getCanonicalPath(this.getElement()), e);
        }

        // backwards compat
        //        storeInContext(absoluteURI, this.showAttribute);
    }

    private void handleEmbedding(String absoluteURI, HashMap map, boolean includeCSS, boolean includeScript)
            throws XFormsException {

        //        if(this.targetAttribute == null) return;
        //todo: handle multiple params
        if (absoluteURI.indexOf("?") > 0) {
            String name = absoluteURI.substring(absoluteURI.indexOf("?") + 1);
            String value = name.substring(name.indexOf("=") + 1, name.length());
            name = name.substring(0, name.indexOf("="));
            this.container.getProcessor().setContextParam(name, value);
        }
        if (this.targetAttribute == null) {
            map.put("uri", absoluteURI);
            map.put("show", this.showAttribute);

            // dispatch internal betterform event
            this.container.dispatch(this.target, BetterFormEventNames.LOAD_URI, map);
            return;
        }

        if (!("embed".equals(this.showAttribute))) {
            throw new XFormsException("@show must be 'embed' if @target is given but was: '" + this.showAttribute
                    + "' on Element " + DOMUtil.getCanonicalPath(this.element));
        }
        //todo: dirty dirty dirty
        String evaluatedTarget = evalAttributeValueTemplates(this.targetAttribute, this.element);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("targetid evaluated to: " + evaluatedTarget);
        }
        Node embed = getEmbeddedDocument(absoluteURI);
        if (embed == null) {
            //todo: review: context info params containing a '-' fail during access in javascript!
            //todo: the following property should have been 'resource-uri' according to spec
            map.put("resourceUri", absoluteURI);
            this.container.dispatch(this.target, XFormsEventNames.LINK_EXCEPTION, map);
            return;
        }

        // Check for 'ClientSide' events (DOMFocusIN / DOMFocusOUT / xforms-select)
        String utilizedEvents = this.getEventsInSubform(embed);

        // fetch CSS / JavaScript
        String inlineCssRules = "";
        String externalCssRules = "";
        String inlineJavaScript = "";
        String externalJavaScript = "";

        if (includeCSS) {
            inlineCssRules = getInlineCSS(embed);
            externalCssRules = getExternalCSS(embed);
        }
        if (includeScript) {
            inlineJavaScript = getInlineJavaScript(embed);
            externalJavaScript = getExternalJavaScript(embed);
        }

        if (Config.getInstance().getProperty("webprocessor.doIncludes", "false").equals("true")) {
            embed = doIncludes(embed, absoluteURI);
        }

        embed = extractFragment(absoluteURI, embed);
        if (LOGGER.isDebugEnabled()) {
            DOMUtil.prettyPrintDOM(embed);
        }

        Element embeddedNode;
        Element targetElem = getTargetElement(evaluatedTarget);

        //Test if targetElem exist.
        if (targetElem != null) {
            // destroy existing embedded form within targetNode
            if (targetElem.hasChildNodes()) {
                destroyembeddedModels(targetElem);
                Initializer.disposeUIElements(targetElem);
            }

            // import referenced embedded form into host document
            embeddedNode = (Element) this.container.getDocument().importNode(embed, true);

            //import namespaces
            NamespaceResolver.applyNamespaces(targetElem.getOwnerDocument().getDocumentElement(),
                    (Element) embeddedNode);

            // keep original targetElem id within hostdoc
            embeddedNode.setAttributeNS(null, "id", targetElem.getAttributeNS(null, "id"));
            //copy all Attributes that might have been on original mountPoint to embedded node
            DOMUtil.copyAttributes(targetElem, embeddedNode, null);
            targetElem.getParentNode().replaceChild(embeddedNode, targetElem);

            //create model for it
            this.container.createEmbeddedForm((Element) embeddedNode);
            // dispatch internal betterform event
            this.container.dispatch((EventTarget) embeddedNode, BetterFormEventNames.EMBED_DONE, map);

            if (getModel().isReady()) {
                map.put("uri", absoluteURI);
                map.put("show", this.showAttribute);
                map.put("targetElement", embeddedNode);
                map.put("nameTarget", evaluatedTarget);
                map.put("xlinkTarget", getXFormsAttribute(targetElem, "id"));
                map.put("inlineCSS", inlineCssRules);
                map.put("externalCSS", externalCssRules);
                map.put("inlineJavascript", inlineJavaScript);
                map.put("externalJavascript", externalJavaScript);
                map.put("utilizedEvents", utilizedEvents);

                this.container.dispatch((EventTarget) embeddedNode, BetterFormEventNames.EMBED, map);
                this.container.dispatch(this.target, BetterFormEventNames.LOAD_URI, map);
            }

            //        storeInContext(absoluteURI, this.showAttribute);
        } else {
            String message = "Element reference for targetid: " + this.targetAttribute + " not found. "
                    + DOMUtil.getCanonicalPath(this.element);
            /*
            Element div = this.element.getOwnerDocument().createElementNS("", "div");
            div.setAttribute("class", "bfException");
            div.setTextContent("Element reference for targetid: " + this.targetAttribute + "not found. " + DOMUtil.getCanonicalPath(this.element));
                
            Element body = (Element) this.container.getDocument().getElementsByTagName("body").item(0);
            body.insertBefore(div, DOMUtil.getFirstChildElement(body));
            */
            map.put("message", message);
            map.put("exceptionPath", DOMUtil.getCanonicalPath(this.element));
            this.container.dispatch(this.target, BetterFormEventNames.EXCEPTION, map);
            //throw new XFormsException(message);
        }
    }

    private String getEventsInSubform(Node embed) throws XFormsException {
        StringBuilder events = new StringBuilder();
        if (embed instanceof Document) {
            embed = ((Document) embed).getDocumentElement();
        }
        try {
            if (XPathUtil.evaluate((Element) embed, "//*[@ev:event='xforms-select']").size() != 0) {
                events.append("useXFSelect:true");
            }
        } catch (Exception e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("no xforms-select event listener in form");
            }
        }
        try {
            if (XPathUtil.evaluate((Element) embed, "//*[@ev:event='DOMFocusIn']").size() != 0) {
                if (events.length() != 0) {
                    events.append(", useDOMFocusIN:true");
                } else {
                    events.append("useDOMFocusIN:true");
                }
            }
        } catch (Exception e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("no DOMFocusIn event listener in form");
            }
        }
        try {
            if (XPathUtil.evaluate((Element) embed, "//*[@ev:event='DOMFocusOut']").size() != 0) {
                if (events.length() != 0) {
                    events.append(", useDOMFocusOUT:true");
                } else {
                    events.append("useDOMFocusOUT:true");
                }
            }
        } catch (Exception e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("no DOMFocusOut event listener in form");
            }
        }
        return events.toString();
    }

    private Node doIncludes(Node embed, String absoluteURI) {
        LOGGER.debug("LoadAction.doInclude: Node to embed: " + embed.toString() + " URI: " + absoluteURI);
        try {
            String uri = absoluteURI.substring(0, absoluteURI.lastIndexOf("/") + 1);

            CachingTransformerService transformerService = (CachingTransformerService) this.container.getProcessor()
                    .getContext().get(TransformerService.TRANSFORMER_SERVICE);
            Transformer transformer = transformerService.getTransformerByName("include.xsl");

            DOMSource source = new DOMSource(embed);
            DOMResult result = new DOMResult();
            transformer.setParameter("root", uri);
            transformer.transform(source, result);

            return result.getNode();
        } catch (TransformerException e) {
            LOGGER.debug("LoadAction.doInclude: TransformerException:", e);
        }
        return embed;
    }

    private String getInlineCSS(Node embed) throws XFormsException {
        //fetch style element(s) from Node to embed
        String cssRules = "";
        if (embed instanceof Document) {
            embed = ((Document) embed).getDocumentElement();
        }
        List result = XPathUtil.evaluate((Element) embed, "//*[@type='text/css' and (not(boolean(@href)))]");
        if (result.size() == 0) {
            return null;
        }
        for (int i = 0; i < result.size(); i++) {
            Object item = result.get(i);
            if (result.get(i) instanceof NodeWrapper) {
                NodeWrapper wrapper = (NodeWrapper) item;
                Node n = (Node) wrapper.getUnderlyingNode();
                cssRules += DOMUtil.getTextNodeAsString(n);
            }
        }
        return cssRules;
    }

    private String getExternalCSS(Node embed) throws XFormsException {
        //fetch style element(s) from Node to embed
        String cssRules = "";
        if (embed instanceof Document) {
            embed = ((Document) embed).getDocumentElement();
        }
        List result = XPathUtil.evaluate((Element) embed, "//*[@type='text/css' and boolean(@href)]");
        if (result.size() == 0) {
            return null;
        }

        for (int i = 0; i < result.size(); i++) {
            Object item = result.get(i);
            if (result.get(i) instanceof NodeWrapper) {
                NodeWrapper wrapper = (NodeWrapper) item;
                Node n = (Node) wrapper.getUnderlyingNode();
                cssRules += n.getAttributes().getNamedItem("href").getNodeValue() + "#";
            }
        }
        return cssRules;
    }

    private String getInlineJavaScript(Node embed) throws XFormsException {
        //fetch style element(s) from Node to embed
        String javaScriptCode = "";
        if (embed instanceof Document) {
            embed = ((Document) embed).getDocumentElement();
        }

        //Get all inline script ignoring script that folllows a xforms:node
        List result = XPathUtil.evaluate((Element) embed,
                "//*[not( namespace-uri()='http://www.w3.org/2002/xforms' )]/*[(@type='text/javascript') and (not(boolean(@src)))]");
        if (result.size() == 0) {
            return null;
        }
        for (int i = 0; i < result.size(); i++) {
            Object item = result.get(i);
            if (result.get(i) instanceof NodeWrapper) {
                NodeWrapper wrapper = (NodeWrapper) item;
                if (!"action".equals(((NodeWrapper) item).getParent().getLocalPart())) {
                    Node n = (Node) wrapper.getUnderlyingNode();
                    javaScriptCode += DOMUtil.getTextNodeAsString(n);
                }
            }
        }

        return javaScriptCode;
    }

    private String getExternalJavaScript(Node embed) throws XFormsException {
        String javaScriptCode = "";
        if (embed instanceof Document) {
            embed = ((Document) embed).getDocumentElement();
        }

        List result = XPathUtil.evaluate((Element) embed, "//*[(@type='text/javascript') and (boolean(@src)) ]");
        for (int i = 0; i < result.size(); i++) {
            Object item = result.get(i);
            if (result.get(i) instanceof NodeWrapper) {
                NodeWrapper wrapper = (NodeWrapper) item;
                Node n = (Node) wrapper.getUnderlyingNode();
                javaScriptCode += n.getAttributes().getNamedItem("src").getNodeValue() + "#";
            }
        }

        return javaScriptCode;
    }

    /**
     * fetches the Element that is the target for embedding. This Element will be replaced by the markup
     * to be embedded or all of its contents is erased in case of an 'unload'.
     *
     * @param evaluatedTarget the targetid of a load action can be a AVT expression and be evaluated. The result
     *                        is treated as an idref to an Element.
     * @return
     * @throws XFormsException
     */
    private Element getTargetElement(String evaluatedTarget) throws XFormsException {
        Element targetElem = null;
        if (isRepeated()) {
            String itemId = getRepeatItemId();
            RepeatItem item = (RepeatItem) container.lookup(itemId);
            int pos = item.getPosition();
            targetElem = (Element) XPathUtil.evaluateAsSingleNode(item.getElement().getOwnerDocument(),
                    "//*[@name='" + evaluatedTarget + "']");
        } else {
            // ##### try to interpret the targetAttribute value as an idref ##### -->
            targetElem = this.container.getElementById(evaluatedTarget);
        }
        return targetElem;
    }

    /**
     * fetches a resources with the help of a Connector. For HTML the response will be a string containing markup that
     * needs to be parsed.
     * <br><br>
     * If a fragment is given on the URI only the resulting part of the response is returned.
     *
     * @param absoluteURI
     * @return a Node
     * @throws XFormsException
     */
    private Node getEmbeddedDocument(String absoluteURI) throws XFormsException {
        //load resource from absoluteURI
        ConnectorFactory factory = ConnectorFactory.getFactory();
        factory.setContext(this.container.getProcessor().getContext());
        URIResolver uriResolver = factory.createURIResolver(absoluteURI, this.element);

        Object answer = uriResolver.resolve();
        Node embed = null;
        if (answer instanceof Node) {
            embed = (Node) answer;
        } else if (answer instanceof String) {
            // we got some plain HTML and this is returned as a string
            try {
                embed = DOMUtil.parseString((String) answer, true, false);
            } catch (ParserConfigurationException e) {
                throw new XFormsException(e);
            } catch (IOException e) {
                throw new XFormsException(e);
            } catch (SAXException e) {
                throw new XFormsException(e);
            }

        }
        //        embed = extractFragment(absoluteURI, embed);
        return embed;
    }

    private Node extractFragment(String absoluteURI, Node embed) throws XFormsException {
        if (embed instanceof Document && absoluteURI.indexOf("#") != -1) {

            String s = absoluteURI.substring(absoluteURI.indexOf("#") + 1);
            if (s.indexOf("?") != -1) {
                s = s.substring(0, s.indexOf("?"));
            }
            embed = DOMUtil.getById((Document) embed, s);
        }

        if (embed == null) {
            throw new XFormsException("content returned from URI could not be recognized");
        }
        if (embed instanceof Document) {
            embed = ((Document) embed).getDocumentElement();
        }
        return embed;
    }

    private void destroyembeddedModels(Element targetElem) throws XFormsException {
        NodeList childNodes = targetElem.getChildNodes();

        for (int index = 0; index < childNodes.getLength(); index++) {
            Node node = childNodes.item(index);

            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element elementImpl = (Element) node;

                String name = elementImpl.getLocalName();
                String uri = elementImpl.getNamespaceURI();

                if (NamespaceConstants.XFORMS_NS.equals(uri) && name.equals(XFormsConstants.MODEL)) {
                    Model model = (Model) elementImpl.getUserData("");
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("dispatch 'model-destruct' event to embedded model: " + model.getId());
                        ;
                    }
                    String modelId = model.getId();
                    // do not dispatch model-destruct to avoid problems in lifecycle
                    // TODO: review: this.container.dispatch(model.getTarget(), XFormsEventNames.MODEL_DESTRUCT, null);
                    model.dispose();
                    this.container.removeModel(model);
                    model = null;
                    if (Config.getInstance().getProperty("betterform.debug-allowed").equals("true")) {
                        Map contextInfo = new HashMap(1);
                        contextInfo.put("modelId", modelId);
                        this.container.dispatch(this.target, BetterFormEventNames.MODEL_REMOVED, contextInfo);
                    }

                } else {
                    destroyembeddedModels(elementImpl);
                }
            }
        }
    }

    public boolean hasBindingExpression() {
        return (getBindingExpression() != null) || resource.isAvailable();
    }

    /**
     * Returns the logger object.
     *
     * @return the logger object.
     */
    protected Log getLogger() {
        return LOGGER;
    }

    /**
     * @deprecated backwards compat
     */
    private void storeInContext(String uri, String show) {
        // store in context
        this.container.getProcessor().getContext().put(XFormsProcessor.LOAD_URI, uri);
        this.container.getProcessor().getContext().put(XFormsProcessor.LOAD_TARGET, show);
    }
}

// end of class