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