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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 // $Id: XPathImpl.java 524814 2007-04-02 15:52:11Z zongaro $
20 package org.apache.xpath.jaxp;
22 import javax.xml.namespace.QName;
23 import javax.xml.namespace.NamespaceContext;
24 import javax.xml.xpath.XPathExpressionException;
25 import javax.xml.xpath.XPathConstants;
26 import javax.xml.xpath.XPathFunctionResolver;
27 import javax.xml.xpath.XPathVariableResolver;
28 import javax.xml.xpath.XPathExpression;
30 import org.apache.xml.dtm.DTM;
31 import org.apache.xpath.*;
32 import org.apache.xpath.objects.XObject;
33 import org.apache.xpath.res.XPATHErrorResources;
34 import org.apache.xalan.res.XSLMessages;
36 import org.w3c.dom.Node;
37 import org.w3c.dom.DOMImplementation;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.traversal.NodeIterator;
41 import org.xml.sax.InputSource;
42 import org.xml.sax.SAXException;
44 import javax.xml.parsers.*;
46 import java.io.IOException;
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.
54 * @version $Revision: 524814 $
55 * @author Ramesh Mandava
57 public class XPathImpl implements javax.xml.xpath.XPath {
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;
71 XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr ) {
72 this.origVariableResolver = this.variableResolver = vr;
73 this.origFunctionResolver = this.functionResolver = fr;
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;
84 * <p>Establishes a variable resolver.</p>
86 * @param resolver Variable Resolver
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 );
95 this.variableResolver = resolver;
99 * <p>Returns the current variable resolver.</p>
101 * @return Current variable resolver
103 public XPathVariableResolver getXPathVariableResolver() {
104 return variableResolver;
108 * <p>Establishes a function resolver.</p>
110 * @param resolver XPath function resolver
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 );
119 this.functionResolver = resolver;
123 * <p>Returns the current function resolver.</p>
125 * @return Current function resolver
127 public XPathFunctionResolver getXPathFunctionResolver() {
128 return functionResolver;
132 * <p>Establishes a namespace context.</p>
134 * @param nsContext Namespace context to use
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 );
143 this.namespaceContext = nsContext;
144 this.prefixResolver = new JAXPPrefixResolver ( nsContext );
148 * <p>Returns the current namespace context.</p>
150 * @return Current Namespace context
152 public NamespaceContext getNamespaceContext() {
153 return namespaceContext;
156 private static Document d = null;
158 private static DocumentBuilder getParser() {
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
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.
169 // so we really have to create a fresh DocumentBuilder every time we need one
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());
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
185 DOMImplementation dim = getParser().getDOMImplementation();
186 d = dim.createDocument("http://java.sun.com/jaxp/xpath",
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;
199 // Create an XPathContext that doesn't support pushing and popping of
200 // variable resolution scopes. Sufficient for simple XPath 1.0
202 if ( functionResolver != null ) {
203 JAXPExtensionsProvider jep = new JAXPExtensionsProvider(
204 functionResolver, featureSecureProcessing );
205 xpathSupport = new org.apache.xpath.XPathContext(jep, false);
207 xpathSupport = new org.apache.xpath.XPathContext(false);
212 xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
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,
219 xobj = xpath.execute ( xpathSupport, DTM.NULL, prefixResolver );
226 * <p>Evaluate an <code>XPath</code> expression in the specified context and return the result as the specified type.</p>
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>
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>
240 * <p>If a <code>null</code> value is provided for
241 * <code>item</code>, an empty document will be used for the
243 * If <code>expression</code> or <code>returnType</code> is <code>null</code>, then a
244 * <code>NullPointerException</code> is thrown.</p>
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.
250 * @return Result of evaluating an XPath expression as an <code>Object</code> of <code>returnType</code>.
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>.
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 );
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 );
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 );
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;
293 // For any other exceptions we need to throw
294 // XPathExpressionException ( as per spec )
295 throw new XPathExpressionException ( te );
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 ) ) ) {
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();
319 // XPathConstants.NUMBER
320 if ( returnType.equals( XPathConstants.NUMBER ) ) {
321 return new Double ( resultObject.num());
323 // XPathConstants.BOOLEAN
324 if ( returnType.equals( XPathConstants.BOOLEAN ) ) {
325 return new Boolean( resultObject.bool());
327 // XPathConstants.NODESET ---ORdered, UNOrdered???
328 if ( returnType.equals( XPathConstants.NODESET ) ) {
329 return resultObject.nodelist();
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();
337 String fmsg = XSLMessages.createXPATHMessage(
338 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
339 new Object[] { returnType.toString()});
340 throw new IllegalArgumentException( fmsg );
346 * <p>Evaluate an XPath expression in the specified context and return the result as a <code>String</code>.</p>
348 * <p>This method calls {@link #evaluate(String expression, Object item, QName returnType)} with a <code>returnType</code> of
349 * {@link XPathConstants#STRING}.</p>
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>
355 * <p>If a <code>null</code> value is provided for
356 * <code>item</code>, an empty document will be used for the
358 * If <code>expression</code> is <code>null</code>, then a <code>NullPointerException</code> is thrown.</p>
360 * @param expression The XPath expression.
361 * @param item The starting context (node or node list, for example).
363 * @return The <code>String</code> that is the result of evaluating the expression and
364 * converting the result to a <code>String</code>.
366 * @throws XPathExpressionException If <code>expression</code> cannot be evaluated.
367 * @throws NullPointerException If <code>expression</code> is <code>null</code>.
369 public String evaluate(String expression, Object item)
370 throws XPathExpressionException {
371 return (String)this.evaluate( expression, item, XPathConstants.STRING );
375 * <p>Compile an XPath expression for later evaluation.</p>
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>
382 * <p>If <code>expression</code> is <code>null</code>, a <code>NullPointerException</code> is thrown.</p>
384 * @param expression The XPath expression.
386 * @return Compiled XPath expression.
388 * @throws XPathExpressionException If <code>expression</code> cannot be compiled.
389 * @throws NullPointerException If <code>expression</code> is <code>null</code>.
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 );
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 );
407 } catch ( javax.xml.transform.TransformerException te ) {
408 throw new XPathExpressionException ( te ) ;
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>
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>
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>
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>
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>
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.
434 * @return The <code>Object</code> that encapsulates the result of evaluating the expression.
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>.
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 );
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 );
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 );
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 );
474 Document document = getParser().parse( source );
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;
487 throw new XPathExpressionException ( te );
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>
500 * <p>This method calls {@link #evaluate(String expression, InputSource source, QName returnType)} with a
501 * <code>returnType</code> of {@link XPathConstants#STRING}.</p>
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>
507 * <p>If <code>expression</code> or <code>source</code> is <code>null</code>,
508 * then a <code>NullPointerException</code> is thrown.</p>
510 * @param expression The XPath expression.
511 * @param source The <code>InputSource</code> of the document to evaluate over.
513 * @return The <code>String</code> that is the result of evaluating the expression and
514 * converting the result to a <code>String</code>.
516 * @throws XPathExpressionException If expression cannot be evaluated.
517 * @throws NullPointerException If <code>expression</code> or <code>source</code> is <code>null</code>.
519 public String evaluate(String expression, InputSource source)
520 throws XPathExpressionException {
521 return (String)this.evaluate( expression, source, XPathConstants.STRING );
525 * <p>Reset this <code>XPath</code> to its original configuration.</p>
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>
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>
539 public void reset() {
540 this.variableResolver = this.origVariableResolver;
541 this.functionResolver = this.origFunctionResolver;
542 this.namespaceContext = null;