001: /*
002: * Copyright 1999-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: // $Id: XPathImpl.java,v 1.1 2005/05/17 17:24:26 mkwan Exp $
017: package org.apache.xpath.jaxp;
018:
019: import javax.xml.namespace.QName;
020: import javax.xml.namespace.NamespaceContext;
021: import javax.xml.xpath.XPathExpressionException;
022: import javax.xml.xpath.XPathConstants;
023: import javax.xml.xpath.XPathFunctionResolver;
024: import javax.xml.xpath.XPathVariableResolver;
025: import javax.xml.xpath.XPathExpression;
026:
027: import org.apache.xml.dtm.DTM;
028: import org.apache.xpath.*;
029: import org.apache.xpath.objects.XObject;
030: import org.apache.xpath.res.XPATHErrorResources;
031: import org.apache.xalan.res.XSLMessages;
032:
033: import org.w3c.dom.Node;
034: import org.w3c.dom.DOMImplementation;
035: import org.w3c.dom.Document;
036: import org.w3c.dom.traversal.NodeIterator;
037:
038: import org.xml.sax.InputSource;
039: import org.xml.sax.SAXException;
040:
041: import javax.xml.parsers.*;
042:
043: import java.io.IOException;
044:
045: /**
046: * The XPathImpl class provides implementation for the methods defined in
047: * javax.xml.xpath.XPath interface. This provide simple access to the results
048: * of an XPath expression.
049: *
050: *
051: * @version $Revision: 1.1 $
052: * @author Ramesh Mandava
053: */
054: public class XPathImpl implements javax.xml.xpath.XPath {
055:
056: // Private variables
057: private XPathVariableResolver variableResolver;
058: private XPathFunctionResolver functionResolver;
059: private XPathVariableResolver origVariableResolver;
060: private XPathFunctionResolver origFunctionResolver;
061: private NamespaceContext namespaceContext = null;
062: private JAXPPrefixResolver prefixResolver;
063: // By default Extension Functions are allowed in XPath Expressions. If
064: // Secure Processing Feature is set on XPathFactory then the invocation of
065: // extensions function need to throw XPathFunctionException
066: private boolean featureSecureProcessing = false;
067:
068: XPathImpl(XPathVariableResolver vr, XPathFunctionResolver fr) {
069: this .origVariableResolver = this .variableResolver = vr;
070: this .origFunctionResolver = this .functionResolver = fr;
071: }
072:
073: XPathImpl(XPathVariableResolver vr, XPathFunctionResolver fr,
074: boolean featureSecureProcessing) {
075: this .origVariableResolver = this .variableResolver = vr;
076: this .origFunctionResolver = this .functionResolver = fr;
077: this .featureSecureProcessing = featureSecureProcessing;
078: }
079:
080: /**
081: * <p>Establishes a variable resolver.</p>
082: *
083: * @param resolver Variable Resolver
084: */
085: public void setXPathVariableResolver(XPathVariableResolver resolver) {
086: if (resolver == null) {
087: String fmsg = XSLMessages.createXPATHMessage(
088: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
089: new Object[] { "XPathVariableResolver" });
090: throw new NullPointerException(fmsg);
091: }
092: this .variableResolver = resolver;
093: }
094:
095: /**
096: * <p>Returns the current variable resolver.</p>
097: *
098: * @return Current variable resolver
099: */
100: public XPathVariableResolver getXPathVariableResolver() {
101: return variableResolver;
102: }
103:
104: /**
105: * <p>Establishes a function resolver.</p>
106: *
107: * @param resolver XPath function resolver
108: */
109: public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
110: if (resolver == null) {
111: String fmsg = XSLMessages.createXPATHMessage(
112: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
113: new Object[] { "XPathFunctionResolver" });
114: throw new NullPointerException(fmsg);
115: }
116: this .functionResolver = resolver;
117: }
118:
119: /**
120: * <p>Returns the current function resolver.</p>
121: *
122: * @return Current function resolver
123: */
124: public XPathFunctionResolver getXPathFunctionResolver() {
125: return functionResolver;
126: }
127:
128: /**
129: * <p>Establishes a namespace context.</p>
130: *
131: * @param nsContext Namespace context to use
132: */
133: public void setNamespaceContext(NamespaceContext nsContext) {
134: if (nsContext == null) {
135: String fmsg = XSLMessages.createXPATHMessage(
136: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
137: new Object[] { "NamespaceContext" });
138: throw new NullPointerException(fmsg);
139: }
140: this .namespaceContext = nsContext;
141: this .prefixResolver = new JAXPPrefixResolver(nsContext);
142: }
143:
144: /**
145: * <p>Returns the current namespace context.</p>
146: *
147: * @return Current Namespace context
148: */
149: public NamespaceContext getNamespaceContext() {
150: return namespaceContext;
151: }
152:
153: private static Document d = null;
154:
155: private static DocumentBuilder getParser() {
156: try {
157: // we'd really like to cache those DocumentBuilders, but we can't because:
158: // 1. thread safety. parsers are not thread-safe, so at least
159: // we need one instance per a thread.
160: // 2. parsers are non-reentrant, so now we are looking at having a
161: // pool of parsers.
162: // 3. then the class loading issue. The look-up procedure of
163: // DocumentBuilderFactory.newInstance() depends on context class loader
164: // and system properties, which may change during the execution of JVM.
165: //
166: // so we really have to create a fresh DocumentBuilder every time we need one
167: // - KK
168: DocumentBuilderFactory dbf = DocumentBuilderFactory
169: .newInstance();
170: dbf.setNamespaceAware(true);
171: dbf.setValidating(false);
172: return dbf.newDocumentBuilder();
173: } catch (ParserConfigurationException e) {
174: // this should never happen with a well-behaving JAXP implementation.
175: throw new Error(e.toString());
176: }
177: }
178:
179: private static Document getDummyDocument() {
180: // we don't need synchronization here; even if two threads
181: // enter this code at the same time, we just waste a little time
182: if (d == null) {
183: DOMImplementation dim = getParser().getDOMImplementation();
184: d = dim.createDocument("http://java.sun.com/jaxp/xpath",
185: "dummyroot", null);
186: }
187: return d;
188: }
189:
190: private XObject eval(String expression, Object contextItem)
191: throws javax.xml.transform.TransformerException {
192: org.apache.xpath.XPath xpath = new org.apache.xpath.XPath(
193: expression, null, prefixResolver,
194: org.apache.xpath.XPath.SELECT);
195: org.apache.xpath.XPathContext xpathSupport = null;
196: if (functionResolver != null) {
197: JAXPExtensionsProvider jep = new JAXPExtensionsProvider(
198: functionResolver, featureSecureProcessing);
199: xpathSupport = new org.apache.xpath.XPathContext(jep);
200: } else {
201: xpathSupport = new org.apache.xpath.XPathContext();
202: }
203:
204: XObject xobj = null;
205:
206: xpathSupport
207: .setVarStack(new JAXPVariableStack(variableResolver));
208:
209: // If item is null, then we will create a a Dummy contextNode
210: if (contextItem instanceof Node) {
211: xobj = xpath.execute(xpathSupport, (Node) contextItem,
212: prefixResolver);
213: } else {
214: xobj = xpath
215: .execute(xpathSupport, DTM.NULL, prefixResolver);
216: }
217:
218: return xobj;
219: }
220:
221: /**
222: * <p>Evaluate an <code>XPath</code> expression in the specified context and return the result as the specified type.</p>
223: *
224: * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
225: * for context item evaluation,
226: * variable, function and <code>QName</code> resolution and return type conversion.</p>
227: *
228: * <p>If <code>returnType</code> is not one of the types defined in {@link XPathConstants} (
229: * {@link XPathConstants#NUMBER NUMBER},
230: * {@link XPathConstants#STRING STRING},
231: * {@link XPathConstants#BOOLEAN BOOLEAN},
232: * {@link XPathConstants#NODE NODE} or
233: * {@link XPathConstants#NODESET NODESET})
234: * then an <code>IllegalArgumentException</code> is thrown.</p>
235: *
236: * <p>If a <code>null</code> value is provided for
237: * <code>item</code>, an empty document will be used for the
238: * context.
239: * If <code>expression</code> or <code>returnType</code> is <code>null</code>, then a
240: * <code>NullPointerException</code> is thrown.</p>
241: *
242: * @param expression The XPath expression.
243: * @param item The starting context (node or node list, for example).
244: * @param returnType The desired return type.
245: *
246: * @return Result of evaluating an XPath expression as an <code>Object</code> of <code>returnType</code>.
247: *
248: * @throws XPathExpressionException If <code>expression</code> cannot be evaluated.
249: * @throws IllegalArgumentException If <code>returnType</code> is not one of the types defined in {@link XPathConstants}.
250: * @throws NullPointerException If <code>expression</code> or <code>returnType</code> is <code>null</code>.
251: */
252: public Object evaluate(String expression, Object item,
253: QName returnType) throws XPathExpressionException {
254: if (expression == null) {
255: String fmsg = XSLMessages.createXPATHMessage(
256: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
257: new Object[] { "XPath expression" });
258: throw new NullPointerException(fmsg);
259: }
260: if (returnType == null) {
261: String fmsg = XSLMessages.createXPATHMessage(
262: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
263: new Object[] { "returnType" });
264: throw new NullPointerException(fmsg);
265: }
266: // Checking if requested returnType is supported. returnType need to
267: // be defined in XPathConstants
268: if (!isSupported(returnType)) {
269: String fmsg = XSLMessages.createXPATHMessage(
270: XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
271: new Object[] { returnType.toString() });
272: throw new IllegalArgumentException(fmsg);
273: }
274:
275: try {
276:
277: XObject resultObject = eval(expression, item);
278: return getResultAsType(resultObject, returnType);
279: } catch (java.lang.NullPointerException npe) {
280: // If VariableResolver returns null Or if we get
281: // NullPointerException at this stage for some other reason
282: // then we have to reurn XPathException
283: throw new XPathExpressionException(npe);
284: } catch (javax.xml.transform.TransformerException te) {
285: Throwable nestedException = te.getException();
286: if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
287: throw (javax.xml.xpath.XPathFunctionException) nestedException;
288: } else {
289: // For any other exceptions we need to throw
290: // XPathExpressionException ( as per spec )
291: throw new XPathExpressionException(te);
292: }
293: }
294:
295: }
296:
297: private boolean isSupported(QName returnType) {
298: if ((returnType.equals(XPathConstants.STRING))
299: || (returnType.equals(XPathConstants.NUMBER))
300: || (returnType.equals(XPathConstants.BOOLEAN))
301: || (returnType.equals(XPathConstants.NODE))
302: || (returnType.equals(XPathConstants.NODESET))) {
303:
304: return true;
305: }
306: return false;
307: }
308:
309: private Object getResultAsType(XObject resultObject,
310: QName returnType)
311: throws javax.xml.transform.TransformerException {
312: // XPathConstants.STRING
313: if (returnType.equals(XPathConstants.STRING)) {
314: return resultObject.str();
315: }
316: // XPathConstants.NUMBER
317: if (returnType.equals(XPathConstants.NUMBER)) {
318: return new Double(resultObject.num());
319: }
320: // XPathConstants.BOOLEAN
321: if (returnType.equals(XPathConstants.BOOLEAN)) {
322: return new Boolean(resultObject.bool());
323: }
324: // XPathConstants.NODESET ---ORdered, UNOrdered???
325: if (returnType.equals(XPathConstants.NODESET)) {
326: return resultObject.nodelist();
327: }
328: // XPathConstants.NODE
329: if (returnType.equals(XPathConstants.NODE)) {
330: NodeIterator ni = resultObject.nodeset();
331: //Return the first node, or null
332: return ni.nextNode();
333: }
334: String fmsg = XSLMessages.createXPATHMessage(
335: XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
336: new Object[] { returnType.toString() });
337: throw new IllegalArgumentException(fmsg);
338: }
339:
340: /**
341: * <p>Evaluate an XPath expression in the specified context and return the result as a <code>String</code>.</p>
342: *
343: * <p>This method calls {@link #evaluate(String expression, Object item, QName returnType)} with a <code>returnType</code> of
344: * {@link XPathConstants#STRING}.</p>
345: *
346: * <p>See "Evaluation of XPath Expressions" of JAXP 1.3 spec
347: * for context item evaluation,
348: * variable, function and QName resolution and return type conversion.</p>
349: *
350: * <p>If a <code>null</code> value is provided for
351: * <code>item</code>, an empty document will be used for the
352: * context.
353: * If <code>expression</code> is <code>null</code>, then a <code>NullPointerException</code> is thrown.</p>
354: *
355: * @param expression The XPath expression.
356: * @param item The starting context (node or node list, for example).
357: *
358: * @return The <code>String</code> that is the result of evaluating the expression and
359: * converting the result to a <code>String</code>.
360: *
361: * @throws XPathExpressionException If <code>expression</code> cannot be evaluated.
362: * @throws NullPointerException If <code>expression</code> is <code>null</code>.
363: */
364: public String evaluate(String expression, Object item)
365: throws XPathExpressionException {
366: return (String) this .evaluate(expression, item,
367: XPathConstants.STRING);
368: }
369:
370: /**
371: * <p>Compile an XPath expression for later evaluation.</p>
372: *
373: * <p>If <code>expression</code> contains any {@link XPathFunction}s,
374: * they must be available via the {@link XPathFunctionResolver}.
375: * An {@link XPathExpressionException} will be thrown if the <code>XPathFunction</code>
376: * cannot be resovled with the <code>XPathFunctionResolver</code>.</p>
377: *
378: * <p>If <code>expression</code> is <code>null</code>, a <code>NullPointerException</code> is thrown.</p>
379: *
380: * @param expression The XPath expression.
381: *
382: * @return Compiled XPath expression.
383:
384: * @throws XPathExpressionException If <code>expression</code> cannot be compiled.
385: * @throws NullPointerException If <code>expression</code> is <code>null</code>.
386: */
387: public XPathExpression compile(String expression)
388: throws XPathExpressionException {
389: if (expression == null) {
390: String fmsg = XSLMessages.createXPATHMessage(
391: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
392: new Object[] { "XPath expression" });
393: throw new NullPointerException(fmsg);
394: }
395: try {
396: org.apache.xpath.XPath xpath = new XPath(expression, null,
397: prefixResolver, org.apache.xpath.XPath.SELECT);
398: // Can have errorListener
399: XPathExpressionImpl ximpl = new XPathExpressionImpl(xpath,
400: prefixResolver, functionResolver, variableResolver,
401: featureSecureProcessing);
402: return ximpl;
403: } catch (javax.xml.transform.TransformerException te) {
404: throw new XPathExpressionException(te);
405: }
406: }
407:
408: /**
409: * <p>Evaluate an XPath expression in the context of the specified <code>InputSource</code>
410: * and return the result as the specified type.</p>
411: *
412: * <p>This method builds a data model for the {@link InputSource} and calls
413: * {@link #evaluate(String expression, Object item, QName returnType)} on the resulting document object.</p>
414: *
415: * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
416: * for context item evaluation,
417: * variable, function and QName resolution and return type conversion.</p>
418: *
419: * <p>If <code>returnType</code> is not one of the types defined in {@link XPathConstants},
420: * then an <code>IllegalArgumentException</code> is thrown.</p>
421: *
422: * <p>If <code>expression</code>, <code>source</code> or <code>returnType</code> is <code>null</code>,
423: * then a <code>NullPointerException</code> is thrown.</p>
424: *
425: * @param expression The XPath expression.
426: * @param source The input source of the document to evaluate over.
427: * @param returnType The desired return type.
428: *
429: * @return The <code>Object</code> that encapsulates the result of evaluating the expression.
430: *
431: * @throws XPathExpressionException If expression cannot be evaluated.
432: * @throws IllegalArgumentException If <code>returnType</code> is not one of the types defined in {@link XPathConstants}.
433: * @throws NullPointerException If <code>expression</code>, <code>source</code> or <code>returnType</code>
434: * is <code>null</code>.
435: */
436: public Object evaluate(String expression, InputSource source,
437: QName returnType) throws XPathExpressionException {
438: // Checking validity of different parameters
439: if (source == null) {
440: String fmsg = XSLMessages.createXPATHMessage(
441: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
442: new Object[] { "source" });
443: throw new NullPointerException(fmsg);
444: }
445: if (expression == null) {
446: String fmsg = XSLMessages.createXPATHMessage(
447: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
448: new Object[] { "XPath expression" });
449: throw new NullPointerException(fmsg);
450: }
451: if (returnType == null) {
452: String fmsg = XSLMessages.createXPATHMessage(
453: XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
454: new Object[] { "returnType" });
455: throw new NullPointerException(fmsg);
456: }
457:
458: //Checking if requested returnType is supported.
459: //returnType need to be defined in XPathConstants
460: if (!isSupported(returnType)) {
461: String fmsg = XSLMessages.createXPATHMessage(
462: XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
463: new Object[] { returnType.toString() });
464: throw new IllegalArgumentException(fmsg);
465: }
466:
467: try {
468:
469: Document document = getParser().parse(source);
470:
471: XObject resultObject = eval(expression, document);
472: return getResultAsType(resultObject, returnType);
473: } catch (SAXException e) {
474: throw new XPathExpressionException(e);
475: } catch (IOException e) {
476: throw new XPathExpressionException(e);
477: } catch (javax.xml.transform.TransformerException te) {
478: Throwable nestedException = te.getException();
479: if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
480: throw (javax.xml.xpath.XPathFunctionException) nestedException;
481: } else {
482: throw new XPathExpressionException(te);
483: }
484: }
485:
486: }
487:
488: /**
489: * <p>Evaluate an XPath expression in the context of the specified <code>InputSource</code>
490: * and return the result as a <code>String</code>.</p>
491: *
492: * <p>This method calls {@link #evaluate(String expression, InputSource source, QName returnType)} with a
493: * <code>returnType</code> of {@link XPathConstants#STRING}.</p>
494: *
495: * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
496: * for context item evaluation,
497: * variable, function and QName resolution and return type conversion.</p>
498: *
499: * <p>If <code>expression</code> or <code>source</code> is <code>null</code>,
500: * then a <code>NullPointerException</code> is thrown.</p>
501: *
502: * @param expression The XPath expression.
503: * @param source The <code>InputSource</code> of the document to evaluate over.
504: *
505: * @return The <code>String</code> that is the result of evaluating the expression and
506: * converting the result to a <code>String</code>.
507: *
508: * @throws XPathExpressionException If expression cannot be evaluated.
509: * @throws NullPointerException If <code>expression</code> or <code>source</code> is <code>null</code>.
510: */
511: public String evaluate(String expression, InputSource source)
512: throws XPathExpressionException {
513: return (String) this .evaluate(expression, source,
514: XPathConstants.STRING);
515: }
516:
517: /**
518: * <p>Reset this <code>XPath</code> to its original configuration.</p>
519: *
520: * <p><code>XPath</code> is reset to the same state as when it was created with
521: * {@link XPathFactory#newXPath()}.
522: * <code>reset()</code> is designed to allow the reuse of existing <code>XPath</code>s
523: * thus saving resources associated with the creation of new <code>XPath</code>s.</p>
524: *
525: * <p>The reset <code>XPath</code> is not guaranteed to have the same
526: * {@link XPathFunctionResolver}, {@link XPathVariableResolver}
527: * or {@link NamespaceContext} <code>Object</code>s, e.g. {@link Object#equals(Object obj)}.
528: * It is guaranteed to have a functionally equal <code>XPathFunctionResolver</code>,
529: * <code>XPathVariableResolver</code>
530: * and <code>NamespaceContext</code>.</p>
531: */
532: public void reset() {
533: this.variableResolver = this.origVariableResolver;
534: this.functionResolver = this.origFunctionResolver;
535: this.namespaceContext = null;
536: }
537:
538: }
|