1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18// $Id: XPathImpl.java 524814 2007-04-02 15:52:11Z zongaro $
19
20package org.apache.xpath.jaxp;
21
22import javax.xml.namespace.QName;
23import javax.xml.namespace.NamespaceContext;
24import javax.xml.xpath.XPathExpressionException;
25import javax.xml.xpath.XPathConstants;
26import javax.xml.xpath.XPathFunctionResolver;
27import javax.xml.xpath.XPathVariableResolver;
28import javax.xml.xpath.XPathExpression;
29
30import org.apache.xml.dtm.DTM;
31import org.apache.xpath.*;
32import org.apache.xpath.objects.XObject;
33import org.apache.xpath.res.XPATHErrorResources;
34import org.apache.xalan.res.XSLMessages;
35
36import org.w3c.dom.Node;
37import org.w3c.dom.DOMImplementation;
38import org.w3c.dom.Document;
39import org.w3c.dom.traversal.NodeIterator;
40
41import org.xml.sax.InputSource;
42import org.xml.sax.SAXException;
43
44import javax.xml.parsers.*;
45
46import java.io.IOException;
47
48/**
49 * The XPathImpl class provides implementation for the methods defined  in
50 * javax.xml.xpath.XPath interface. This provide simple access to the results
51 * of an XPath expression.
52 *
53 *
54 * @version $Revision: 524814 $
55 * @author  Ramesh Mandava
56 */
57public class XPathImpl implements javax.xml.xpath.XPath {
58
59    // Private variables
60    private XPathVariableResolver variableResolver;
61    private XPathFunctionResolver functionResolver;
62    private XPathVariableResolver origVariableResolver;
63    private XPathFunctionResolver origFunctionResolver;
64    private NamespaceContext namespaceContext=null;
65    private JAXPPrefixResolver prefixResolver;
66    // By default Extension Functions are allowed in XPath Expressions. If
67    // Secure Processing Feature is set on XPathFactory then the invocation of
68    // extensions function need to throw XPathFunctionException
69    private boolean featureSecureProcessing = false;
70
71    XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr ) {
72        this.origVariableResolver = this.variableResolver = vr;
73        this.origFunctionResolver = this.functionResolver = fr;
74    }
75
76    XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr,
77            boolean featureSecureProcessing ) {
78        this.origVariableResolver = this.variableResolver = vr;
79        this.origFunctionResolver = this.functionResolver = fr;
80        this.featureSecureProcessing = featureSecureProcessing;
81    }
82
83    /**
84     * <p>Establishes a variable resolver.</p>
85     *
86     * @param resolver Variable Resolver
87     */
88    public void setXPathVariableResolver(XPathVariableResolver resolver) {
89        if ( resolver == null ) {
90            String fmsg = XSLMessages.createXPATHMessage(
91                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
92                    new Object[] {"XPathVariableResolver"} );
93            throw new NullPointerException( fmsg );
94        }
95        this.variableResolver = resolver;
96    }
97
98    /**
99     * <p>Returns the current variable resolver.</p>
100     *
101     * @return Current variable resolver
102     */
103    public XPathVariableResolver getXPathVariableResolver() {
104        return variableResolver;
105    }
106
107    /**
108     * <p>Establishes a function resolver.</p>
109     *
110     * @param resolver XPath function resolver
111     */
112    public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
113        if ( resolver == null ) {
114            String fmsg = XSLMessages.createXPATHMessage(
115                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
116                    new Object[] {"XPathFunctionResolver"} );
117            throw new NullPointerException( fmsg );
118        }
119        this.functionResolver = resolver;
120    }
121
122    /**
123     * <p>Returns the current function resolver.</p>
124     *
125     * @return Current function resolver
126     */
127    public XPathFunctionResolver getXPathFunctionResolver() {
128        return functionResolver;
129    }
130
131    /**
132     * <p>Establishes a namespace context.</p>
133     *
134     * @param nsContext Namespace context to use
135     */
136    public void setNamespaceContext(NamespaceContext nsContext) {
137        if ( nsContext == null ) {
138            String fmsg = XSLMessages.createXPATHMessage(
139                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
140                    new Object[] {"NamespaceContext"} );
141            throw new NullPointerException( fmsg );
142        }
143        this.namespaceContext = nsContext;
144        this.prefixResolver = new JAXPPrefixResolver ( nsContext );
145    }
146
147    /**
148     * <p>Returns the current namespace context.</p>
149     *
150     * @return Current Namespace context
151     */
152    public NamespaceContext getNamespaceContext() {
153        return namespaceContext;
154    }
155
156    private static Document d = null;
157
158    private static DocumentBuilder getParser() {
159        try {
160            // we'd really like to cache those DocumentBuilders, but we can't because:
161            // 1. thread safety. parsers are not thread-safe, so at least
162            //    we need one instance per a thread.
163            // 2. parsers are non-reentrant, so now we are looking at having a
164            // pool of parsers.
165            // 3. then the class loading issue. The look-up procedure of
166            //    DocumentBuilderFactory.newInstance() depends on context class loader
167            //    and system properties, which may change during the execution of JVM.
168            //
169            // so we really have to create a fresh DocumentBuilder every time we need one
170            // - KK
171            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
172            dbf.setNamespaceAware( true );
173            dbf.setValidating( false );
174            return dbf.newDocumentBuilder();
175        } catch (ParserConfigurationException e) {
176            // this should never happen with a well-behaving JAXP implementation.
177            throw new Error(e.toString());
178        }
179    }
180
181    private static Document getDummyDocument( ) {
182        // we don't need synchronization here; even if two threads
183        // enter this code at the same time, we just waste a little time
184        if(d==null) {
185            DOMImplementation dim = getParser().getDOMImplementation();
186            d = dim.createDocument("http://java.sun.com/jaxp/xpath",
187                "dummyroot", null);
188        }
189        return d;
190    }
191
192
193    private XObject eval(String expression, Object contextItem)
194        throws javax.xml.transform.TransformerException {
195        org.apache.xpath.XPath xpath = new org.apache.xpath.XPath( expression,
196            null, prefixResolver, org.apache.xpath.XPath.SELECT );
197        org.apache.xpath.XPathContext xpathSupport = null;
198
199        // Create an XPathContext that doesn't support pushing and popping of
200        // variable resolution scopes.  Sufficient for simple XPath 1.0
201        // expressions.
202        if ( functionResolver != null ) {
203            JAXPExtensionsProvider jep = new JAXPExtensionsProvider(
204                    functionResolver, featureSecureProcessing );
205            xpathSupport = new org.apache.xpath.XPathContext(jep, false);
206        } else {
207            xpathSupport = new org.apache.xpath.XPathContext(false);
208        }
209
210        XObject xobj = null;
211
212        xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
213
214        // If item is null, then we will create a a Dummy contextNode
215        if ( contextItem instanceof Node ) {
216            xobj = xpath.execute (xpathSupport, (Node)contextItem,
217                    prefixResolver );
218        } else {
219            xobj = xpath.execute ( xpathSupport, DTM.NULL, prefixResolver );
220        }
221
222        return xobj;
223    }
224
225    /**
226     * <p>Evaluate an <code>XPath</code> expression in the specified context and return the result as the specified type.</p>
227     *
228     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
229     * for context item evaluation,
230     * variable, function and <code>QName</code> resolution and return type conversion.</p>
231     *
232     * <p>If <code>returnType</code> is not one of the types defined in {@link XPathConstants} (
233     * {@link XPathConstants#NUMBER NUMBER},
234     * {@link XPathConstants#STRING STRING},
235     * {@link XPathConstants#BOOLEAN BOOLEAN},
236     * {@link XPathConstants#NODE NODE} or
237     * {@link XPathConstants#NODESET NODESET})
238     * then an <code>IllegalArgumentException</code> is thrown.</p>
239     *
240     * <p>If a <code>null</code> value is provided for
241     * <code>item</code>, an empty document will be used for the
242     * context.
243     * If <code>expression</code> or <code>returnType</code> is <code>null</code>, then a
244     * <code>NullPointerException</code> is thrown.</p>
245     *
246     * @param expression The XPath expression.
247     * @param item The starting context (node or node list, for example).
248     * @param returnType The desired return type.
249     *
250     * @return Result of evaluating an XPath expression as an <code>Object</code> of <code>returnType</code>.
251     *
252     * @throws XPathExpressionException If <code>expression</code> cannot be evaluated.
253     * @throws IllegalArgumentException If <code>returnType</code> is not one of the types defined in {@link XPathConstants}.
254     * @throws NullPointerException If <code>expression</code> or <code>returnType</code> is <code>null</code>.
255     */
256    public Object evaluate(String expression, Object item, QName returnType)
257            throws XPathExpressionException {
258        if ( expression == null ) {
259            String fmsg = XSLMessages.createXPATHMessage(
260                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
261                    new Object[] {"XPath expression"} );
262            throw new NullPointerException ( fmsg );
263        }
264        if ( returnType == null ) {
265            String fmsg = XSLMessages.createXPATHMessage(
266                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
267                    new Object[] {"returnType"} );
268            throw new NullPointerException ( fmsg );
269        }
270        // Checking if requested returnType is supported. returnType need to
271        // be defined in XPathConstants
272        if ( !isSupported ( returnType ) ) {
273            String fmsg = XSLMessages.createXPATHMessage(
274                    XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
275                    new Object[] { returnType.toString() } );
276            throw new IllegalArgumentException ( fmsg );
277        }
278
279        try {
280
281            XObject resultObject = eval( expression, item );
282            return getResultAsType( resultObject, returnType );
283        } catch ( java.lang.NullPointerException npe ) {
284            // If VariableResolver returns null Or if we get
285            // NullPointerException at this stage for some other reason
286            // then we have to reurn XPathException
287            throw new XPathExpressionException ( npe );
288        } catch ( javax.xml.transform.TransformerException te ) {
289            Throwable nestedException = te.getException();
290            if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) {
291                throw (javax.xml.xpath.XPathFunctionException)nestedException;
292            } else {
293                // For any other exceptions we need to throw
294                // XPathExpressionException ( as per spec )
295                throw new XPathExpressionException ( te );
296            }
297        }
298
299    }
300
301    private boolean isSupported( QName returnType ) {
302        if ( ( returnType.equals( XPathConstants.STRING ) ) ||
303             ( returnType.equals( XPathConstants.NUMBER ) ) ||
304             ( returnType.equals( XPathConstants.BOOLEAN ) ) ||
305             ( returnType.equals( XPathConstants.NODE ) ) ||
306             ( returnType.equals( XPathConstants.NODESET ) )  ) {
307
308            return true;
309        }
310        return false;
311     }
312
313    private Object getResultAsType( XObject resultObject, QName returnType )
314        throws javax.xml.transform.TransformerException {
315        // XPathConstants.STRING
316        if ( returnType.equals( XPathConstants.STRING ) ) {
317            return resultObject.str();
318        }
319        // XPathConstants.NUMBER
320        if ( returnType.equals( XPathConstants.NUMBER ) ) {
321            return new Double ( resultObject.num());
322        }
323        // XPathConstants.BOOLEAN
324        if ( returnType.equals( XPathConstants.BOOLEAN ) ) {
325            return new Boolean( resultObject.bool());
326        }
327        // XPathConstants.NODESET ---ORdered, UNOrdered???
328        if ( returnType.equals( XPathConstants.NODESET ) ) {
329            return resultObject.nodelist();
330        }
331        // XPathConstants.NODE
332        if ( returnType.equals( XPathConstants.NODE ) ) {
333            NodeIterator ni = resultObject.nodeset();
334            //Return the first node, or null
335            return ni.nextNode();
336        }
337        String fmsg = XSLMessages.createXPATHMessage(
338                XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
339                new Object[] { returnType.toString()});
340        throw new IllegalArgumentException( fmsg );
341    }
342
343
344
345    /**
346     * <p>Evaluate an XPath expression in the specified context and return the result as a <code>String</code>.</p>
347     *
348     * <p>This method calls {@link #evaluate(String expression, Object item, QName returnType)} with a <code>returnType</code> of
349     * {@link XPathConstants#STRING}.</p>
350     *
351     * <p>See "Evaluation of XPath Expressions" of JAXP 1.3 spec
352     * for context item evaluation,
353     * variable, function and QName resolution and return type conversion.</p>
354     *
355     * <p>If a <code>null</code> value is provided for
356     * <code>item</code>, an empty document will be used for the
357     * context.
358     * If <code>expression</code> is <code>null</code>, then a <code>NullPointerException</code> is thrown.</p>
359     *
360     * @param expression The XPath expression.
361     * @param item The starting context (node or node list, for example).
362     *
363     * @return The <code>String</code> that is the result of evaluating the expression and
364     *   converting the result to a <code>String</code>.
365     *
366     * @throws XPathExpressionException If <code>expression</code> cannot be evaluated.
367     * @throws NullPointerException If <code>expression</code> is <code>null</code>.
368     */
369    public String evaluate(String expression, Object item)
370        throws XPathExpressionException {
371        return (String)this.evaluate( expression, item, XPathConstants.STRING );
372    }
373
374    /**
375     * <p>Compile an XPath expression for later evaluation.</p>
376     *
377     * <p>If <code>expression</code> contains any {@link XPathFunction}s,
378     * they must be available via the {@link XPathFunctionResolver}.
379     * An {@link XPathExpressionException} will be thrown if the <code>XPathFunction</code>
380     * cannot be resovled with the <code>XPathFunctionResolver</code>.</p>
381     *
382     * <p>If <code>expression</code> is <code>null</code>, a <code>NullPointerException</code> is thrown.</p>
383     *
384     * @param expression The XPath expression.
385     *
386     * @return Compiled XPath expression.
387
388     * @throws XPathExpressionException If <code>expression</code> cannot be compiled.
389     * @throws NullPointerException If <code>expression</code> is <code>null</code>.
390     */
391    public XPathExpression compile(String expression)
392        throws XPathExpressionException {
393        if ( expression == null ) {
394            String fmsg = XSLMessages.createXPATHMessage(
395                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
396                    new Object[] {"XPath expression"} );
397            throw new NullPointerException ( fmsg );
398        }
399        try {
400            org.apache.xpath.XPath xpath = new XPath (expression, null,
401                    prefixResolver, org.apache.xpath.XPath.SELECT );
402            // Can have errorListener
403            XPathExpressionImpl ximpl = new XPathExpressionImpl (xpath,
404                    prefixResolver, functionResolver, variableResolver,
405                    featureSecureProcessing );
406            return ximpl;
407        } catch ( javax.xml.transform.TransformerException te ) {
408            throw new XPathExpressionException ( te ) ;
409        }
410    }
411
412
413    /**
414     * <p>Evaluate an XPath expression in the context of the specified <code>InputSource</code>
415     * and return the result as the specified type.</p>
416     *
417     * <p>This method builds a data model for the {@link InputSource} and calls
418     * {@link #evaluate(String expression, Object item, QName returnType)} on the resulting document object.</p>
419     *
420     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
421     * for context item evaluation,
422     * variable, function and QName resolution and return type conversion.</p>
423     *
424     * <p>If <code>returnType</code> is not one of the types defined in {@link XPathConstants},
425     * then an <code>IllegalArgumentException</code> is thrown.</p>
426     *
427     * <p>If <code>expression</code>, <code>source</code> or <code>returnType</code> is <code>null</code>,
428     * then a <code>NullPointerException</code> is thrown.</p>
429     *
430     * @param expression The XPath expression.
431     * @param source The input source of the document to evaluate over.
432     * @param returnType The desired return type.
433     *
434     * @return The <code>Object</code> that encapsulates the result of evaluating the expression.
435     *
436     * @throws XPathExpressionException If expression cannot be evaluated.
437     * @throws IllegalArgumentException If <code>returnType</code> is not one of the types defined in {@link XPathConstants}.
438     * @throws NullPointerException If <code>expression</code>, <code>source</code> or <code>returnType</code>
439     *   is <code>null</code>.
440     */
441    public Object evaluate(String expression, InputSource source,
442            QName returnType) throws XPathExpressionException {
443        // Checking validity of different parameters
444        if( source== null ) {
445            String fmsg = XSLMessages.createXPATHMessage(
446                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
447                    new Object[] {"source"} );
448            throw new NullPointerException ( fmsg );
449        }
450        if ( expression == null ) {
451            String fmsg = XSLMessages.createXPATHMessage(
452                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
453                    new Object[] {"XPath expression"} );
454            throw new NullPointerException ( fmsg );
455        }
456        if ( returnType == null ) {
457            String fmsg = XSLMessages.createXPATHMessage(
458                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
459                    new Object[] {"returnType"} );
460            throw new NullPointerException ( fmsg );
461        }
462
463        //Checking if requested returnType is supported.
464        //returnType need to be defined in XPathConstants
465        if ( !isSupported ( returnType ) ) {
466            String fmsg = XSLMessages.createXPATHMessage(
467                    XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
468                    new Object[] { returnType.toString() } );
469            throw new IllegalArgumentException ( fmsg );
470        }
471
472        try {
473
474            Document document = getParser().parse( source );
475
476            XObject resultObject = eval( expression, document );
477            return getResultAsType( resultObject, returnType );
478        } catch ( SAXException e ) {
479            throw new XPathExpressionException ( e );
480        } catch( IOException e ) {
481            throw new XPathExpressionException ( e );
482        } catch ( javax.xml.transform.TransformerException te ) {
483            Throwable nestedException = te.getException();
484            if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) {
485                throw (javax.xml.xpath.XPathFunctionException)nestedException;
486            } else {
487                throw new XPathExpressionException ( te );
488            }
489        }
490
491    }
492
493
494
495
496    /**
497     * <p>Evaluate an XPath expression in the context of the specified <code>InputSource</code>
498     * and return the result as a <code>String</code>.</p>
499     *
500     * <p>This method calls {@link #evaluate(String expression, InputSource source, QName returnType)} with a
501     * <code>returnType</code> of {@link XPathConstants#STRING}.</p>
502     *
503     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
504     * for context item evaluation,
505     * variable, function and QName resolution and return type conversion.</p>
506     *
507     * <p>If <code>expression</code> or <code>source</code> is <code>null</code>,
508     * then a <code>NullPointerException</code> is thrown.</p>
509     *
510     * @param expression The XPath expression.
511     * @param source The <code>InputSource</code> of the document to evaluate over.
512     *
513     * @return The <code>String</code> that is the result of evaluating the expression and
514     *   converting the result to a <code>String</code>.
515     *
516     * @throws XPathExpressionException If expression cannot be evaluated.
517     * @throws NullPointerException If <code>expression</code> or <code>source</code> is <code>null</code>.
518     */
519    public String evaluate(String expression, InputSource source)
520        throws XPathExpressionException {
521        return (String)this.evaluate( expression, source, XPathConstants.STRING );
522    }
523
524    /**
525     * <p>Reset this <code>XPath</code> to its original configuration.</p>
526     *
527     * <p><code>XPath</code> is reset to the same state as when it was created with
528     * {@link XPathFactory#newXPath()}.
529     * <code>reset()</code> is designed to allow the reuse of existing <code>XPath</code>s
530     * thus saving resources associated with the creation of new <code>XPath</code>s.</p>
531     *
532     * <p>The reset <code>XPath</code> is not guaranteed to have the same
533     * {@link XPathFunctionResolver}, {@link XPathVariableResolver}
534     * or {@link NamespaceContext} <code>Object</code>s, e.g. {@link Object#equals(Object obj)}.
535     * It is guaranteed to have a functionally equal <code>XPathFunctionResolver</code>,
536     * <code>XPathVariableResolver</code>
537     * and <code>NamespaceContext</code>.</p>
538     */
539    public void reset() {
540        this.variableResolver = this.origVariableResolver;
541        this.functionResolver = this.origFunctionResolver;
542        this.namespaceContext = null;
543    }
544
545}
546