/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // $Id: XPathImpl.java 524814 2007-04-02 15:52:11Z zongaro $ package org.apache.xpath.jaxp; import javax.xml.namespace.QName; import javax.xml.namespace.NamespaceContext; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFunctionResolver; import javax.xml.xpath.XPathVariableResolver; import javax.xml.xpath.XPathExpression; import org.apache.xml.dtm.DTM; import org.apache.xpath.*; import org.apache.xpath.objects.XObject; import org.apache.xpath.res.XPATHErrorResources; import org.apache.xalan.res.XSLMessages; import org.w3c.dom.Node; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.traversal.NodeIterator; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.xml.parsers.*; import java.io.IOException; /** * The XPathImpl class provides implementation for the methods defined in * javax.xml.xpath.XPath interface. This provide simple access to the results * of an XPath expression. * * * @version $Revision: 524814 $ * @author Ramesh Mandava */ public class XPathImpl implements javax.xml.xpath.XPath { // Private variables private XPathVariableResolver variableResolver; private XPathFunctionResolver functionResolver; private XPathVariableResolver origVariableResolver; private XPathFunctionResolver origFunctionResolver; private NamespaceContext namespaceContext=null; private JAXPPrefixResolver prefixResolver; // By default Extension Functions are allowed in XPath Expressions. If // Secure Processing Feature is set on XPathFactory then the invocation of // extensions function need to throw XPathFunctionException private boolean featureSecureProcessing = false; XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr ) { this.origVariableResolver = this.variableResolver = vr; this.origFunctionResolver = this.functionResolver = fr; } XPathImpl( XPathVariableResolver vr, XPathFunctionResolver fr, boolean featureSecureProcessing ) { this.origVariableResolver = this.variableResolver = vr; this.origFunctionResolver = this.functionResolver = fr; this.featureSecureProcessing = featureSecureProcessing; } /** *

Establishes a variable resolver.

