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 20 package org.apache.xpath.jaxp; 21 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; 29 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; 35 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; 40 41 import org.xml.sax.InputSource; 42 import org.xml.sax.SAXException; 43 44 import javax.xml.parsers.*; 45 46 import 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 */ 57 public 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