1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 /* 6 * Copyright 1999-2004 The Apache Software Foundation. 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 // $Id: XPathImpl.java,v 1.2 2005/08/16 22:41:08 jeffsuttor Exp $ 21 22 package com.sun.org.apache.xpath.internal.jaxp; 23 24 import javax.xml.namespace.QName; 25 import javax.xml.namespace.NamespaceContext; 26 import javax.xml.xpath.XPathExpressionException; 27 import javax.xml.xpath.XPathConstants; 28 import javax.xml.xpath.XPathFunctionResolver; 29 import javax.xml.xpath.XPathVariableResolver; 30 import javax.xml.xpath.XPathExpression; 31 32 import com.sun.org.apache.xml.internal.dtm.DTM; 33 import com.sun.org.apache.xpath.internal.*; 34 import com.sun.org.apache.xpath.internal.objects.XObject; 35 import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; 36 import com.sun.org.apache.xalan.internal.res.XSLMessages; 37 import com.sun.org.apache.xalan.internal.utils.FactoryImpl; 38 import com.sun.org.apache.xalan.internal.utils.FeatureManager; 39 40 import org.w3c.dom.Node; 41 import org.w3c.dom.Document; 42 import org.w3c.dom.traversal.NodeIterator; 43 44 import org.xml.sax.InputSource; 45 import org.xml.sax.SAXException; 46 47 import javax.xml.parsers.*; 48 49 import java.io.IOException; 50 51 /** 52 * The XPathImpl class provides implementation for the methods defined in 53 * javax.xml.xpath.XPath interface. This provide simple access to the results 54 * of an XPath expression. 55 * 56 * 57 * @author Ramesh Mandava 58 */ 59 public class XPathImpl implements javax.xml.xpath.XPath { 60 61 // Private variables 62 private XPathVariableResolver variableResolver; 63 private XPathFunctionResolver functionResolver; 64 private XPathVariableResolver origVariableResolver; 65 private XPathFunctionResolver origFunctionResolver; 66 private NamespaceContext namespaceContext=null; 67 private JAXPPrefixResolver prefixResolver; 68 // By default Extension Functions are allowed in XPath Expressions. If 69 // Secure Processing Feature is set on XPathFactory then the invocation of 70 // extensions function need to throw XPathFunctionException 71 private boolean featureSecureProcessing = false; 72 private boolean useServiceMechanism = true; 73 private final FeatureManager featureManager; 74 75 XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr ) { 76 this(vr, fr, false, true, new FeatureManager()); 77 } 78 79 XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr, 80 boolean featureSecureProcessing, boolean useServiceMechanism, 81 FeatureManager featureManager) { 82 this.origVariableResolver = this.variableResolver = vr; 83 this.origFunctionResolver = this.functionResolver = fr; 84 this.featureSecureProcessing = featureSecureProcessing; 85 this.useServiceMechanism = useServiceMechanism; 86 this.featureManager = featureManager; 87 } 88 89 /** 90 * <p>Establishes a variable resolver.</p> 91 * 92 * @param resolver Variable Resolver 93 */ 94 public void setXPathVariableResolver(XPathVariableResolver resolver) { 95 if ( resolver == null ) { 96 String fmsg = XSLMessages.createXPATHMessage( 97 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 98 new Object[] {"XPathVariableResolver"} ); 99 throw new NullPointerException( fmsg ); 100 } 101 this.variableResolver = resolver; 102 } 103 104 /** 105 * <p>Returns the current variable resolver.</p> 106 * 107 * @return Current variable resolver 108 */ 109 public XPathVariableResolver getXPathVariableResolver() { 110 return variableResolver; 111 } 112 113 /** 114 * <p>Establishes a function resolver.</p> 115 * 116 * @param resolver XPath function resolver 117 */ 118 public void setXPathFunctionResolver(XPathFunctionResolver resolver) { 119 if ( resolver == null ) { 120 String fmsg = XSLMessages.createXPATHMessage( 121 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 122 new Object[] {"XPathFunctionResolver"} ); 123 throw new NullPointerException( fmsg ); 124 } 125 this.functionResolver = resolver; 126 } 127 128 /** 129 * <p>Returns the current function resolver.</p> 130 * 131 * @return Current function resolver 132 */ 133 public XPathFunctionResolver getXPathFunctionResolver() { 134 return functionResolver; 135 } 136 137 /** 138 * <p>Establishes a namespace context.</p> 139 * 140 * @param nsContext Namespace context to use 141 */ 142 public void setNamespaceContext(NamespaceContext nsContext) { 143 if ( nsContext == null ) { 144 String fmsg = XSLMessages.createXPATHMessage( 145 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 146 new Object[] {"NamespaceContext"} ); 147 throw new NullPointerException( fmsg ); 148 } 149 this.namespaceContext = nsContext; 150 this.prefixResolver = new JAXPPrefixResolver ( nsContext ); 151 } 152 153 /** 154 * <p>Returns the current namespace context.</p> 155 * 156 * @return Current Namespace context 157 */ 158 public NamespaceContext getNamespaceContext() { 159 return namespaceContext; 160 } 161 162 private static Document d = null; 163 164 private DocumentBuilder getParser() { 165 try { 166 // we'd really like to cache those DocumentBuilders, but we can't because: 167 // 1. thread safety. parsers are not thread-safe, so at least 168 // we need one instance per a thread. 169 // 2. parsers are non-reentrant, so now we are looking at having a 170 // pool of parsers. 171 // 3. then the class loading issue. The look-up procedure of 172 // DocumentBuilderFactory.newInstance() depends on context class loader 173 // and system properties, which may change during the execution of JVM. 174 // 175 // so we really have to create a fresh DocumentBuilder every time we need one 176 // - KK 177 DocumentBuilderFactory dbf = FactoryImpl.getDOMFactory(useServiceMechanism); 178 dbf.setNamespaceAware( true ); 179 dbf.setValidating( false ); 180 return dbf.newDocumentBuilder(); 181 } catch (ParserConfigurationException e) { 182 // this should never happen with a well-behaving JAXP implementation. 183 throw new Error(e); 184 } 185 } 186 187 188 private XObject eval(String expression, Object contextItem) 189 throws javax.xml.transform.TransformerException { 190 com.sun.org.apache.xpath.internal.XPath xpath = new com.sun.org.apache.xpath.internal.XPath( expression, 191 null, prefixResolver, com.sun.org.apache.xpath.internal.XPath.SELECT ); 192 com.sun.org.apache.xpath.internal.XPathContext xpathSupport = null; 193 if ( functionResolver != null ) { 194 JAXPExtensionsProvider jep = new JAXPExtensionsProvider( 195 functionResolver, featureSecureProcessing, featureManager ); 196 xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext( jep ); 197 } else { 198 xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext(); 199 } 200 201 XObject xobj = null; 202 203 xpathSupport.setVarStack(new JAXPVariableStack(variableResolver)); 204 205 // If item is null, then we will create a a Dummy contextNode 206 if ( contextItem instanceof Node ) { 207 xobj = xpath.execute (xpathSupport, (Node)contextItem, 208 prefixResolver ); 209 } else { 210 xobj = xpath.execute ( xpathSupport, DTM.NULL, prefixResolver ); 211 } 212 213 return xobj; 214 } 215 216 /** 217 * <p>Evaluate an <code>XPath</code> expression in the specified context and return the result as the specified type.</p> 218 * 219 * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec 220 * for context item evaluation, 221 * variable, function and <code>QName</code> resolution and return type conversion.</p> 222 * 223 * <p>If <code>returnType</code> is not one of the types defined in {@link XPathConstants} ( 224 * {@link XPathConstants#NUMBER NUMBER}, 225 * {@link XPathConstants#STRING STRING}, 226 * {@link XPathConstants#BOOLEAN BOOLEAN}, 227 * {@link XPathConstants#NODE NODE} or 228 * {@link XPathConstants#NODESET NODESET}) 229 * then an <code>IllegalArgumentException</code> is thrown.</p> 230 * 231 * <p>If a <code>null</code> value is provided for 232 * <code>item</code>, an empty document will be used for the 233 * context. 234 * If <code>expression</code> or <code>returnType</code> is <code>null</code>, then a 235 * <code>NullPointerException</code> is thrown.</p> 236 * 237 * @param expression The XPath expression. 238 * @param item The starting context (node or node list, for example). 239 * @param returnType The desired return type. 240 * 241 * @return Result of evaluating an XPath expression as an <code>Object</code> of <code>returnType</code>. 242 * 243 * @throws XPathExpressionException If <code>expression</code> cannot be evaluated. 244 * @throws IllegalArgumentException If <code>returnType</code> is not one of the types defined in {@link XPathConstants}. 245 * @throws NullPointerException If <code>expression</code> or <code>returnType</code> is <code>null</code>. 246 */ 247 public Object evaluate(String expression, Object item, QName returnType) 248 throws XPathExpressionException { 249 if ( expression == null ) { 250 String fmsg = XSLMessages.createXPATHMessage( 251 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 252 new Object[] {"XPath expression"} ); 253 throw new NullPointerException ( fmsg ); 254 } 255 if ( returnType == null ) { 256 String fmsg = XSLMessages.createXPATHMessage( 257 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 258 new Object[] {"returnType"} ); 259 throw new NullPointerException ( fmsg ); 260 } 261 // Checking if requested returnType is supported. returnType need to 262 // be defined in XPathConstants 263 if ( !isSupported ( returnType ) ) { 264 String fmsg = XSLMessages.createXPATHMessage( 265 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE, 266 new Object[] { returnType.toString() } ); 267 throw new IllegalArgumentException ( fmsg ); 268 } 269 270 try { 271 272 XObject resultObject = eval( expression, item ); 273 return getResultAsType( resultObject, returnType ); 274 } catch ( java.lang.NullPointerException npe ) { 275 // If VariableResolver returns null Or if we get 276 // NullPointerException at this stage for some other reason 277 // then we have to reurn XPathException 278 throw new XPathExpressionException ( npe ); 279 } catch ( javax.xml.transform.TransformerException te ) { 280 Throwable nestedException = te.getException(); 281 if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) { 282 throw (javax.xml.xpath.XPathFunctionException)nestedException; 283 } else { 284 // For any other exceptions we need to throw 285 // XPathExpressionException ( as per spec ) 286 throw new XPathExpressionException ( te ); 287 } 288 } 289 290 } 291 292 private boolean isSupported( QName returnType ) { 293 if ( ( returnType.equals( XPathConstants.STRING ) ) || 294 ( returnType.equals( XPathConstants.NUMBER ) ) || 295 ( returnType.equals( XPathConstants.BOOLEAN ) ) || 296 ( returnType.equals( XPathConstants.NODE ) ) || 297 ( returnType.equals( XPathConstants.NODESET ) ) ) { 298 299 return true; 300 } 301 return false; 302 } 303 304 private Object getResultAsType( XObject resultObject, QName returnType ) 305 throws javax.xml.transform.TransformerException { 306 // XPathConstants.STRING 307 if ( returnType.equals( XPathConstants.STRING ) ) { 308 return resultObject.str(); 309 } 310 // XPathConstants.NUMBER 311 if ( returnType.equals( XPathConstants.NUMBER ) ) { 312 return new Double ( resultObject.num()); 313 } 314 // XPathConstants.BOOLEAN 315 if ( returnType.equals( XPathConstants.BOOLEAN ) ) { 316 return new Boolean( resultObject.bool()); 317 } 318 // XPathConstants.NODESET ---ORdered, UNOrdered??? 319 if ( returnType.equals( XPathConstants.NODESET ) ) { 320 return resultObject.nodelist(); 321 } 322 // XPathConstants.NODE 323 if ( returnType.equals( XPathConstants.NODE ) ) { 324 NodeIterator ni = resultObject.nodeset(); 325 //Return the first node, or null 326 return ni.nextNode(); 327 } 328 String fmsg = XSLMessages.createXPATHMessage( 329 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE, 330 new Object[] { returnType.toString()}); 331 throw new IllegalArgumentException( fmsg ); 332 } 333 334 335 336 /** 337 * <p>Evaluate an XPath expression in the specified context and return the result as a <code>String</code>.</p> 338 * 339 * <p>This method calls {@link #evaluate(String expression, Object item, QName returnType)} with a <code>returnType</code> of 340 * {@link XPathConstants#STRING}.</p> 341 * 342 * <p>See "Evaluation of XPath Expressions" of JAXP 1.3 spec 343 * for context item evaluation, 344 * variable, function and QName resolution and return type conversion.</p> 345 * 346 * <p>If a <code>null</code> value is provided for 347 * <code>item</code>, an empty document will be used for the 348 * context. 349 * If <code>expression</code> is <code>null</code>, then a <code>NullPointerException</code> is thrown.</p> 350 * 351 * @param expression The XPath expression. 352 * @param item The starting context (node or node list, for example). 353 * 354 * @return The <code>String</code> that is the result of evaluating the expression and 355 * converting the result to a <code>String</code>. 356 * 357 * @throws XPathExpressionException If <code>expression</code> cannot be evaluated. 358 * @throws NullPointerException If <code>expression</code> is <code>null</code>. 359 */ 360 public String evaluate(String expression, Object item) 361 throws XPathExpressionException { 362 return (String)this.evaluate( expression, item, XPathConstants.STRING ); 363 } 364 365 /** 366 * <p>Compile an XPath expression for later evaluation.</p> 367 * 368 * <p>If <code>expression</code> contains any {@link XPathFunction}s, 369 * they must be available via the {@link XPathFunctionResolver}. 370 * An {@link XPathExpressionException} will be thrown if the <code>XPathFunction</code> 371 * cannot be resovled with the <code>XPathFunctionResolver</code>.</p> 372 * 373 * <p>If <code>expression</code> is <code>null</code>, a <code>NullPointerException</code> is thrown.</p> 374 * 375 * @param expression The XPath expression. 376 * 377 * @return Compiled XPath expression. 378 379 * @throws XPathExpressionException If <code>expression</code> cannot be compiled. 380 * @throws NullPointerException If <code>expression</code> is <code>null</code>. 381 */ 382 public XPathExpression compile(String expression) 383 throws XPathExpressionException { 384 if ( expression == null ) { 385 String fmsg = XSLMessages.createXPATHMessage( 386 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 387 new Object[] {"XPath expression"} ); 388 throw new NullPointerException ( fmsg ); 389 } 390 try { 391 com.sun.org.apache.xpath.internal.XPath xpath = new XPath (expression, null, 392 prefixResolver, com.sun.org.apache.xpath.internal.XPath.SELECT ); 393 // Can have errorListener 394 XPathExpressionImpl ximpl = new XPathExpressionImpl (xpath, 395 prefixResolver, functionResolver, variableResolver, 396 featureSecureProcessing, useServiceMechanism, featureManager ); 397 return ximpl; 398 } catch ( javax.xml.transform.TransformerException te ) { 399 throw new XPathExpressionException ( te ) ; 400 } 401 } 402 403 404 /** 405 * <p>Evaluate an XPath expression in the context of the specified <code>InputSource</code> 406 * and return the result as the specified type.</p> 407 * 408 * <p>This method builds a data model for the {@link InputSource} and calls 409 * {@link #evaluate(String expression, Object item, QName returnType)} on the resulting document object.</p> 410 * 411 * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec 412 * for context item evaluation, 413 * variable, function and QName resolution and return type conversion.</p> 414 * 415 * <p>If <code>returnType</code> is not one of the types defined in {@link XPathConstants}, 416 * then an <code>IllegalArgumentException</code> is thrown.</p> 417 * 418 * <p>If <code>expression</code>, <code>source</code> or <code>returnType</code> is <code>null</code>, 419 * then a <code>NullPointerException</code> is thrown.</p> 420 * 421 * @param expression The XPath expression. 422 * @param source The input source of the document to evaluate over. 423 * @param returnType The desired return type. 424 * 425 * @return The <code>Object</code> that encapsulates the result of evaluating the expression. 426 * 427 * @throws XPathExpressionException If expression cannot be evaluated. 428 * @throws IllegalArgumentException If <code>returnType</code> is not one of the types defined in {@link XPathConstants}. 429 * @throws NullPointerException If <code>expression</code>, <code>source</code> or <code>returnType</code> 430 * is <code>null</code>. 431 */ 432 public Object evaluate(String expression, InputSource source, 433 QName returnType) throws XPathExpressionException { 434 // Checking validity of different parameters 435 if( source== null ) { 436 String fmsg = XSLMessages.createXPATHMessage( 437 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 438 new Object[] {"source"} ); 439 throw new NullPointerException ( fmsg ); 440 } 441 if ( expression == null ) { 442 String fmsg = XSLMessages.createXPATHMessage( 443 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 444 new Object[] {"XPath expression"} ); 445 throw new NullPointerException ( fmsg ); 446 } 447 if ( returnType == null ) { 448 String fmsg = XSLMessages.createXPATHMessage( 449 XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, 450 new Object[] {"returnType"} ); 451 throw new NullPointerException ( fmsg ); 452 } 453 454 //Checking if requested returnType is supported. 455 //returnType need to be defined in XPathConstants 456 if ( !isSupported ( returnType ) ) { 457 String fmsg = XSLMessages.createXPATHMessage( 458 XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE, 459 new Object[] { returnType.toString() } ); 460 throw new IllegalArgumentException ( fmsg ); 461 } 462 463 try { 464 465 Document document = getParser().parse( source ); 466 467 XObject resultObject = eval( expression, document ); 468 return getResultAsType( resultObject, returnType ); 469 } catch ( SAXException e ) { 470 throw new XPathExpressionException ( e ); 471 } catch( IOException e ) { 472 throw new XPathExpressionException ( e ); 473 } catch ( javax.xml.transform.TransformerException te ) { 474 Throwable nestedException = te.getException(); 475 if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) { 476 throw (javax.xml.xpath.XPathFunctionException)nestedException; 477 } else { 478 throw new XPathExpressionException ( te ); 479 } 480 } 481 482 } 483 484 485 486 487 /** 488 * <p>Evaluate an XPath expression in the context of the specified <code>InputSource</code> 489 * and return the result as a <code>String</code>.</p> 490 * 491 * <p>This method calls {@link #evaluate(String expression, InputSource source, QName returnType)} with a 492 * <code>returnType</code> of {@link XPathConstants#STRING}.</p> 493 * 494 * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec 495 * for context item evaluation, 496 * variable, function and QName resolution and return type conversion.</p> 497 * 498 * <p>If <code>expression</code> or <code>source</code> is <code>null</code>, 499 * then a <code>NullPointerException</code> is thrown.</p> 500 * 501 * @param expression The XPath expression. 502 * @param source The <code>InputSource</code> of the document to evaluate over. 503 * 504 * @return The <code>String</code> that is the result of evaluating the expression and 505 * converting the result to a <code>String</code>. 506 * 507 * @throws XPathExpressionException If expression cannot be evaluated. 508 * @throws NullPointerException If <code>expression</code> or <code>source</code> is <code>null</code>. 509 */ 510 public String evaluate(String expression, InputSource source) 511 throws XPathExpressionException { 512 return (String)this.evaluate( expression, source, XPathConstants.STRING ); 513 } 514 515 /** 516 * <p>Reset this <code>XPath</code> to its original configuration.</p> 517 * 518 * <p><code>XPath</code> is reset to the same state as when it was created with 519 * {@link XPathFactory#newXPath()}. 520 * <code>reset()</code> is designed to allow the reuse of existing <code>XPath</code>s 521 * thus saving resources associated with the creation of new <code>XPath</code>s.</p> 522 * 523 * <p>The reset <code>XPath</code> is not guaranteed to have the same 524 * {@link XPathFunctionResolver}, {@link XPathVariableResolver} 525 * or {@link NamespaceContext} <code>Object</code>s, e.g. {@link Object#equals(Object obj)}. 526 * It is guaranteed to have a functionally equal <code>XPathFunctionResolver</code>, 527 * <code>XPathVariableResolver</code> 528 * and <code>NamespaceContext</code>.</p> 529 */ 530 public void reset() { 531 this.variableResolver = this.origVariableResolver; 532 this.functionResolver = this.origFunctionResolver; 533 this.namespaceContext = null; 534 } 535 536 }