001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  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    package org.apache.camel.builder.xml;
019    
020    import static org.apache.camel.converter.ObjectConverter.toBoolean;
021    import org.apache.camel.Exchange;
022    import org.apache.camel.Expression;
023    import org.apache.camel.Predicate;
024    import org.apache.camel.RuntimeExpressionException;
025    import org.apache.camel.Message;
026    import org.w3c.dom.Document;
027    import org.w3c.dom.Element;
028    import org.xml.sax.InputSource;
029    
030    import javax.xml.namespace.QName;
031    import javax.xml.xpath.XPath;
032    import javax.xml.xpath.XPathConstants;
033    import javax.xml.xpath.XPathExpression;
034    import javax.xml.xpath.XPathExpressionException;
035    import javax.xml.xpath.XPathFactory;
036    import javax.xml.xpath.XPathFactoryConfigurationException;
037    import javax.xml.xpath.XPathFunctionResolver;
038    import java.io.StringReader;
039    
040    /**
041     * Creates an XPath expression builder
042     *
043     * @version $Revision: 531854 $
044     */
045    public class XPathBuilder<E extends Exchange> implements Expression<E>, Predicate<E> {
046        private final String text;
047        private XPathFactory xpathFactory;
048        private Class documentType = Document.class;
049        private QName resultType = null;
050        private String objectModelUri = null;
051        private DefaultNamespaceContext namespaceContext;
052        private XPathFunctionResolver functionResolver;
053        private XPathExpression expression;
054        private MessageVariableResolver variableResolver = new MessageVariableResolver();
055    
056        public static XPathBuilder xpath(String text) {
057            return new XPathBuilder(text);
058        }
059    
060        public XPathBuilder(String text) {
061            this.text = text;
062        }
063    
064        @Override
065        public String toString() {
066            return "XPath: " + text;
067        }
068    
069        public boolean matches(E exchange) {
070            Object booleanResult = evaluateAs(exchange, XPathConstants.BOOLEAN);
071            return toBoolean(booleanResult);
072        }
073    
074        public void assertMatches(String text, E exchange) throws AssertionError {
075            Object booleanResult = evaluateAs(exchange, XPathConstants.BOOLEAN);
076            if (!toBoolean(booleanResult)) {
077                throw new AssertionError(this + " failed on " + exchange + " as returned <" + booleanResult + ">");
078            }
079        }
080    
081        public Object evaluate(E exchange) {
082            return evaluateAs(exchange, resultType);
083        }
084    
085    
086        // Builder methods
087        //-------------------------------------------------------------------------
088    
089        /**
090         * Sets the expression result type to boolean
091         *
092         * @return the current builder
093         */
094        public XPathBuilder<E> booleanResult() {
095            resultType = XPathConstants.BOOLEAN;
096            return this;
097        }
098    
099        /**
100         * Sets the expression result type to boolean
101         *
102         * @return the current builder
103         */
104        public XPathBuilder<E> nodeResult() {
105            resultType = XPathConstants.NODE;
106            return this;
107        }
108    
109        /**
110         * Sets the expression result type to boolean
111         *
112         * @return the current builder
113         */
114        public XPathBuilder<E> nodeSetResult() {
115            resultType = XPathConstants.NODESET;
116            return this;
117        }
118    
119        /**
120         * Sets the expression result type to boolean
121         *
122         * @return the current builder
123         */
124        public XPathBuilder<E> numberResult() {
125            resultType = XPathConstants.NUMBER;
126            return this;
127        }
128    
129        /**
130         * Sets the expression result type to boolean
131         *
132         * @return the current builder
133         */
134        public XPathBuilder<E> stringResult() {
135            resultType = XPathConstants.STRING;
136            return this;
137        }
138    
139        /**
140         * Sets the object model URI to use
141         *
142         * @return the current builder
143         */
144        public XPathBuilder<E> objectModel(String uri) {
145            this.objectModelUri = uri;
146            return this;
147        }
148    
149        /**
150         * Sets the {@link XPathFunctionResolver} instance to use on these XPath expressions
151         *
152         * @return the current builder
153         */
154        public XPathBuilder<E> functionResolver(XPathFunctionResolver functionResolver) {
155            this.functionResolver = functionResolver;
156            return this;
157        }
158    
159        /**
160         * Registers the namespace prefix and URI with the builder so that the prefix can be used in XPath expressions
161         *
162         * @param prefix is the namespace prefix that can be used in the XPath expressions
163         * @param uri is the namespace URI to which the prefix refers
164         * @return the current builder
165         */
166        public XPathBuilder<E> namespace(String prefix, String uri) {
167            getNamespaceContext().add(prefix, uri);
168            return this;
169        }
170    
171        /**
172         * Registers a variable (in the global namespace) which can be referred to from XPath expressions
173         */
174        public XPathBuilder<E> variable(String name, Object value) {
175            variableResolver.addVariable(name, value);
176            return this;
177        }
178    
179    
180        // Properties
181        //-------------------------------------------------------------------------
182        public XPathFactory getXPathFactory() throws XPathFactoryConfigurationException {
183            if (xpathFactory == null) {
184                if (objectModelUri != null) {
185                    xpathFactory = XPathFactory.newInstance(objectModelUri);
186                }
187                xpathFactory = XPathFactory.newInstance();
188            }
189            return xpathFactory;
190        }
191    
192        public void setXPathFactory(XPathFactory xpathFactory) {
193            this.xpathFactory = xpathFactory;
194        }
195    
196        public Class getDocumentType() {
197            return documentType;
198        }
199    
200        public void setDocumentType(Class documentType) {
201            this.documentType = documentType;
202        }
203    
204        public String getText() {
205            return text;
206        }
207    
208        public QName getResultType() {
209            return resultType;
210        }
211    
212        public DefaultNamespaceContext getNamespaceContext() {
213            if (namespaceContext == null) {
214                try {
215                    namespaceContext = new DefaultNamespaceContext(getXPathFactory());
216                }
217                catch (XPathFactoryConfigurationException e) {
218                    throw new RuntimeExpressionException(e);
219                }
220            }
221            return namespaceContext;
222        }
223    
224        public void setNamespaceContext(DefaultNamespaceContext namespaceContext) {
225            this.namespaceContext = namespaceContext;
226        }
227    
228        public XPathFunctionResolver getFunctionResolver() {
229            return functionResolver;
230        }
231    
232        public void setFunctionResolver(XPathFunctionResolver functionResolver) {
233            this.functionResolver = functionResolver;
234        }
235    
236        public XPathExpression getExpression() throws XPathFactoryConfigurationException, XPathExpressionException {
237            if (expression == null) {
238                expression = createXPathExpression();
239            }
240            return expression;
241        }
242    
243        public void setNamespacesFromDom(Element node) {
244            getNamespaceContext().setNamespacesFromDom(node);        
245        }
246    
247        // Implementation methods
248        //-------------------------------------------------------------------------
249    
250    
251        /**
252         * Evaluates the expression as the given result type
253         */
254        protected synchronized Object evaluateAs(E exchange, QName resultType) {
255            variableResolver.setExchange(exchange);
256            try {
257                Object document = getDocument(exchange);
258                if (resultType != null) {
259                    if (document instanceof InputSource) {
260                        InputSource inputSource = (InputSource) document;
261                        return getExpression().evaluate(inputSource, resultType);
262                    }
263                    else {
264                        return getExpression().evaluate(document, resultType);
265                    }
266                }
267                else {
268                    if (document instanceof InputSource) {
269                        InputSource inputSource = (InputSource) document;
270                        return getExpression().evaluate(inputSource);
271                    }
272                    else {
273                        return getExpression().evaluate(document);
274                    }
275                }
276            }
277            catch (XPathExpressionException e) {
278                throw new InvalidXPathExpression(getText(), e);
279            }
280            catch (XPathFactoryConfigurationException e) {
281                throw new InvalidXPathExpression(getText(), e);
282            }
283        }
284    
285        protected XPathExpression createXPathExpression() throws XPathExpressionException, XPathFactoryConfigurationException {
286            XPath xPath = getXPathFactory().newXPath();
287    
288            // lets now clear any factory references to avoid keeping them around
289            xpathFactory = null;
290    
291            xPath.setNamespaceContext(getNamespaceContext());
292            xPath.setXPathVariableResolver(variableResolver);
293            if (functionResolver != null) {
294                xPath.setXPathFunctionResolver(functionResolver);
295            }
296            return xPath.compile(text);
297        }
298    
299        /**
300         * Strategy method to extract the document from the exchange
301         */
302        protected Object getDocument(E exchange) {
303            Message in = exchange.getIn();
304            Class type = getDocumentType();
305            Object answer = null;
306            if (type != null) {
307                answer = in.getBody(type);
308            }
309            if (answer == null) {
310                answer = in.getBody();
311            }
312    
313            // lets try coerce some common types into something JAXP can deal with
314            if (answer instanceof String) {
315                answer = new InputSource(new StringReader(answer.toString()));
316            }
317            return answer;
318        }
319    
320    
321    }