001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    // $Id: XPathImpl.java 1225280 2011-12-28 18:52:55Z mrglavas $
019    
020    package org.apache.xpath.jaxp;
021    
022    import javax.xml.namespace.QName;
023    import javax.xml.namespace.NamespaceContext;
024    import javax.xml.xpath.XPathExpressionException;
025    import javax.xml.xpath.XPathConstants;
026    import javax.xml.xpath.XPathFunctionResolver;
027    import javax.xml.xpath.XPathVariableResolver;
028    import javax.xml.xpath.XPathExpression;
029    
030    import org.apache.xml.dtm.DTM;
031    import org.apache.xpath.*;
032    import org.apache.xpath.objects.XObject;
033    import org.apache.xpath.res.XPATHErrorResources;
034    import org.apache.xalan.res.XSLMessages;
035    
036    import org.w3c.dom.Node;
037    import org.w3c.dom.DOMImplementation;
038    import org.w3c.dom.Document;
039    import org.w3c.dom.traversal.NodeIterator;
040    
041    import org.xml.sax.InputSource;
042    import org.xml.sax.SAXException;
043    
044    import javax.xml.parsers.*;
045    
046    import java.io.IOException;
047    
048    /**
049     * The XPathImpl class provides implementation for the methods defined  in
050     * javax.xml.xpath.XPath interface. This provide simple access to the results
051     * of an XPath expression.
052     *
053     *
054     * @version $Revision: 1225280 $
055     * @author  Ramesh Mandava
056     */
057    public class XPathImpl implements javax.xml.xpath.XPath {
058    
059        // Private variables
060        private XPathVariableResolver variableResolver;
061        private XPathFunctionResolver functionResolver;
062        private XPathVariableResolver origVariableResolver;
063        private XPathFunctionResolver origFunctionResolver;
064        private NamespaceContext namespaceContext=null;
065        private JAXPPrefixResolver prefixResolver;
066        // By default Extension Functions are allowed in XPath Expressions. If 
067        // Secure Processing Feature is set on XPathFactory then the invocation of
068        // extensions function need to throw XPathFunctionException
069        private boolean featureSecureProcessing = false; 
070    
071        XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr ) {
072            this.origVariableResolver = this.variableResolver = vr;
073            this.origFunctionResolver = this.functionResolver = fr;
074        }
075    
076        XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr, 
077                boolean featureSecureProcessing ) {
078            this.origVariableResolver = this.variableResolver = vr;
079            this.origFunctionResolver = this.functionResolver = fr;
080            this.featureSecureProcessing = featureSecureProcessing;
081        }
082    
083        /**
084         * <p>Establishes a variable resolver.</p>
085         *
086         * @param resolver Variable Resolver
087         */
088        public void setXPathVariableResolver(XPathVariableResolver resolver) {
089            if ( resolver == null ) {
090                String fmsg = XSLMessages.createXPATHMessage( 
091                        XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
092                        new Object[] {"XPathVariableResolver"} );
093                throw new NullPointerException( fmsg );
094            }
095            this.variableResolver = resolver;
096        }
097    
098        /**
099         * <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 resultObject.bool() ? Boolean.TRUE : Boolean.FALSE;
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    }