org.wso2.carbon.humantask.core.engine.runtime.xpath.XPathExpressionRuntime.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.humantask.core.engine.runtime.xpath.XPathExpressionRuntime.java

Source

/*
 * Copyright (c) 2012, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.humantask.core.engine.runtime.xpath;

import net.sf.saxon.value.DurationValue;
import net.sf.saxon.xpath.XPathEvaluator;
import net.sf.saxon.xpath.XPathFactoryImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.*;
import org.wso2.carbon.humantask.core.engine.runtime.api.EvaluationContext;
import org.wso2.carbon.humantask.core.engine.runtime.api.ExpressionLanguageRuntime;
import org.wso2.carbon.humantask.core.engine.runtime.api.HumanTaskRuntimeException;
import org.wso2.carbon.humantask.core.utils.DOMUtils;
import org.wso2.carbon.humantask.core.utils.Duration;
import org.wso2.carbon.humantask.core.utils.ISO8601DateParser;

import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.util.*;

/**
 * Implementation for XPath 2.0
 */
public class XPathExpressionRuntime implements ExpressionLanguageRuntime {

    private static final Log log = LogFactory.getLog(ExpressionLanguageRuntime.class);

    /**
     * Xpath 2 name space
     */
    public static final String ns = XPath2Constants.WSHT_EXP_LANG_XPATH20;
    private static final String JAVAX_XML_XPATH_XPATH_FACTORY = "javax.xml.xpath.XPathFactory:";
    private static final String NET_SF_SAXON_XPATH_XPATH_FACTORY_IMPL = "net.sf.saxon.xpath.XPathFactoryImpl";

    /**
     * Evaluate XPath expression
     *
     * @param exp     XPath expression string
     * @param evalCtx Evaluation context containing all the required context information
     * @return Return List of selected nodes or string
     */
    @Override
    public List evaluate(String exp, EvaluationContext evalCtx) {
        List result;
        Object someRes;
        try {
            someRes = evaluate(exp, evalCtx, XPathConstants.NODESET);
        } catch (Exception e) {
            someRes = evaluate(exp, evalCtx, XPathConstants.STRING);
        }
        if (someRes instanceof List) {
            result = (List) someRes;
            if (log.isDebugEnabled()) {
                log.debug("Returned list of size " + result.size());
            }

            if ((result.size() == 1) && !(result.get(0) instanceof Node)) {
                // Dealing with a Java class
                Object simpleType = result.get(0);
                // Dates get a separate treatment as we don't want to call toString on them
                String textVal;
                if (simpleType instanceof Date) {
                    textVal = ISO8601DateParser.format((Date) simpleType);
                } else if (simpleType instanceof DurationValue) {
                    textVal = ((DurationValue) simpleType).getStringValue();
                } else {
                    textVal = simpleType.toString();
                }

                // Wrapping in a document
                Document document = DOMUtils.newDocument();
                // Giving our node a parent just in case it's an LValue expression
                Element wrapper = document.createElement("wrapper");
                Text text = document.createTextNode(textVal);
                wrapper.appendChild(text);
                document.appendChild(wrapper);
                result = Collections.singletonList(text);
            }
        } else if (someRes instanceof NodeList) {
            NodeList retVal = (NodeList) someRes;
            if (log.isDebugEnabled()) {
                log.debug("Returned node list of size " + retVal.getLength());
            }
            result = new ArrayList(retVal.getLength());
            for (int m = 0; m < retVal.getLength(); ++m) {
                Node val = retVal.item(m);
                if (val.getNodeType() == Node.DOCUMENT_NODE) {
                    val = ((Document) val).getDocumentElement();
                }
                result.add(val);
            }
        } else if (someRes instanceof String) {
            // Wrapping in a document
            Document document = DOMUtils.newDocument();
            Element wrapper = document.createElement("wrapper");
            Text text = document.createTextNode((String) someRes);
            wrapper.appendChild(text);
            document.appendChild(wrapper);
            result = Collections.singletonList(text);
        } else {
            result = null;
        }

        return result;
    }

