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 }