* * @param resolver Variable Resolver */ public void setXPathVariableResolver(XPathVariableResolver resolver) { if ( resolver == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"XPathVariableResolver"} ); throw new NullPointerException( fmsg ); } this.variableResolver = resolver; } /** *

Returns the current variable resolver.

* * @return Current variable resolver */ public XPathVariableResolver getXPathVariableResolver() { return variableResolver; } /** *

Establishes a function resolver.

* * @param resolver XPath function resolver */ public void setXPathFunctionResolver(XPathFunctionResolver resolver) { if ( resolver == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"XPathFunctionResolver"} ); throw new NullPointerException( fmsg ); } this.functionResolver = resolver; } /** *

Returns the current function resolver.

* * @return Current function resolver */ public XPathFunctionResolver getXPathFunctionResolver() { return functionResolver; } /** *

Establishes a namespace context.

* * @param nsContext Namespace context to use */ public void setNamespaceContext(NamespaceContext nsContext) { if ( nsContext == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"NamespaceContext"} ); throw new NullPointerException( fmsg ); } this.namespaceContext = nsContext; this.prefixResolver = new JAXPPrefixResolver ( nsContext ); } /** *

Returns the current namespace context.

* * @return Current Namespace context */ public NamespaceContext getNamespaceContext() { return namespaceContext; } private static Document d = null; private static DocumentBuilder getParser() { try { // we'd really like to cache those DocumentBuilders, but we can't because: // 1. thread safety. parsers are not thread-safe, so at least // we need one instance per a thread. // 2. parsers are non-reentrant, so now we are looking at having a // pool of parsers. // 3. then the class loading issue. The look-up procedure of // DocumentBuilderFactory.newInstance() depends on context class loader // and system properties, which may change during the execution of JVM. // // so we really have to create a fresh DocumentBuilder every time we need one // - KK DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware( true ); dbf.setValidating( false ); return dbf.newDocumentBuilder(); } catch (ParserConfigurationException e) { // this should never happen with a well-behaving JAXP implementation. throw new Error(e.toString()); } } private static Document getDummyDocument( ) { // we don't need synchronization here; even if two threads // enter this code at the same time, we just waste a little time if(d==null) { DOMImplementation dim = getParser().getDOMImplementation(); d = dim.createDocument("http://java.sun.com/jaxp/xpath", "dummyroot", null); } return d; } private XObject eval(String expression, Object contextItem) throws javax.xml.transform.TransformerException { org.apache.xpath.XPath xpath = new org.apache.xpath.XPath( expression, null, prefixResolver, org.apache.xpath.XPath.SELECT ); org.apache.xpath.XPathContext xpathSupport = null; // Create an XPathContext that doesn't support pushing and popping of // variable resolution scopes. Sufficient for simple XPath 1.0 // expressions. if ( functionResolver != null ) { JAXPExtensionsProvider jep = new JAXPExtensionsProvider( functionResolver, featureSecureProcessing ); xpathSupport = new org.apache.xpath.XPathContext(jep, false); } else { xpathSupport = new org.apache.xpath.XPathContext(false); } XObject xobj = null; xpathSupport.setVarStack(new JAXPVariableStack(variableResolver)); // If item is null, then we will create a a Dummy contextNode if ( contextItem instanceof Node ) { xobj = xpath.execute (xpathSupport, (Node)contextItem, prefixResolver ); } else { xobj = xpath.execute ( xpathSupport, DTM.NULL, prefixResolver ); } return xobj; } /** *

Evaluate an XPath expression in the specified context and return the result as the specified type.

* *

See "Evaluation of XPath Expressions" section of JAXP 1.3 spec * for context item evaluation, * variable, function and QName resolution and return type conversion.

* *

If returnType is not one of the types defined in {@link XPathConstants} ( * {@link XPathConstants#NUMBER NUMBER}, * {@link XPathConstants#STRING STRING}, * {@link XPathConstants#BOOLEAN BOOLEAN}, * {@link XPathConstants#NODE NODE} or * {@link XPathConstants#NODESET NODESET}) * then an IllegalArgumentException is thrown.

* *

If a null value is provided for * item, an empty document will be used for the * context. * If expression or returnType is null, then a * NullPointerException is thrown.

* * @param expression The XPath expression. * @param item The starting context (node or node list, for example). * @param returnType The desired return type. * * @return Result of evaluating an XPath expression as an Object of returnType. * * @throws XPathExpressionException If expression cannot be evaluated. * @throws IllegalArgumentException If returnType is not one of the types defined in {@link XPathConstants}. * @throws NullPointerException If expression or returnType is null. */ public Object evaluate(String expression, Object item, QName returnType) throws XPathExpressionException { if ( expression == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"XPath expression"} ); throw new NullPointerException ( fmsg ); } if ( returnType == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"returnType"} ); throw new NullPointerException ( fmsg ); } // Checking if requested returnType is supported. returnType need to // be defined in XPathConstants if ( !isSupported ( returnType ) ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE, new Object[] { returnType.toString() } ); throw new IllegalArgumentException ( fmsg ); } try { XObject resultObject = eval( expression, item ); return getResultAsType( resultObject, returnType ); } catch ( java.lang.NullPointerException npe ) { // If VariableResolver returns null Or if we get // NullPointerException at this stage for some other reason // then we have to reurn XPathException throw new XPathExpressionException ( npe ); } catch ( javax.xml.transform.TransformerException te ) { Throwable nestedException = te.getException(); if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) { throw (javax.xml.xpath.XPathFunctionException)nestedException; } else { // For any other exceptions we need to throw // XPathExpressionException ( as per spec ) throw new XPathExpressionException ( te ); } } } private boolean isSupported( QName returnType ) { if ( ( returnType.equals( XPathConstants.STRING ) ) || ( returnType.equals( XPathConstants.NUMBER ) ) || ( returnType.equals( XPathConstants.BOOLEAN ) ) || ( returnType.equals( XPathConstants.NODE ) ) || ( returnType.equals( XPathConstants.NODESET ) ) ) { return true; } return false; } private Object getResultAsType( XObject resultObject, QName returnType ) throws javax.xml.transform.TransformerException { // XPathConstants.STRING if ( returnType.equals( XPathConstants.STRING ) ) { return resultObject.str(); } // XPathConstants.NUMBER if ( returnType.equals( XPathConstants.NUMBER ) ) { return new Double ( resultObject.num()); } // XPathConstants.BOOLEAN if ( returnType.equals( XPathConstants.BOOLEAN ) ) { return new Boolean( resultObject.bool()); } // XPathConstants.NODESET ---ORdered, UNOrdered??? if ( returnType.equals( XPathConstants.NODESET ) ) { return resultObject.nodelist(); } // XPathConstants.NODE if ( returnType.equals( XPathConstants.NODE ) ) { NodeIterator ni = resultObject.nodeset(); //Return the first node, or null return ni.nextNode(); } String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE, new Object[] { returnType.toString()}); throw new IllegalArgumentException( fmsg ); } /** *

Evaluate an XPath expression in the specified context and return the result as a String.

* *

This method calls {@link #evaluate(String expression, Object item, QName returnType)} with a returnType of * {@link XPathConstants#STRING}.

* *

See "Evaluation of XPath Expressions" of JAXP 1.3 spec * for context item evaluation, * variable, function and QName resolution and return type conversion.

* *

If a null value is provided for * item, an empty document will be used for the * context. * If expression is null, then a NullPointerException is thrown.

* * @param expression The XPath expression. * @param item The starting context (node or node list, for example). * * @return The String that is the result of evaluating the expression and * converting the result to a String. * * @throws XPathExpressionException If expression cannot be evaluated. * @throws NullPointerException If expression is null. */ public String evaluate(String expression, Object item) throws XPathExpressionException { return (String)this.evaluate( expression, item, XPathConstants.STRING ); } /** *

Compile an XPath expression for later evaluation.

* *

If expression contains any {@link XPathFunction}s, * they must be available via the {@link XPathFunctionResolver}. * An {@link XPathExpressionException} will be thrown if the XPathFunction * cannot be resovled with the XPathFunctionResolver.

* *

If expression is null, a NullPointerException is thrown.

* * @param expression The XPath expression. * * @return Compiled XPath expression. * @throws XPathExpressionException If expression cannot be compiled. * @throws NullPointerException If expression is null. */ public XPathExpression compile(String expression) throws XPathExpressionException { if ( expression == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"XPath expression"} ); throw new NullPointerException ( fmsg ); } try { org.apache.xpath.XPath xpath = new XPath (expression, null, prefixResolver, org.apache.xpath.XPath.SELECT ); // Can have errorListener XPathExpressionImpl ximpl = new XPathExpressionImpl (xpath, prefixResolver, functionResolver, variableResolver, featureSecureProcessing ); return ximpl; } catch ( javax.xml.transform.TransformerException te ) { throw new XPathExpressionException ( te ) ; } } /** *

Evaluate an XPath expression in the context of the specified InputSource * and return the result as the specified type.

* *

This method builds a data model for the {@link InputSource} and calls * {@link #evaluate(String expression, Object item, QName returnType)} on the resulting document object.

* *

See "Evaluation of XPath Expressions" section of JAXP 1.3 spec * for context item evaluation, * variable, function and QName resolution and return type conversion.

* *

If returnType is not one of the types defined in {@link XPathConstants}, * then an IllegalArgumentException is thrown.

* *

If expression, source or returnType is null, * then a NullPointerException is thrown.

* * @param expression The XPath expression. * @param source The input source of the document to evaluate over. * @param returnType The desired return type. * * @return The Object that encapsulates the result of evaluating the expression. * * @throws XPathExpressionException If expression cannot be evaluated. * @throws IllegalArgumentException If returnType is not one of the types defined in {@link XPathConstants}. * @throws NullPointerException If expression, source or returnType * is null. */ public Object evaluate(String expression, InputSource source, QName returnType) throws XPathExpressionException { // Checking validity of different parameters if( source== null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"source"} ); throw new NullPointerException ( fmsg ); } if ( expression == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"XPath expression"} ); throw new NullPointerException ( fmsg ); } if ( returnType == null ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_ARG_CANNOT_BE_NULL, new Object[] {"returnType"} ); throw new NullPointerException ( fmsg ); } //Checking if requested returnType is supported. //returnType need to be defined in XPathConstants if ( !isSupported ( returnType ) ) { String fmsg = XSLMessages.createXPATHMessage( XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE, new Object[] { returnType.toString() } ); throw new IllegalArgumentException ( fmsg ); } try { Document document = getParser().parse( source ); XObject resultObject = eval( expression, document ); return getResultAsType( resultObject, returnType ); } catch ( SAXException e ) { throw new XPathExpressionException ( e ); } catch( IOException e ) { throw new XPathExpressionException ( e ); } catch ( javax.xml.transform.TransformerException te ) { Throwable nestedException = te.getException(); if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) { throw (javax.xml.xpath.XPathFunctionException)nestedException; } else { throw new XPathExpressionException ( te ); } } } /** *

Evaluate an XPath expression in the context of the specified InputSource * and return the result as a String.

* *

This method calls {@link #evaluate(String expression, InputSource source, QName returnType)} with a * returnType of {@link XPathConstants#STRING}.

* *

See "Evaluation of XPath Expressions" section of JAXP 1.3 spec * for context item evaluation, * variable, function and QName resolution and return type conversion.

* *

If expression or source is null, * then a NullPointerException is thrown.

* * @param expression The XPath expression. * @param source The InputSource of the document to evaluate over. * * @return The String that is the result of evaluating the expression and * converting the result to a String. * * @throws XPathExpressionException If expression cannot be evaluated. * @throws NullPointerException If expression or source is null. */ public String evaluate(String expression, InputSource source) throws XPathExpressionException { return (String)this.evaluate( expression, source, XPathConstants.STRING ); } /** *

Reset this XPath to its original configuration.

* *

XPath is reset to the same state as when it was created with * {@link XPathFactory#newXPath()}. * reset() is designed to allow the reuse of existing XPaths * thus saving resources associated with the creation of new XPaths.

* *

The reset XPath is not guaranteed to have the same * {@link XPathFunctionResolver}, {@link XPathVariableResolver} * or {@link NamespaceContext} Objects, e.g. {@link Object#equals(Object obj)}. * It is guaranteed to have a functionally equal XPathFunctionResolver, * XPathVariableResolver * and NamespaceContext.

*/ public void reset() { this.variableResolver = this.origVariableResolver; this.functionResolver = this.origFunctionResolver; this.namespaceContext = null; } }