    private Object evaluate(String exp, EvaluationContext evalCtx, QName type) {
        try {
            XPathFactory xpf = new XPathFactoryImpl();
            JaxpFunctionResolver funcResolve = new JaxpFunctionResolver(evalCtx);
            xpf.setXPathFunctionResolver(funcResolve);
            XPathEvaluator xpe = (XPathEvaluator) xpf.newXPath();
            xpe.setXPathFunctionResolver(funcResolve);
            xpe.setBackwardsCompatible(true);
            xpe.setNamespaceContext(evalCtx.getNameSpaceContextOfTask());
            XPathExpression xpathExpression = xpe.compile(exp);
            Node contextNode = evalCtx.getRootNode() == null ? DOMUtils.newDocument() : evalCtx.getRootNode();

            Object evalResult = xpathExpression.evaluate(contextNode, type);

            if (evalResult != null && log.isDebugEnabled()) {
                log.debug("Expression " + exp + " generate result " + evalResult + " - type="
                        + evalResult.getClass().getName());
                if (evalCtx.getRootNode() != null) {
                    log.debug("Was using context node " + DOMUtils.domToString(evalCtx.getRootNode()));
                }
            }

            return evalResult;
        } catch (XPathFactoryConfigurationException e) {
            log.error("Exception occurred while creating XPathFactory.", e);
            throw new XPathProcessingException("Exception occurred while creating XPathFactory.", e);
        } catch (XPathExpressionException e) {
            String msg = "Error evaluating XPath expression: " + exp;
            log.error(msg, e);
            throw new XPathProcessingException(msg, e);
        } catch (ParserConfigurationException e) {
            String msg = "XML Parsing error.";
            log.error(msg, e);
            throw new XPathProcessingException(msg, e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new XPathProcessingException(e.getMessage(), e);
        }
    }

    /**
     * Evaluate given XPath string and returns result as a string
     *
     * @param exp     XPath expression string
     * @param evalCtx Evaluation context containing all the required context information
     * @return String
     */
    @Override
    public String evaluateAsString(String exp, EvaluationContext evalCtx) {
        return (String) evaluate(exp, evalCtx, XPathConstants.STRING);
    }

    /**
     * Evaluate given XPath string and returns the result as Date
     *
     * @param exp     XPath expression string
     * @param evalCtx Evaluation context containing all the required context information
     * @return Calendar
     */
    @Override
    public Calendar evaluateAsDate(String exp, EvaluationContext evalCtx) {
        List literal = toList(evaluate(exp, evalCtx));
        if (literal.size() == 0) {
            throw new IllegalArgumentException("No results for expression:" + exp);
        }
        if (literal.size() > 1) {
            throw new IllegalArgumentException("Multiple results for expression:" + exp);
        }

        Object date = literal.get(0);
        if (date instanceof Calendar) {
            return (Calendar) date;
        }
        if (date instanceof Date) {
            Calendar cal = Calendar.getInstance();
            cal.setTime((Date) date);
            return cal;
        }
        if (date instanceof Element) {
            date = ((Element) date).getTextContent();
        }

        if (date instanceof Text) {
            date = ((Text) date).getTextContent();
        }

        try {
            return ISO8601DateParser.parseCal(date.toString());
        } catch (Exception ex) {
            String errmsg = "Invalid date format: " + literal;
            log.error(errmsg, ex);
            throw new IllegalArgumentException(errmsg, ex);
        }
    }

    /**
     * Evaluate given XPath string and returns the result as Date
     *
     * @param exp     XPath expression string
     * @param evalCtx Evaluation context containing all the required context information
     * @return Duration
     */
    @Override
    public Duration evaluateAsDuration(String exp, EvaluationContext evalCtx) {
        String literal = exp;
        if (!Duration.isValidExpression(exp)) {
            literal = this.evaluateAsString(exp, evalCtx);
            //TODO exp should be evaluateAsString in the first place. If it is not an xpath then it should return the
            // exp string itself
        }

        try {
            return new Duration(literal);
        } catch (Exception ex) {
            String errmsg = "Invalid duration: " + exp;
            //String errmsg = "Invalid duration: " + literal;
            log.error(errmsg, ex);
            throw new IllegalArgumentException(errmsg, ex);
        }
    }

    /**
     * Evaluate given XPath string and returns the result as boolean
     *
     * @param exp     XPath expression
     * @param evalCtx Evaluation context containing all the required context information
     * @return boolean
     */
    @Override
    public boolean evaluateAsBoolean(String exp, EvaluationContext evalCtx) {
        return (Boolean) evaluate(exp, evalCtx, XPathConstants.BOOLEAN);
    }

    /**
     * Evaluate given XPath and returns the results as a java.lang.Number
     *
     * @param exp     XPath expression
     * @param evalCtx Evaluation context containing all the required context information
     * @return Number
     */
    @Override
    public Number evaluateAsNumber(String exp, EvaluationContext evalCtx) {
        return (Number) evaluate(exp, evalCtx, XPathConstants.NUMBER);
    }

    /**
     * Evaluate the expression returns an OMElement
     *
     * @param exp      Expresion
     * @param partName Name of the part
     * @param evalCtx  EvaluationContext
     * @return Part as an Node
     */
    @Override
    public Node evaluateAsPart(String exp, String partName, EvaluationContext evalCtx) {
        Document document = DOMUtils.newDocument();
        Node node = document.createElement(partName);

        List<Node> nodeList = evaluate(exp, evalCtx);

        if (nodeList.size() == 0) {
            String errMsg = "0 nodes selected for the expression: " + exp;
            log.error(errMsg);
            throw new HumanTaskRuntimeException(errMsg);
        } else if (nodeList.size() > 1) {
            String errMsg = "More than one nodes are selected for the expression: " + exp;
            log.error(errMsg);
            throw new HumanTaskRuntimeException(errMsg);
        }

        Node partNode = nodeList.get(0);
        replaceElement((Element) node, (Element) partNode);

        return node;
    }

    private Element replaceElement(Element lval, Element src) {
        Document doc = lval.getOwnerDocument();
        NodeList nl = src.getChildNodes();
        for (int i = 0; i < nl.getLength(); ++i) {
            lval.appendChild(doc.importNode(nl.item(i), true));
        }
        NamedNodeMap attrs = src.getAttributes();
        for (int i = 0; i < attrs.getLength(); ++i) {
            Attr attr = (Attr) attrs.item(i);
            if (!attr.getName().startsWith("xmlns")) {
                lval.setAttributeNodeNS((Attr) doc.importNode(attrs.item(i), true));
                // Case of qualified attribute values, we're forced to add corresponding namespace declaration manually
                int colonIdx = attr.getValue().indexOf(":");
                if (colonIdx > 0) {
                    String prefix = attr.getValue().substring(0, colonIdx);
                    String attrValNs = src.lookupPrefix(prefix);
                    if (attrValNs != null) {
                        lval.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns:" + prefix, attrValNs);
                    }
                }
            }
        }

        return lval;
    }

    /**
     * Somewhat eases the pain of dealing with both Lists and Nodelists by converting either
     * passed as parameter to a List.
     *
     * @param nl a NodeList or a List
     * @return a List
     */
    public static List<Node> toList(Object nl) {
        if (nl == null) {
            return null;
        }
        if (nl instanceof List) {
            return (List<Node>) nl;
        }

        NodeList cnl = (NodeList) nl;
        LinkedList<Node> ll = new LinkedList<Node>();
        for (int m = 0; m < cnl.getLength(); m++) {
            ll.add(cnl.item(m));
        }
        return ll;
    }
}