1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.org.apache.xpath.internal.jaxp;
  27 
  28 import com.sun.org.apache.xalan.internal.res.XSLMessages;
  29 import com.sun.org.apache.xalan.internal.utils.FactoryImpl;
  30 import com.sun.org.apache.xml.internal.dtm.DTM;
  31 import com.sun.org.apache.xpath.internal.axes.LocPathIterator;
  32 import com.sun.org.apache.xpath.internal.objects.XObject;
  33 import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
  34 import java.io.IOException;
  35 import javax.xml.namespace.QName;
  36 import javax.xml.parsers.DocumentBuilderFactory;
  37 import javax.xml.parsers.ParserConfigurationException;
  38 import javax.xml.transform.TransformerException;
  39 import javax.xml.xpath.XPathConstants;
  40 import javax.xml.xpath.XPathEvaluationResult;
  41 import javax.xml.xpath.XPathExpressionException;
  42 import javax.xml.xpath.XPathFunctionResolver;
  43 import javax.xml.xpath.XPathNodes;
  44 import javax.xml.xpath.XPathVariableResolver;
  45 import jdk.xml.internal.JdkXmlFeatures;
  46 import org.w3c.dom.Document;
  47 import org.w3c.dom.Node;
  48 import org.w3c.dom.traversal.NodeIterator;
  49 import org.xml.sax.InputSource;
  50 import org.xml.sax.SAXException;
  51 
  52 /**
  53  * This class contains several utility methods used by XPathImpl and
  54  * XPathExpressionImpl
  55  */
  56 class XPathImplUtil {
  57     XPathFunctionResolver functionResolver;
  58     XPathVariableResolver variableResolver;
  59     JAXPPrefixResolver prefixResolver;
  60     boolean useServiceMechanism = true;
  61     // By default Extension Functions are allowed in XPath Expressions. If
  62     // Secure Processing Feature is set on XPathFactory then the invocation of
  63     // extensions function need to throw XPathFunctionException
  64     boolean featureSecureProcessing = false;
  65     JdkXmlFeatures featureManager;
  66 
  67     /**
  68      * Evaluate an XPath context using the internal XPath engine
  69      * @param contextItem The XPath context
  70      * @param xpath The internal XPath engine
  71      * @return an XObject
  72      * @throws javax.xml.transform.TransformerException If the expression cannot be evaluated.
  73      */
  74     XObject eval(Object contextItem, com.sun.org.apache.xpath.internal.XPath xpath)
  75             throws javax.xml.transform.TransformerException {
  76         com.sun.org.apache.xpath.internal.XPathContext xpathSupport;
  77         if (contextItem == null && xpath.getExpression() instanceof LocPathIterator) {
  78             // the operation must have no dependency on the context that is null
  79             throw new TransformerException(XSLMessages.createXPATHMessage(
  80                     XPATHErrorResources.ER_CONTEXT_CAN_NOT_BE_NULL,
  81                     new Object[] {}));
  82         }
  83         if (functionResolver != null) {
  84             JAXPExtensionsProvider jep = new JAXPExtensionsProvider(
  85                     functionResolver, featureSecureProcessing, featureManager);
  86             xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext(jep);
  87         } else {
  88             xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext();
  89         }
  90 
  91         xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
  92         XObject xobj;
  93 
  94         Node contextNode = (Node)contextItem;
  95         // We always need to have a ContextNode with Xalan XPath implementation
  96         // To allow simple expression evaluation like 1+1 we are setting
  97         // dummy Document as Context Node
  98         if (contextNode == null) {
  99             xobj = xpath.execute(xpathSupport, DTM.NULL, prefixResolver);
 100         } else {
 101             xobj = xpath.execute(xpathSupport, contextNode, prefixResolver);
 102         }
 103 
 104         return xobj;
 105     }
 106 
 107     /**
 108      * Parse the input source and return a Document.
 109      * @param source The {@code InputSource} of the document
 110      * @return a DOM Document
 111      * @throws XPathExpressionException if there is an error parsing the source.
 112      */
 113     Document getDocument(InputSource source)
 114         throws XPathExpressionException {
 115         requireNonNull(source, "Source");
 116         try {
 117             // we'd really like to cache those DocumentBuilders, but we can't because:
 118             // 1. thread safety. parsers are not thread-safe, so at least
 119             //    we need one instance per a thread.
 120             // 2. parsers are non-reentrant, so now we are looking at having a
 121             // pool of parsers.
 122             // 3. then the class loading issue. The look-up procedure of
 123             //    DocumentBuilderFactory.newInstance() depends on context class loader
 124             //    and system properties, which may change during the execution of JVM.
 125             //
 126             // so we really have to create a fresh DocumentBuilder every time we need one
 127             // - KK
 128             DocumentBuilderFactory dbf = FactoryImpl.getDOMFactory(useServiceMechanism);
 129             dbf.setNamespaceAware(true);
 130             dbf.setValidating(false);
 131             return dbf.newDocumentBuilder().parse(source);
 132         } catch (ParserConfigurationException | SAXException | IOException e) {
 133             throw new XPathExpressionException (e);
 134         }
 135     }
 136 
 137     /**
 138      * Get result depending on the QName type defined in XPathConstants
 139      * @param resultObject the result of an evaluation
 140      * @param returnType the return type
 141      * @return result per the return type
 142      * @throws TransformerException if the result can not be converted to
 143      * the specified return type.
 144      */
 145     Object getResultAsType(XObject resultObject, QName returnType)
 146         throws TransformerException {
 147         // XPathConstants.STRING
 148         if (returnType.equals(XPathConstants.STRING)) {
 149             return resultObject.str();
 150         }
 151         // XPathConstants.NUMBER
 152         if (returnType.equals(XPathConstants.NUMBER)) {
 153             return resultObject.num();
 154         }
 155         // XPathConstants.BOOLEAN
 156         if (returnType.equals(XPathConstants.BOOLEAN)) {
 157             return resultObject.bool();
 158         }
 159         // XPathConstants.NODESET ---ORdered, UNOrdered???
 160         if (returnType.equals(XPathConstants.NODESET)) {
 161             return resultObject.nodelist();
 162         }
 163         // XPathConstants.NODE
 164         if (returnType.equals(XPathConstants.NODE)) {
 165             NodeIterator ni = resultObject.nodeset();
 166             //Return the first node, or null
 167             return ni.nextNode();
 168         }
 169         // If isSupported check is already done then the execution path
 170         // shouldn't come here. Being defensive
 171         String fmsg = XSLMessages.createXPATHMessage(
 172                 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
 173                 new Object[] { returnType.toString()});
 174         throw new IllegalArgumentException (fmsg);
 175     }
 176 
 177     /**
 178      * Construct an XPathExpressionResult object based on the result of the
 179      * evaluation and cast to the specified class type.
 180      * @param <T> The class type
 181      * @param resultObject the result of an evaluation
 182      * @param type The class type expected to be returned by the XPath expression.
 183      * @return an instance of the specified type or null if the XObject returned
 184      * an UNKNOWN object type.
 185      * @throws TransformerException if there is an error converting the result
 186      * to the specified type. It's unlikely to happen. This is mostly needed
 187      * by the internal XPath engine.
 188      */
 189     <T> T getXPathResult(XObject resultObject, Class<T> type)
 190             throws TransformerException {
 191         int resultType = resultObject.getType();
 192 
 193         switch (resultType) {
 194             case XObject.CLASS_BOOLEAN :
 195                 return type.cast(new XPathResultImpl<>(resultObject, Boolean.class));
 196             case XObject.CLASS_NUMBER :
 197                 return type.cast(new XPathResultImpl<>(resultObject, Double.class));
 198             case XObject.CLASS_STRING :
 199                 return type.cast(new XPathResultImpl<>(resultObject, String.class));
 200             case XObject.CLASS_NODESET :
 201                 return type.cast(new XPathResultImpl<>(resultObject, XPathNodes.class));
 202             case XObject.CLASS_RTREEFRAG :  //NODE
 203                 return type.cast(new XPathResultImpl<>(resultObject, Node.class));
 204         }
 205 
 206         return null;
 207     }
 208 
 209     /**
 210      * Check whether or not the specified type is supported
 211      * @param <T> The class type
 212      * @param type The type to be checked
 213      * @throws IllegalArgumentException if the type is not supported
 214      */
 215     <T> void isSupportedClassType(Class<T> type) {
 216         requireNonNull(type, "The class type");
 217         if (type.isAssignableFrom(Boolean.class) ||
 218                 type.isAssignableFrom(Double.class) ||
 219                 type.isAssignableFrom(Integer.class) ||
 220                 type.isAssignableFrom(Long.class) ||
 221                 type.isAssignableFrom(String.class) ||
 222                 type.isAssignableFrom(XPathNodes.class) ||
 223                 type.isAssignableFrom(Node.class) ||
 224                 type.isAssignableFrom(XPathEvaluationResult.class)) {
 225             return;
 226         }
 227         String fmsg = XSLMessages.createXPATHMessage(
 228                 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
 229                 new Object[] { type.toString() });
 230         throw new IllegalArgumentException (fmsg);
 231     }
 232 
 233     /**
 234      * Check if the requested returnType is supported.
 235      * @param returnType the return type
 236      * @throws IllegalArgumentException if the return type is not supported
 237      */
 238     void isSupported(QName returnType) {
 239         requireNonNull(returnType, "returnType");
 240         if (returnType.equals(XPathConstants.STRING) ||
 241                 returnType.equals(XPathConstants.NUMBER) ||
 242                 returnType.equals(XPathConstants.BOOLEAN) ||
 243                 returnType.equals(XPathConstants.NODE) ||
 244                 returnType.equals(XPathConstants.NODESET)) {
 245             return;
 246         }
 247         String fmsg = XSLMessages.createXPATHMessage(
 248                 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
 249                 new Object[] { returnType.toString() });
 250         throw new IllegalArgumentException (fmsg);
 251      }
 252 
 253     /**
 254      * Checks that the specified parameter is not {@code null}.
 255      *
 256      * @param <T> the type of the reference
 257      * @param param the parameter to check for nullity
 258      * @param paramName the parameter name
 259      * @throws NullPointerException if {@code param} is {@code null}
 260      */
 261     <T> void requireNonNull(T param, String paramName) {
 262         if (param == null) {
 263             String fmsg = XSLMessages.createXPATHMessage(
 264                     XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
 265                     new Object[] {paramName});
 266             throw new NullPointerException (fmsg);
 267         }
 268     }
 269 }