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
26package com.sun.org.apache.xpath.internal.jaxp;
27
28import com.sun.org.apache.xalan.internal.res.XSLMessages;
29import com.sun.org.apache.xalan.internal.utils.FactoryImpl;
30import com.sun.org.apache.xml.internal.dtm.DTM;
31import com.sun.org.apache.xpath.internal.axes.LocPathIterator;
32import com.sun.org.apache.xpath.internal.objects.XObject;
33import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
34import java.io.IOException;
35import javax.xml.namespace.QName;
36import javax.xml.parsers.DocumentBuilderFactory;
37import javax.xml.parsers.ParserConfigurationException;
38import javax.xml.transform.TransformerException;
39import javax.xml.xpath.XPathConstants;
40import javax.xml.xpath.XPathEvaluationResult;
41import javax.xml.xpath.XPathExpressionException;
42import javax.xml.xpath.XPathFunctionResolver;
43import javax.xml.xpath.XPathNodes;
44import javax.xml.xpath.XPathVariableResolver;
45import jdk.xml.internal.JdkXmlFeatures;
46import org.w3c.dom.Document;
47import org.w3c.dom.Node;
48import org.w3c.dom.traversal.NodeIterator;
49import org.xml.sax.InputSource;
50import org.xml.sax.SAXException;
51
52/**
53 * This class contains several utility methods used by XPathImpl and
54 * XPathExpressionImpl
55 */
56class 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}
270