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 }