001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    // $Id: XPathExpressionImpl.java 1225277 2011-12-28 18:50:56Z mrglavas $
019    
020    package org.apache.xpath.jaxp;
021    
022    import javax.xml.namespace.QName;
023    import javax.xml.parsers.DocumentBuilder;
024    import javax.xml.parsers.DocumentBuilderFactory;
025    import javax.xml.xpath.XPathConstants;
026    import javax.xml.xpath.XPathExpressionException;
027    import javax.xml.xpath.XPathFunctionResolver;
028    import javax.xml.xpath.XPathVariableResolver;
029    
030    import org.apache.xalan.res.XSLMessages;
031    import org.apache.xpath.objects.XObject;
032    import org.apache.xpath.res.XPATHErrorResources;
033    import org.w3c.dom.DOMImplementation;
034    import org.w3c.dom.Document;
035    import org.w3c.dom.Node;
036    import org.w3c.dom.traversal.NodeIterator;
037    import org.xml.sax.InputSource;
038    
039    /**
040     * The XPathExpression interface encapsulates a (compiled) XPath expression.
041     *
042     * @version $Revision: 1225277 $
043     * @author  Ramesh Mandava
044     */
045    public class XPathExpressionImpl  implements javax.xml.xpath.XPathExpression{
046    
047        private XPathFunctionResolver functionResolver;
048        private XPathVariableResolver variableResolver;
049        private JAXPPrefixResolver prefixResolver;
050        private org.apache.xpath.XPath xpath;
051    
052        // By default Extension Functions are allowed in XPath Expressions. If
053        // Secure Processing Feature is set on XPathFactory then the invocation of
054        // extensions function need to throw XPathFunctionException
055        private boolean featureSecureProcessing = false;
056    
057        /** Protected constructor to prevent direct instantiation; use compile()
058         * from the context.
059         */
060        protected XPathExpressionImpl() { };
061    
062        protected XPathExpressionImpl(org.apache.xpath.XPath xpath, 
063                JAXPPrefixResolver prefixResolver, 
064                XPathFunctionResolver functionResolver,
065                XPathVariableResolver variableResolver ) { 
066            this.xpath = xpath;
067            this.prefixResolver = prefixResolver;
068            this.functionResolver = functionResolver;
069            this.variableResolver = variableResolver;
070            this.featureSecureProcessing = false;
071        };
072    
073        protected XPathExpressionImpl(org.apache.xpath.XPath xpath,
074                JAXPPrefixResolver prefixResolver,
075                XPathFunctionResolver functionResolver,
076                XPathVariableResolver variableResolver,
077                boolean featureSecureProcessing ) { 
078            this.xpath = xpath;
079            this.prefixResolver = prefixResolver;
080            this.functionResolver = functionResolver;
081            this.variableResolver = variableResolver;
082            this.featureSecureProcessing = featureSecureProcessing;
083        };
084    
085        public void setXPath (org.apache.xpath.XPath xpath ) {
086            this.xpath = xpath;
087        }  
088    
089        public Object eval(Object item, QName returnType)
090                throws javax.xml.transform.TransformerException {
091            XObject resultObject = eval ( item );
092            return getResultAsType( resultObject, returnType );
093        }
094        
095        private XObject eval ( Object contextItem )
096                throws javax.xml.transform.TransformerException {
097            org.apache.xpath.XPathContext xpathSupport = null;
098    
099            // Create an XPathContext that doesn't support pushing and popping of
100            // variable resolution scopes.  Sufficient for simple XPath 1.0
101            // expressions.
102            if ( functionResolver != null ) {
103                JAXPExtensionsProvider jep = new JAXPExtensionsProvider(
104                        functionResolver, featureSecureProcessing );
105                xpathSupport = new org.apache.xpath.XPathContext(jep, false);
106            } else {
107                xpathSupport = new org.apache.xpath.XPathContext(false);
108            }
109    
110            xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
111            XObject xobj = null;
112              
113            Node contextNode = (Node)contextItem;
114            // We always need to have a ContextNode with Xalan XPath implementation
115            // To allow simple expression evaluation like 1+1 we are setting 
116            // dummy Document as Context Node
117            if ( contextNode == null ) {
118                  contextNode = getDummyDocument();
119            } 
120    
121            xobj = xpath.execute(xpathSupport, contextNode, prefixResolver );
122            return xobj;
123        }
124    
125    
126        /**
127         * <p>Evaluate the compiled XPath expression in the specified context and
128         *  return the result as the specified type.</p>
129         *
130         * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
131         * for context item evaluation,
132         * variable, function and QName resolution and return type conversion.</p>
133         *
134         * <p>If <code>returnType</code> is not one of the types defined 
135         * in {@link XPathConstants},
136         * then an <code>IllegalArgumentException</code> is thrown.</p>
137         *
138         * <p>If a <code>null</code> value is provided for
139         * <code>item</code>, an empty document will be used for the
140         * context.
141         * If <code>returnType</code> is <code>null</code>, then a 
142         * <code>NullPointerException</code> is thrown.</p>
143         *
144         * @param item The starting context (node or node list, for example).
145         * @param returnType The desired return type.
146         *
147         * @return The <code>Object</code> that is the result of evaluating the
148         * expression and converting the result to
149         *   <code>returnType</code>.
150         *
151         * @throws XPathExpressionException If the expression cannot be evaluated.
152         * @throws IllegalArgumentException If <code>returnType</code> is not one
153         * of the types defined in {@link XPathConstants}.
154         * @throws NullPointerException If  <code>returnType</code> is
155         * <code>null</code>.
156         */
157        public Object evaluate(Object item, QName returnType)
158            throws XPathExpressionException {
159            //Validating parameters to enforce constraints defined by JAXP spec
160            if ( returnType == null ) {
161               //Throwing NullPointerException as defined in spec
162                String fmsg = XSLMessages.createXPATHMessage( 
163                        XPATHErrorResources.ER_ARG_CANNOT_BE_NULL,
164                        new Object[] {"returnType"} );
165                throw new NullPointerException( fmsg );
166            }
167            // Checking if requested returnType is supported. returnType need to be
168            // defined in XPathConstants 
169            if ( !isSupported ( returnType ) ) {
170                String fmsg = XSLMessages.createXPATHMessage( 
171                        XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
172                        new Object[] { returnType.toString() } );
173                throw new IllegalArgumentException ( fmsg );
174            }
175            try { 
176                return eval( item, returnType);
177            } catch ( java.lang.NullPointerException npe ) {
178                // If VariableResolver returns null Or if we get 
179                // NullPointerException at this stage for some other reason
180                // then we have to reurn XPathException
181                throw new XPathExpressionException ( npe );
182            } catch ( javax.xml.transform.TransformerException te ) {
183                Throwable nestedException = te.getException();
184                if ( nestedException instanceof javax.xml.xpath.XPathFunctionException ) {
185                    throw (javax.xml.xpath.XPathFunctionException)nestedException;
186                } else {
187                    // For any other exceptions we need to throw
188                    // XPathExpressionException ( as per spec )
189                    throw new XPathExpressionException( te);
190                }
191            }
192    
193        }
194        
195        /**
196         * <p>Evaluate the compiled XPath expression in the specified context and
197         * return the result as a <code>String</code>.</p>
198         *
199         * <p>This method calls {@link #evaluate(Object item, QName returnType)}
200         * with a <code>returnType</code> of
201         * {@link XPathConstants#STRING}.</p>
202         *
203         * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
204         *  for context item evaluation,
205         * variable, function and QName resolution and return type conversion.</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         *
211         * @param item The starting context (node or node list, for example).
212         *
213         * @return The <code>String</code> that is the result of evaluating the
214         * expression and converting the result to a
215         *   <code>String</code>.
216         *
217         * @throws XPathExpressionException If the expression cannot be evaluated.
218         */
219        public String evaluate(Object item) 
220            throws XPathExpressionException {
221            return (String)this.evaluate( item, XPathConstants.STRING );
222        }
223    
224    
225    
226        static DocumentBuilderFactory dbf = null;
227        static DocumentBuilder db = null;
228        static Document d = null;
229    
230        /**
231         * <p>Evaluate the compiled XPath expression in the context of the 
232         * specified <code>InputSource</code> and return the result as the
233         *  specified type.</p>
234         *
235         * <p>This method builds a data model for the {@link InputSource} and calls
236         * {@link #evaluate(Object item, QName returnType)} on the resulting 
237         * document object.</p>
238         *
239         * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
240         *  for context item evaluation,
241         * variable, function and QName resolution and return type conversion.</p>
242         *
243         * <p>If <code>returnType</code> is not one of the types defined in 
244         * {@link XPathConstants},
245         * then an <code>IllegalArgumentException</code> is thrown.</p>
246         *
247         *<p>If <code>source</code> or <code>returnType</code> is <code>null</code>,
248         * then a <code>NullPointerException</code> is thrown.</p>
249         *
250         * @param source The <code>InputSource</code> of the document to evaluate
251         * over.
252         * @param returnType The desired return type.
253         *
254         * @return The <code>Object</code> that is the result of evaluating the
255         * expression and converting the result to
256         *   <code>returnType</code>.
257         *
258         * @throws XPathExpressionException If the expression cannot be evaluated.
259         * @throws IllegalArgumentException If <code>returnType</code> is not one
260         * of the types defined in {@link XPathConstants}.
261         * @throws NullPointerException If  <code>source</code> or 
262         * <code>returnType</code> is <code>null</code>.
263         */
264        public Object evaluate(InputSource source, QName returnType)
265            throws XPathExpressionException {
266            if ( ( source == null ) || ( returnType == null ) ) {
267                String fmsg = XSLMessages.createXPATHMessage( 
268                        XPATHErrorResources.ER_SOURCE_RETURN_TYPE_CANNOT_BE_NULL,
269                        null );
270                throw new NullPointerException ( fmsg );
271            }
272            // Checking if requested returnType is supported. returnType need to be
273            // defined in XPathConstants 
274            if ( !isSupported ( returnType ) ) {
275                String fmsg = XSLMessages.createXPATHMessage( 
276                        XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
277                        new Object[] { returnType.toString() } );
278                throw new IllegalArgumentException ( fmsg );
279            }
280            try {
281                if ( dbf == null ) {
282                    dbf = DocumentBuilderFactory.newInstance();
283                    dbf.setNamespaceAware( true );
284                    dbf.setValidating( false );
285                }
286                db = dbf.newDocumentBuilder();
287                Document document = db.parse( source );
288                return eval(  document, returnType );
289            } catch ( Exception e ) {
290                throw new XPathExpressionException ( e );
291            }
292        }
293    
294        /**
295         * <p>Evaluate the compiled XPath expression in the context of the specified <code>InputSource</code> and return the result as a
296         * <code>String</code>.</p>
297         *
298         * <p>This method calls {@link #evaluate(InputSource source, QName returnType)} with a <code>returnType</code> of
299         * {@link XPathConstants#STRING}.</p>
300         *
301         * <p>See "Evaluation of XPath Expressions" section of JAXP 1.3 spec
302         * for context item evaluation,
303         * variable, function and QName resolution and return type conversion.</p>
304         *
305         * <p>If <code>source</code> is <code>null</code>, then a <code>NullPointerException</code> is thrown.</p>
306         *
307         * @param source The <code>InputSource</code> of the document to evaluate over.
308         *
309         * @return The <code>String</code> that is the result of evaluating the expression and converting the result to a
310         *   <code>String</code>.
311         *
312         * @throws XPathExpressionException If the expression cannot be evaluated.
313         * @throws NullPointerException If  <code>source</code> is <code>null</code>.
314         */
315        public String evaluate(InputSource source)
316            throws XPathExpressionException {
317            return (String)this.evaluate( source, XPathConstants.STRING );
318        }
319    
320        private boolean isSupported( QName returnType ) {
321            // XPathConstants.STRING
322            if ( ( returnType.equals( XPathConstants.STRING ) ) ||
323                 ( returnType.equals( XPathConstants.NUMBER ) ) ||
324                 ( returnType.equals( XPathConstants.BOOLEAN ) ) ||
325                 ( returnType.equals( XPathConstants.NODE ) ) ||
326                 ( returnType.equals( XPathConstants.NODESET ) )  ) {
327        
328                return true;
329            }
330            return false;
331         }
332    
333         private Object getResultAsType( XObject resultObject, QName returnType )
334            throws javax.xml.transform.TransformerException {
335            // XPathConstants.STRING
336            if ( returnType.equals( XPathConstants.STRING ) ) {
337                return resultObject.str();
338            }
339            // XPathConstants.NUMBER
340            if ( returnType.equals( XPathConstants.NUMBER ) ) {
341                return new Double ( resultObject.num());
342            }
343            // XPathConstants.BOOLEAN
344            if ( returnType.equals( XPathConstants.BOOLEAN ) ) {
345                return resultObject.bool() ? Boolean.TRUE : Boolean.FALSE;
346            }
347            // XPathConstants.NODESET ---ORdered, UNOrdered???
348            if ( returnType.equals( XPathConstants.NODESET ) ) {
349                return resultObject.nodelist();
350            }
351            // XPathConstants.NODE
352            if ( returnType.equals( XPathConstants.NODE ) ) {
353                NodeIterator ni = resultObject.nodeset();
354                //Return the first node, or null
355                return ni.nextNode();
356            }
357            // If isSupported check is already done then the execution path 
358            // shouldn't come here. Being defensive
359            String fmsg = XSLMessages.createXPATHMessage( 
360                    XPATHErrorResources.ER_UNSUPPORTED_RETURN_TYPE,
361                    new Object[] { returnType.toString()});
362            throw new IllegalArgumentException ( fmsg );
363        }
364    
365    
366        private static Document getDummyDocument( ) {
367            try {
368                if ( dbf == null ) {
369                    dbf = DocumentBuilderFactory.newInstance();
370                    dbf.setNamespaceAware( true );
371                    dbf.setValidating( false );
372                }
373                db = dbf.newDocumentBuilder();
374    
375                DOMImplementation dim = db.getDOMImplementation();
376                d = dim.createDocument("http://java.sun.com/jaxp/xpath",
377                    "dummyroot", null);
378                return d;
379            } catch ( Exception e ) {
380                e.printStackTrace();
381            }
382            return null;
383        }
384    
385    
386    
387    
388    }