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: XPathExpressionImpl.java 524813 2007-04-02 15:52:07Z zongaro $
19
20package org.apache.xpath.jaxp;
21
22import org.apache.xpath.*;
23import javax.xml.transform.TransformerException;
24
25import org.apache.xpath.objects.XObject;
26import org.apache.xml.utils.PrefixResolver;
27import org.apache.xpath.res.XPATHErrorResources;
28import org.apache.xalan.res.XSLMessages;
29
30import javax.xml.namespace.NamespaceContext;
31import javax.xml.namespace.QName;
32import javax.xml.xpath.XPathExpressionException;
33import javax.xml.xpath.XPathConstants;
34import javax.xml.xpath.XPathFunctionResolver;
35import javax.xml.xpath.XPathVariableResolver;
36import javax.xml.xpath.XPathConstants;
37
38import org.w3c.dom.Node;
39import org.w3c.dom.Document;
40import org.w3c.dom.DOMImplementation;
41import org.w3c.dom.traversal.NodeIterator;
42import javax.xml.parsers.DocumentBuilderFactory;
43import javax.xml.parsers.DocumentBuilder;
44
45import org.xml.sax.InputSource;
46
47/**
48 * The XPathExpression interface encapsulates a (compiled) XPath expression.
49 *
50 * @version $Revision: 524813 $
51 * @author  Ramesh Mandava
52 */
53public class XPathExpressionImpl  implements javax.xml.xpath.XPathExpression{
54
55    private XPathFunctionResolver functionResolver;
56    private XPathVariableResolver variableResolver;
57    private JAXPPrefixResolver prefixResolver;
58    private org.apache.xpath.XPath xpath;
59
60    // By default Extension Functions are allowed in XPath Expressions. If
61    // Secure Processing Feature is set on XPathFactory then the invocation of
62    // extensions function need to throw XPathFunctionException
63    private boolean featureSecureProcessing = false;
64
65    /** Protected constructor to prevent direct instantiation; use compile()
66     * from the context.
67     */
68    protected XPathExpressionImpl() { };
69
70    protected XPathExpressionImpl(org.apache.xpath.XPath xpath,
71            JAXPPrefixResolver prefixResolver,
72            XPathFunctionResolver functionResolver,
73            XPathVariableResolver variableResolver ) {
74        this.xpath = xpath;
75        this.prefixResolver = prefixResolver;
76        this.functionResolver = functionResolver;
77        this.variableResolver = variableResolver;
78        this.featureSecureProcessing = false;
79    };
80
81    protected XPathExpressionImpl(org.apache.xpath.XPath xpath,
82            JAXPPrefixResolver prefixResolver,
83            XPathFunctionResolver functionResolver,
84            XPathVariableResolver variableResolver,
85            boolean featureSecureProcessing ) {
86        this.xpath = xpath;
87        this.prefixResolver = prefixResolver;
88        this.functionResolver = functionResolver;
89        this.variableResolver = variableResolver;
90        this.featureSecureProcessing = featureSecureProcessing;
91    };
92
93    public void setXPath (org.apache.xpath.XPath xpath ) {
94        this.xpath = xpath;
95    }
96
97    public Object eval(Object item, QName returnType)
98            throws javax.xml.transform.TransformerException {
99        XObject resultObject = eval ( item );
100        return getResultAsType( resultObject, returnType );
101    }
102
103    private XObject eval ( Object contextItem )
104            throws javax.xml.transform.TransformerException {
105        org.apache.xpath.XPathContext xpathSupport = null;
106
107        // Create an XPathContext that doesn't support pushing and popping of
108        // variable resolution scopes.  Sufficient for simple XPath 1.0
109        // expressions.
110        if ( functionResolver != null ) {
111            JAXPExtensionsProvider jep = new JAXPExtensionsProvider(
112                    functionResolver, featureSecureProcessing );
113            xpathSupport = new org.apache.xpath.XPathContext(jep, false);
114        } else {
115            xpathSupport = new org.apache.xpath.XPathContext(false);
116        }
117
118        xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
119        XObject xobj = null;
120
121        Node contextNode = (Node)contextItem;
122        // We always need to have a ContextNode with Xalan XPath implementation
123        // To allow simple expression evaluation like 1+1 we are setting
124        // dummy Document as Context Node
125        if ( contextNode == null ) {
126              contextNode = getDummyDocument();
127        }
128
129        xobj = xpath.execute(xpathSupport, contextNode, prefixResolver );
130        return xobj;
131    }
132
133
134    /**
135     * <p>Evaluate the compiled XPath expression in the specified context and
136     *  return the result as the specified type.</p>
137     *
138     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
139     * for context item evaluation,
140     * variable, function and QName resolution and return type conversion.</p>
141     *
142     * <p>If <code>returnType</code> is not one of the types defined
143     * in {@link XPathConstants},
144     * then an <code>IllegalArgumentException</code> is thrown.</p>
145     *
146     * <p>If a <code>null</code> value is provided for
147     * <code>item</code>, an empty document will be used for the
148     * context.
149     * If <code>returnType</code> is <code>null</code>, then a
150     * <code>NullPointerException</code> is thrown.</p>
151     *
152     * @param item The starting context (node or node list, for example).
153     * @param returnType The desired return type.
154     *
155     * @return The <code>Object</code> that is the result of evaluating the
156     * expression and converting the result to
157     *   <code>returnType</code>.
158     *
159     * @throws XPathExpressionException If the expression cannot be evaluated.
160     * @throws IllegalArgumentException If <code>returnType</code> is not one
161     * of the types defined in {@link XPathConstants}.
162     * @throws NullPointerException If  <code>returnType</code> is
163     * <code>null</code>.
164     */
165    public Object evaluate(Object item, QName returnType)
166        throws XPathExpressionException {
167        //Validating parameters to enforce constraints defined by JAXP spec
168        if ( returnType == null ) {
169           //Throwing NullPointerException as defined in spec
170            String fmsg = XSLMessages.createXPATHMessage(
171                    XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
172                    new Object[] {"returnType"} );
173            throw new NullPointerException( fmsg );
174        }
175        // Checking if requested returnType is supported. returnType need to be
176        // defined in XPathConstants
177        if ( !isSupported ( returnType ) ) {
178            String fmsg = XSLMessages.createXPATHMessage(
179                    XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
180                    new Object[] { returnType.toString() } );
181            throw new IllegalArgumentException ( fmsg );
182        }
183        try {
184            return eval( item, returnType);
185        } catch ( java.lang.NullPointerException npe ) {
186            // If VariableResolver returns null Or if we get
187            // NullPointerException at this stage for some other reason
188            // then we have to reurn XPathException
189            throw new XPathExpressionException ( npe );
190        } catch ( javax.xml.transform.TransformerException te ) {
191            Throwable nestedException = te.getException();
192            if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) {
193                throw (javax.xml.xpath.XPathFunctionException)nestedException;
194            } else {
195                // For any other exceptions we need to throw
196                // XPathExpressionException ( as per spec )
197                throw new XPathExpressionException( te);
198            }
199        }
200
201    }
202
203    /**
204     * <p>Evaluate the compiled XPath expression in the specified context and
205     * return the result as a <code>String</code>.</p>
206     *
207     * <p>This method calls {@link #evaluate(Object item, QName returnType)}
208     * with a <code>returnType</code> of
209     * {@link XPathConstants#STRING}.</p>
210     *
211     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
212     *  for context item evaluation,
213     * variable, function and QName resolution and return type conversion.</p>
214     *
215     * <p>If a <code>null</code> value is provided for
216     * <code>item</code>, an empty document will be used for the
217     * context.
218     *
219     * @param item The starting context (node or node list, for example).
220     *
221     * @return The <code>String</code> that is the result of evaluating the
222     * expression and converting the result to a
223     *   <code>String</code>.
224     *
225     * @throws XPathExpressionException If the expression cannot be evaluated.
226     */
227    public String evaluate(Object item)
228        throws XPathExpressionException {
229        return (String)this.evaluate( item, XPathConstants.STRING );
230    }
231
232
233
234    static DocumentBuilderFactory dbf = null;
235    static DocumentBuilder db = null;
236    static Document d = null;
237
238    /**
239     * <p>Evaluate the compiled XPath expression in the context of the
240     * specified <code>InputSource</code> and return the result as the
241     *  specified type.</p>
242     *
243     * <p>This method builds a data model for the {@link InputSource} and calls
244     * {@link #evaluate(Object item, QName returnType)} on the resulting
245     * document object.</p>
246     *
247     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
248     *  for context item evaluation,
249     * variable, function and QName resolution and return type conversion.</p>
250     *
251     * <p>If <code>returnType</code> is not one of the types defined in
252     * {@link XPathConstants},
253     * then an <code>IllegalArgumentException</code> is thrown.</p>
254     *
255     *<p>If <code>source</code> or <code>returnType</code> is <code>null</code>,
256     * then a <code>NullPointerException</code> is thrown.</p>
257     *
258     * @param source The <code>InputSource</code> of the document to evaluate
259     * over.
260     * @param returnType The desired return type.
261     *
262     * @return The <code>Object</code> that is the result of evaluating the
263     * expression and converting the result to
264     *   <code>returnType</code>.
265     *
266     * @throws XPathExpressionException If the expression cannot be evaluated.
267     * @throws IllegalArgumentException If <code>returnType</code> is not one
268     * of the types defined in {@link XPathConstants}.
269     * @throws NullPointerException If  <code>source</code> or
270     * <code>returnType</code> is <code>null</code>.
271     */
272    public Object evaluate(InputSource source, QName returnType)
273        throws XPathExpressionException {
274        if ( ( source == null ) || ( returnType == null ) ) {
275            String fmsg = XSLMessages.createXPATHMessage(
276                    XPATHErrorResources.ER_SOURCE_RETURN_TYPE_CANNOT_BE_NULL,
277                    null );
278            throw new NullPointerException ( fmsg );
279        }
280        // Checking if requested returnType is supported. returnType need to be
281        // defined in XPathConstants
282        if ( !isSupported ( returnType ) ) {
283            String fmsg = XSLMessages.createXPATHMessage(
284                    XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
285                    new Object[] { returnType.toString() } );
286            throw new IllegalArgumentException ( fmsg );
287        }
288        try {
289            if ( dbf == null ) {
290                dbf = DocumentBuilderFactory.newInstance();
291                dbf.setNamespaceAware( true );
292                dbf.setValidating( false );
293            }
294            db = dbf.newDocumentBuilder();
295            Document document = db.parse( source );
296            return eval(  document, returnType );
297        } catch ( Exception e ) {
298            throw new XPathExpressionException ( e );
299        }
300    }
301
302    /**
303     * <p>Evaluate the compiled XPath expression in the context of the specified <code>InputSource</code> and return the result as a
304     * <code>String</code>.</p>
305     *
306     * <p>This method calls {@link #evaluate(InputSource source, QName returnType)} with a <code>returnType</code> of
307     * {@link XPathConstants#STRING}.</p>
308     *
309     * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
310     * for context item evaluation,
311     * variable, function and QName resolution and return type conversion.</p>
312     *
313     * <p>If <code>source</code> is <code>null</code>, then a <code>NullPointerException</code> is thrown.</p>
314     *
315     * @param source The <code>InputSource</code> of the document to evaluate over.
316     *
317     * @return The <code>String</code> that is the result of evaluating the expression and converting the result to a
318     *   <code>String</code>.
319     *
320     * @throws XPathExpressionException If the expression cannot be evaluated.
321     * @throws NullPointerException If  <code>source</code> is <code>null</code>.
322     */
323    public String evaluate(InputSource source)
324        throws XPathExpressionException {
325        return (String)this.evaluate( source, XPathConstants.STRING );
326    }
327
328    private boolean isSupported( QName returnType ) {
329        // XPathConstants.STRING
330        if ( ( returnType.equals( XPathConstants.STRING ) ) ||
331             ( returnType.equals( XPathConstants.NUMBER ) ) ||
332             ( returnType.equals( XPathConstants.BOOLEAN ) ) ||
333             ( returnType.equals( XPathConstants.NODE ) ) ||
334             ( returnType.equals( XPathConstants.NODESET ) )  ) {
335
336            return true;
337        }
338        return false;
339     }
340
341     private Object getResultAsType( XObject resultObject, QName returnType )
342        throws javax.xml.transform.TransformerException {
343        // XPathConstants.STRING
344        if ( returnType.equals( XPathConstants.STRING ) ) {
345            return resultObject.str();
346        }
347        // XPathConstants.NUMBER
348        if ( returnType.equals( XPathConstants.NUMBER ) ) {
349            return new Double ( resultObject.num());
350        }
351        // XPathConstants.BOOLEAN
352        if ( returnType.equals( XPathConstants.BOOLEAN ) ) {
353            return new Boolean( resultObject.bool());
354        }
355        // XPathConstants.NODESET ---ORdered, UNOrdered???
356        if ( returnType.equals( XPathConstants.NODESET ) ) {
357            return resultObject.nodelist();
358        }
359        // XPathConstants.NODE
360        if ( returnType.equals( XPathConstants.NODE ) ) {
361            NodeIterator ni = resultObject.nodeset();
362            //Return the first node, or null
363            return ni.nextNode();
364        }
365        // If isSupported check is already done then the execution path
366        // shouldn't come here. Being defensive
367        String fmsg = XSLMessages.createXPATHMessage(
368                XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
369                new Object[] { returnType.toString()});
370        throw new IllegalArgumentException ( fmsg );
371    }
372
373
374    private static Document getDummyDocument( ) {
375        try {
376            if ( dbf == null ) {
377                dbf = DocumentBuilderFactory.newInstance();
378                dbf.setNamespaceAware( true );
379                dbf.setValidating( false );
380            }
381            db = dbf.newDocumentBuilder();
382
383            DOMImplementation dim = db.getDOMImplementation();
384            d = dim.createDocument("http://java.sun.com/jaxp/xpath",
385                "dummyroot", null);
386            return d;
387        } catch ( Exception e ) {
388            e.printStackTrace();
389        }
390        return null;
391    }
392
393
394
395
396}
397