001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.builder.xml;
018    
019    import java.io.File;
020    import java.io.InputStream;
021    import java.io.StringReader;
022    import java.util.HashSet;
023    import java.util.LinkedHashMap;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Properties;
027    import java.util.Queue;
028    import java.util.concurrent.ConcurrentHashMap;
029    import java.util.concurrent.ConcurrentLinkedQueue;
030    import javax.xml.namespace.QName;
031    import javax.xml.transform.dom.DOMSource;
032    import javax.xml.xpath.XPath;
033    import javax.xml.xpath.XPathConstants;
034    import javax.xml.xpath.XPathExpression;
035    import javax.xml.xpath.XPathExpressionException;
036    import javax.xml.xpath.XPathFactory;
037    import javax.xml.xpath.XPathFactoryConfigurationException;
038    import javax.xml.xpath.XPathFunction;
039    import javax.xml.xpath.XPathFunctionException;
040    import javax.xml.xpath.XPathFunctionResolver;
041    
042    import org.w3c.dom.Document;
043    import org.w3c.dom.Node;
044    import org.w3c.dom.NodeList;
045    import org.xml.sax.InputSource;
046    
047    import org.apache.camel.CamelContext;
048    import org.apache.camel.Exchange;
049    import org.apache.camel.Expression;
050    import org.apache.camel.NoTypeConversionAvailableException;
051    import org.apache.camel.Predicate;
052    import org.apache.camel.RuntimeExpressionException;
053    import org.apache.camel.WrappedFile;
054    import org.apache.camel.component.bean.BeanInvocation;
055    import org.apache.camel.impl.DefaultExchange;
056    import org.apache.camel.spi.Language;
057    import org.apache.camel.spi.NamespaceAware;
058    import org.apache.camel.support.ServiceSupport;
059    import org.apache.camel.util.ExchangeHelper;
060    import org.apache.camel.util.IOHelper;
061    import org.apache.camel.util.MessageHelper;
062    import org.apache.camel.util.ObjectHelper;
063    import org.slf4j.Logger;
064    import org.slf4j.LoggerFactory;
065    
066    import static org.apache.camel.builder.xml.Namespaces.DEFAULT_NAMESPACE;
067    import static org.apache.camel.builder.xml.Namespaces.FUNCTION_NAMESPACE;
068    import static org.apache.camel.builder.xml.Namespaces.IN_NAMESPACE;
069    import static org.apache.camel.builder.xml.Namespaces.OUT_NAMESPACE;
070    import static org.apache.camel.builder.xml.Namespaces.isMatchingNamespaceOrEmptyNamespace;
071    
072    /**
073     * Creates an XPath expression builder which creates a nodeset result by default.
074     * If you want to evaluate a String expression then call {@link #stringResult()}
075     * <p/>
076     * An XPath object is not thread-safe and not reentrant. In other words, it is the application's responsibility to make
077     * sure that one XPath object is not used from more than one thread at any given time, and while the evaluate method
078     * is invoked, applications may not recursively call the evaluate method.
079     * <p/>
080     * This implementation is thread safe by using thread locals and pooling to allow concurrency.
081     * <p/>
082     * <b>Important:</b> After configuring the {@link XPathBuilder} its adviced to invoke {@link #start()}
083     * to prepare the builder before using; though the builder will auto-start on first use.
084     *
085     * @see XPathConstants#NODESET
086     */
087    public class XPathBuilder extends ServiceSupport implements Expression, Predicate, NamespaceAware {
088        private static final Logger LOG = LoggerFactory.getLogger(XPathBuilder.class);
089        private static final String SAXON_OBJECT_MODEL_URI = "http://saxon.sf.net/jaxp/xpath/om";
090        private static final String OBTAIN_ALL_NS_XPATH = "//*/namespace::*";
091    
092        private static volatile XPathFactory defaultXPathFactory;
093    
094        private final Queue<XPathExpression> pool = new ConcurrentLinkedQueue<XPathExpression>();
095        private final Queue<XPathExpression> poolLogNamespaces = new ConcurrentLinkedQueue<XPathExpression>();
096        private final String text;
097        private final ThreadLocal<Exchange> exchange = new ThreadLocal<Exchange>();
098        private final MessageVariableResolver variableResolver = new MessageVariableResolver(exchange);
099        private final Map<String, String> namespaces = new ConcurrentHashMap<String, String>();
100        private volatile XPathFactory xpathFactory;
101        private volatile Class<?> documentType = Document.class;
102        // For some reason the default expression of "a/b" on a document such as
103        // <a><b>1</b><b>2</b></a>
104        // will evaluate as just "1" by default which is bizarre. So by default
105        // let's assume XPath expressions result in nodesets.
106        private volatile Class<?> resultType;
107        private volatile QName resultQName = XPathConstants.NODESET;
108        private volatile String objectModelUri;
109        private volatile DefaultNamespaceContext namespaceContext;
110        private volatile boolean logNamespaces;
111        private volatile XPathFunctionResolver functionResolver;
112        private volatile XPathFunction bodyFunction;
113        private volatile XPathFunction headerFunction;
114        private volatile XPathFunction outBodyFunction;
115        private volatile XPathFunction outHeaderFunction;
116        private volatile XPathFunction propertiesFunction;
117        private volatile XPathFunction simpleFunction;
118        /**
119         * The name of the header we want to apply the XPath expression to, which when set will cause
120         * the xpath to be evaluated on the required header, otherwise it will be applied to the body
121         */
122        private volatile String headerName;
123    
124        /**
125         * @param text The XPath expression
126         */
127        public XPathBuilder(String text) {
128            this.text = text;
129        }
130    
131        /**
132         * @param text The XPath expression
133         * @return A new XPathBuilder object
134         */
135        public static XPathBuilder xpath(String text) {
136            return new XPathBuilder(text);
137        }
138    
139        /**
140         * @param text The XPath expression
141         * @param resultType The result type that the XPath expression will return.
142         * @return A new XPathBuilder object
143         */
144        public static XPathBuilder xpath(String text, Class<?> resultType) {
145            XPathBuilder builder = new XPathBuilder(text);
146            builder.setResultType(resultType);
147            return builder;
148        }
149    
150        @Override
151        public String toString() {
152            return "XPath: " + text;
153        }
154    
155        public boolean matches(Exchange exchange) {
156            try {
157                Object booleanResult = evaluateAs(exchange, XPathConstants.BOOLEAN);
158                return exchange.getContext().getTypeConverter().convertTo(Boolean.class, booleanResult);
159            } finally {
160                // remove the thread local after usage
161                this.exchange.remove();
162            }
163        }
164    
165        public <T> T evaluate(Exchange exchange, Class<T> type) {
166            try {
167                Object result = evaluate(exchange);
168                return exchange.getContext().getTypeConverter().convertTo(type, exchange, result);
169            } finally {
170                // remove the thread local after usage
171                this.exchange.remove();
172            }
173        }
174    
175        /**
176         * Matches the given xpath using the provided body.
177         *
178         * @param context the camel context
179         * @param body    the body
180         * @return <tt>true</tt> if matches, <tt>false</tt> otherwise
181         */
182        public boolean matches(CamelContext context, Object body) {
183            ObjectHelper.notNull(context, "CamelContext");
184    
185            // create a dummy Exchange to use during matching
186            Exchange dummy = new DefaultExchange(context);
187            dummy.getIn().setBody(body);
188    
189            try {
190                return matches(dummy);
191            } finally {
192                // remove the thread local after usage
193                exchange.remove();
194            }
195        }
196    
197        /**
198         * Evaluates the given xpath using the provided body.
199         *
200         * @param context the camel context
201         * @param body    the body
202         * @param type    the type to return
203         * @return result of the evaluation
204         */
205        public <T> T evaluate(CamelContext context, Object body, Class<T> type) {
206            ObjectHelper.notNull(context, "CamelContext");
207    
208            // create a dummy Exchange to use during evaluation
209            Exchange dummy = new DefaultExchange(context);
210            dummy.getIn().setBody(body);
211    
212            try {
213                return evaluate(dummy, type);
214            } finally {
215                // remove the thread local after usage
216                exchange.remove();
217            }
218        }
219    
220        /**
221         * Evaluates the given xpath using the provided body as a String return type.
222         *
223         * @param context the camel context
224         * @param body    the body
225         * @return result of the evaluation
226         */
227        public String evaluate(CamelContext context, Object body) {
228            ObjectHelper.notNull(context, "CamelContext");
229    
230            // create a dummy Exchange to use during evaluation
231            Exchange dummy = new DefaultExchange(context);
232            dummy.getIn().setBody(body);
233    
234            setResultQName(XPathConstants.STRING);
235            try {
236                return evaluate(dummy, String.class);
237            } finally {
238                // remove the thread local after usage
239                this.exchange.remove();
240            }
241        }
242    
243        // Builder methods
244        // -------------------------------------------------------------------------
245    
246        /**
247         * Sets the expression result type to {@link XPathConstants#BOOLEAN}
248         *
249         * @return the current builder
250         */
251        public XPathBuilder booleanResult() {
252            resultQName = XPathConstants.BOOLEAN;
253            return this;
254        }
255    
256        /**
257         * Sets the expression result type to {@link XPathConstants#NODE}
258         *
259         * @return the current builder
260         */
261        public XPathBuilder nodeResult() {
262            resultQName = XPathConstants.NODE;
263            return this;
264        }
265    
266        /**
267         * Sets the expression result type to {@link XPathConstants#NODESET}
268         *
269         * @return the current builder
270         */
271        public XPathBuilder nodeSetResult() {
272            resultQName = XPathConstants.NODESET;
273            return this;
274        }
275    
276        /**
277         * Sets the expression result type to {@link XPathConstants#NUMBER}
278         *
279         * @return the current builder
280         */
281        public XPathBuilder numberResult() {
282            resultQName = XPathConstants.NUMBER;
283            return this;
284        }
285    
286        /**
287         * Sets the expression result type to {@link XPathConstants#STRING}
288         *
289         * @return the current builder
290         */
291        public XPathBuilder stringResult() {
292            resultQName = XPathConstants.STRING;
293            return this;
294        }
295    
296        /**
297         * Sets the expression result type to the given {@code resultType} 
298         *
299         * @return the current builder
300         */
301        public XPathBuilder resultType(Class<?> resultType) {
302            setResultType(resultType);
303            return this;
304        }
305    
306        /**
307         * Sets the object model URI to use
308         *
309         * @return the current builder
310         */
311        public XPathBuilder objectModel(String uri) {
312            // Careful! Setting the Object Model URI this way will set the *Default* XPath Factory, which since is a static field,
313            // will set the XPath Factory system-wide. Decide what to do, as changing this behaviour can break compatibility. Provided the setObjectModel which changes
314            // this instance's XPath Factory rather than the static field
315            this.objectModelUri = uri;
316            return this;
317        }
318    
319        /**
320         * Configures to use Saxon as the XPathFactory which allows you to use XPath 2.0 functions
321         * which may not be part of the build in JDK XPath parser.
322         *
323         * @return the current builder
324         */
325        public XPathBuilder saxon() {
326            this.objectModelUri = SAXON_OBJECT_MODEL_URI;
327            return this;
328        }
329    
330        /**
331         * Sets the {@link XPathFunctionResolver} instance to use on these XPath
332         * expressions
333         *
334         * @return the current builder
335         */
336        public XPathBuilder functionResolver(XPathFunctionResolver functionResolver) {
337            this.functionResolver = functionResolver;
338            return this;
339        }
340    
341        /**
342         * Registers the namespace prefix and URI with the builder so that the
343         * prefix can be used in XPath expressions
344         *
345         * @param prefix is the namespace prefix that can be used in the XPath
346         *               expressions
347         * @param uri    is the namespace URI to which the prefix refers
348         * @return the current builder
349         */
350        public XPathBuilder namespace(String prefix, String uri) {
351            namespaces.put(prefix, uri);
352            return this;
353        }
354    
355        /**
356         * Registers namespaces with the builder so that the registered
357         * prefixes can be used in XPath expressions
358         *
359         * @param namespaces is namespaces object that should be used in the
360         *                   XPath expression
361         * @return the current builder
362         */
363        public XPathBuilder namespaces(Namespaces namespaces) {
364            namespaces.configure(this);
365            return this;
366        }
367    
368        /**
369         * Registers a variable (in the global namespace) which can be referred to
370         * from XPath expressions
371         *
372         * @param name  name of variable
373         * @param value value of variable
374         * @return the current builder
375         */
376        public XPathBuilder variable(String name, Object value) {
377            getVariableResolver().addVariable(name, value);
378            return this;
379        }
380    
381        /**
382         * Configures the document type to use.
383         * <p/>
384         * The document type controls which kind of Class Camel should convert the payload
385         * to before doing the xpath evaluation.
386         * <p/>
387         * For example you can set it to {@link InputSource} to use SAX streams.
388         * By default Camel uses {@link Document} as the type.
389         *
390         * @param documentType the document type
391         * @return the current builder
392         */
393        public XPathBuilder documentType(Class<?> documentType) {
394            setDocumentType(documentType);
395            return this;
396        }
397    
398        /**
399         * Configures to use the provided XPath factory.
400         * <p/>
401         * Can be used to use Saxon instead of the build in factory from the JDK.
402         *
403         * @param xpathFactory the xpath factory to use
404         * @return the current builder.
405         */
406        public XPathBuilder factory(XPathFactory xpathFactory) {
407            setXPathFactory(xpathFactory);
408            return this;
409        }
410    
411        /**
412         * Activates trace logging of all discovered namespaces in the message - to simplify debugging namespace-related issues
413         * <p/>
414         * Namespaces are printed in Hashmap style <code>{xmlns:prefix=[namespaceURI], xmlns:prefix=[namespaceURI]}</code>.
415         * <p/>
416         * The implicit XML namespace is omitted (http://www.w3.org/XML/1998/namespace).
417         * XML allows for namespace prefixes to be redefined/overridden due to hierarchical scoping, i.e. prefix abc can be mapped to http://abc.com,
418         * and deeper in the document it can be mapped to http://def.com. When two prefixes are detected which are equal but are mapped to different
419         * namespace URIs, Camel will show all namespaces URIs it is mapped to in an array-style.
420         * <p/>
421         * This feature is disabled by default.
422         *
423         * @return the current builder.
424         */
425        public XPathBuilder logNamespaces() {
426            setLogNamespaces(true);
427            return this;
428        }
429    
430        // Properties
431        // -------------------------------------------------------------------------
432    
433        /**
434         * Gets the xpath factory, can be <tt>null</tt> if no custom factory has been assigned.
435         * <p/>
436         * A default factory will be assigned (if no custom assigned) when either starting this builder
437         * or on first evaluation.
438         *
439         * @return the factory, or <tt>null</tt> if this builder has not been started/used before.
440         */
441        public XPathFactory getXPathFactory() {
442            return xpathFactory;
443        }
444    
445        public void setXPathFactory(XPathFactory xpathFactory) {
446            this.xpathFactory = xpathFactory;
447        }
448    
449        public Class<?> getDocumentType() {
450            return documentType;
451        }
452    
453        public void setDocumentType(Class<?> documentType) {
454            this.documentType = documentType;
455        }
456    
457        public String getText() {
458            return text;
459        }
460    
461        public QName getResultQName() {
462            return resultQName;
463        }
464    
465        public void setResultQName(QName resultQName) {
466            this.resultQName = resultQName;
467        }
468    
469        public String getHeaderName() {
470            return headerName;
471        }
472        
473        public void setHeaderName(String headerName) { 
474            this.headerName = headerName;
475        }
476    
477        /**
478         * Gets the namespace context, can be <tt>null</tt> if no custom context has been assigned.
479         * <p/>
480         * A default context will be assigned (if no custom assigned) when either starting this builder
481         * or on first evaluation.
482         *
483         * @return the context, or <tt>null</tt> if this builder has not been started/used before.
484         */
485        public DefaultNamespaceContext getNamespaceContext() {
486            return namespaceContext;
487        }
488    
489        public void setNamespaceContext(DefaultNamespaceContext namespaceContext) {
490            this.namespaceContext = namespaceContext;
491        }
492    
493        public XPathFunctionResolver getFunctionResolver() {
494            return functionResolver;
495        }
496    
497        public void setFunctionResolver(XPathFunctionResolver functionResolver) {
498            this.functionResolver = functionResolver;
499        }
500    
501        public void setNamespaces(Map<String, String> namespaces) {
502            this.namespaces.clear();
503            this.namespaces.putAll(namespaces);
504        }
505    
506        /**
507         * Gets the {@link XPathFunction} for getting the input message body.
508         * <p/>
509         * A default function will be assigned (if no custom assigned) when either starting this builder
510         * or on first evaluation.
511         *
512         * @return the function, or <tt>null</tt> if this builder has not been started/used before.
513         */
514        public XPathFunction getBodyFunction() {
515            return bodyFunction;
516        }
517    
518        private XPathFunction createBodyFunction() {
519            return new XPathFunction() {
520                @SuppressWarnings("rawtypes")
521                public Object evaluate(List list) throws XPathFunctionException {
522                    if (exchange == null) {
523                        return null;
524                    }
525                    return exchange.get().getIn().getBody();
526                }
527            };
528        }
529    
530        public void setBodyFunction(XPathFunction bodyFunction) {
531            this.bodyFunction = bodyFunction;
532        }
533    
534        /**
535         * Gets the {@link XPathFunction} for getting the input message header.
536         * <p/>
537         * A default function will be assigned (if no custom assigned) when either starting this builder
538         * or on first evaluation.
539         *
540         * @return the function, or <tt>null</tt> if this builder has not been started/used before.
541         */
542        public XPathFunction getHeaderFunction() {
543            return headerFunction;
544        }
545    
546        private XPathFunction createHeaderFunction() {
547            return new XPathFunction() {
548                @SuppressWarnings("rawtypes")
549                public Object evaluate(List list) throws XPathFunctionException {
550                    if (exchange != null && !list.isEmpty()) {
551                        Object value = list.get(0);
552                        if (value != null) {
553                            String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
554                            return exchange.get().getIn().getHeader(text);
555                        }
556                    }
557                    return null;
558                }
559            };
560        }
561    
562        public void setHeaderFunction(XPathFunction headerFunction) {
563            this.headerFunction = headerFunction;
564        }
565    
566        /**
567         * Gets the {@link XPathFunction} for getting the output message body.
568         * <p/>
569         * A default function will be assigned (if no custom assigned) when either starting this builder
570         * or on first evaluation.
571         *
572         * @return the function, or <tt>null</tt> if this builder has not been started/used before.
573         */
574        public XPathFunction getOutBodyFunction() {
575            return outBodyFunction;
576        }
577    
578        private XPathFunction createOutBodyFunction() {
579            return new XPathFunction() {
580                @SuppressWarnings("rawtypes")
581                public Object evaluate(List list) throws XPathFunctionException {
582                    if (exchange.get() != null && exchange.get().hasOut()) {
583                        return exchange.get().getOut().getBody();
584                    }
585                    return null;
586                }
587            };
588        }
589    
590        public void setOutBodyFunction(XPathFunction outBodyFunction) {
591            this.outBodyFunction = outBodyFunction;
592        }
593    
594        /**
595         * Gets the {@link XPathFunction} for getting the output message header.
596         * <p/>
597         * A default function will be assigned (if no custom assigned) when either starting this builder
598         * or on first evaluation.
599         *
600         * @return the function, or <tt>null</tt> if this builder has not been started/used before.
601         */
602        public XPathFunction getOutHeaderFunction() {
603            return outHeaderFunction;
604        }
605    
606        private XPathFunction createOutHeaderFunction() {
607            return new XPathFunction() {
608                @SuppressWarnings("rawtypes")
609                public Object evaluate(List list) throws XPathFunctionException {
610                    if (exchange.get() != null && !list.isEmpty()) {
611                        Object value = list.get(0);
612                        if (value != null) {
613                            String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
614                            return exchange.get().getOut().getHeader(text);
615                        }
616                    }
617                    return null;
618                }
619            };
620        }
621    
622        public void setOutHeaderFunction(XPathFunction outHeaderFunction) {
623            this.outHeaderFunction = outHeaderFunction;
624        }
625    
626        /**
627         * Gets the {@link XPathFunction} for getting the exchange properties.
628         * <p/>
629         * A default function will be assigned (if no custom assigned) when either starting this builder
630         * or on first evaluation.
631         *
632         * @return the function, or <tt>null</tt> if this builder has not been started/used before.
633         */
634        public XPathFunction getPropertiesFunction() {
635            return propertiesFunction;
636        }
637    
638        private XPathFunction createPropertiesFunction() {
639            return new XPathFunction() {
640                @SuppressWarnings("rawtypes")
641                public Object evaluate(List list) throws XPathFunctionException {
642                    if (exchange != null && !list.isEmpty()) {
643                        Object value = list.get(0);
644                        if (value != null) {
645                            String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
646                            try {
647                                // use the property placeholder resolver to lookup the property for us
648                                Object answer = exchange.get().getContext().resolvePropertyPlaceholders("{{" + text + "}}");
649                                return answer;
650                            } catch (Exception e) {
651                                throw new XPathFunctionException(e);
652                            }
653                        }
654                    }
655                    return null;
656                }
657            };
658        }
659    
660        public void setPropertiesFunction(XPathFunction propertiesFunction) {
661            this.propertiesFunction = propertiesFunction;
662        }
663    
664        /**
665         * Gets the {@link XPathFunction} for executing <a href="http://camel.apache.org/simple">simple</a>
666         * language as xpath function.
667         * <p/>
668         * A default function will be assigned (if no custom assigned) when either starting this builder
669         * or on first evaluation.
670         *
671         * @return the function, or <tt>null</tt> if this builder has not been started/used before.
672         */
673        public XPathFunction getSimpleFunction() {
674            return simpleFunction;
675        }
676    
677        private XPathFunction createSimpleFunction() {
678            return new XPathFunction() {
679                @SuppressWarnings("rawtypes")
680                public Object evaluate(List list) throws XPathFunctionException {
681                    if (exchange != null && !list.isEmpty()) {
682                        Object value = list.get(0);
683                        if (value != null) {
684                            String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
685                            Language simple = exchange.get().getContext().resolveLanguage("simple");
686                            Expression exp = simple.createExpression(text);
687                            Object answer = exp.evaluate(exchange.get(), Object.class);
688                            return answer;
689                        }
690                    }
691                    return null;
692                }
693            };
694        }
695    
696        public void setSimpleFunction(XPathFunction simpleFunction) {
697            this.simpleFunction = simpleFunction;
698        }
699    
700        public Class<?> getResultType() {
701            return resultType;
702        }
703    
704        public void setResultType(Class<?> resultType) {
705            this.resultType = resultType;
706            if (Number.class.isAssignableFrom(resultType)) {
707                numberResult();
708            } else if (String.class.isAssignableFrom(resultType)) {
709                stringResult();
710            } else if (Boolean.class.isAssignableFrom(resultType)) {
711                booleanResult();
712            } else if (Node.class.isAssignableFrom(resultType)) {
713                nodeResult();
714            } else if (NodeList.class.isAssignableFrom(resultType)) {
715                nodeSetResult();
716            }
717        }
718    
719        public void setLogNamespaces(boolean logNamespaces) {
720            this.logNamespaces = logNamespaces;
721        }
722    
723        public boolean isLogNamespaces() {
724            return logNamespaces;
725        }
726    
727        public String getObjectModelUri() {
728            return objectModelUri;
729        }
730    
731        /**
732         * Enables Saxon on this particular XPath expression, as {@link #saxon()} sets the default static XPathFactory which may have already been initialised
733         * by previous XPath expressions
734         */
735        public void enableSaxon() {
736            this.setObjectModelUri(SAXON_OBJECT_MODEL_URI);
737        }
738    
739        public void setObjectModelUri(String objectModelUri) {
740            this.objectModelUri = objectModelUri;
741        }
742    
743        // Implementation methods
744        // -------------------------------------------------------------------------
745    
746        protected Object evaluate(Exchange exchange) {
747            Object answer = evaluateAs(exchange, resultQName);
748            if (resultType != null) {
749                return ExchangeHelper.convertToType(exchange, resultType, answer);
750            }
751            return answer;
752        }
753    
754        /**
755         * Evaluates the expression as the given result type
756         */
757        protected Object evaluateAs(Exchange exchange, QName resultQName) {
758            // pool a pre compiled expression from pool
759            XPathExpression xpathExpression = pool.poll();
760            if (xpathExpression == null) {
761                LOG.trace("Creating new XPathExpression as none was available from pool");
762                // no avail in pool then create one
763                try {
764                    xpathExpression = createXPathExpression();
765                } catch (XPathExpressionException e) {
766                    throw new InvalidXPathExpression(getText(), e);
767                } catch (Exception e) {
768                    throw new RuntimeExpressionException("Cannot create xpath expression", e);
769                }
770            } else {
771                LOG.trace("Acquired XPathExpression from pool");
772            }
773            try {
774                if (logNamespaces && LOG.isInfoEnabled()) {
775                    logNamespaces(exchange);
776                }
777                return doInEvaluateAs(xpathExpression, exchange, resultQName);
778            } finally {
779                // release it back to the pool
780                pool.add(xpathExpression);
781                LOG.trace("Released XPathExpression back to pool");
782            }
783        }
784    
785        private void logNamespaces(Exchange exchange) {
786            InputStream is = null;
787            NodeList answer = null;
788            XPathExpression xpathExpression = null;
789    
790            try {
791                xpathExpression = poolLogNamespaces.poll();
792                if (xpathExpression == null) {
793                    xpathExpression = createTraceNamespaceExpression();
794                }
795    
796                // prepare the input
797                Object document;
798                if (isInputStreamNeeded(exchange)) {
799                    is = exchange.getIn().getBody(InputStream.class);
800                    document = getDocument(exchange, is);
801                } else {
802                    Object body = exchange.getIn().getBody();
803                    document = getDocument(exchange, body);
804                }
805                // fetch all namespaces
806                if (document instanceof InputSource) {
807                    InputSource inputSource = (InputSource) document;
808                    answer = (NodeList) xpathExpression.evaluate(inputSource, XPathConstants.NODESET);
809                } else if (document instanceof DOMSource) {
810                    DOMSource source = (DOMSource) document;
811                    answer = (NodeList) xpathExpression.evaluate(source.getNode(), XPathConstants.NODESET);
812                } else {
813                    answer = (NodeList) xpathExpression.evaluate(document, XPathConstants.NODESET);
814                }
815            } catch (Exception e) {
816                LOG.warn("Unable to trace discovered namespaces in XPath expression", e);
817            } finally {
818                // IOHelper can handle if is is null
819                IOHelper.close(is);
820                poolLogNamespaces.add(xpathExpression);
821            }
822    
823            if (answer != null) {
824                logDiscoveredNamespaces(answer);
825            }
826        }
827    
828        private void logDiscoveredNamespaces(NodeList namespaces) {
829            Map<String, HashSet<String>> map = new LinkedHashMap<String, HashSet<String>>();
830            for (int i = 0; i < namespaces.getLength(); i++) {
831                Node n = namespaces.item(i);
832                if (n.getNodeName().equals("xmlns:xml")) {
833                    // skip the implicit XML namespace as it provides no value
834                    continue;
835                }
836    
837                String prefix = namespaces.item(i).getNodeName();
838                if (prefix.equals("xmlns")) {
839                    prefix = "DEFAULT";
840                }
841    
842                // add to map
843                if (!map.containsKey(prefix)) {
844                    map.put(prefix, new HashSet<String>());
845                }
846                map.get(prefix).add(namespaces.item(i).getNodeValue());
847            }
848    
849            LOG.info("Namespaces discovered in message: {}.", map);
850        }
851    
852        protected Object doInEvaluateAs(XPathExpression xpathExpression, Exchange exchange, QName resultQName) {
853            LOG.trace("Evaluating exchange: {} as: {}", exchange, resultQName);
854    
855            Object answer;
856    
857            // set exchange and variable resolver as thread locals for concurrency
858            this.exchange.set(exchange);
859    
860            // the underlying input stream, which we need to close to avoid locking files or other resources
861            InputStream is = null;
862            try {
863                Object document;
864               
865                // Check if we need to apply the XPath expression to a header
866                if (ObjectHelper.isNotEmpty(getHeaderName())) {         
867                    String headerName = getHeaderName();
868                    // only convert to input stream if really needed
869                    if (isInputStreamNeeded(exchange, headerName)) {
870                        is = exchange.getIn().getHeader(headerName, InputStream.class);
871                        document = getDocument(exchange, is);
872                    } else {
873                        Object headerObject = exchange.getIn().getHeader(getHeaderName());
874                        document = getDocument(exchange, headerObject);
875                    }
876                } else {
877                    // only convert to input stream if really needed
878                    if (isInputStreamNeeded(exchange)) {
879                        is = exchange.getIn().getBody(InputStream.class);
880                        document = getDocument(exchange, is);
881                    } else {
882                        Object body = exchange.getIn().getBody();
883                        document = getDocument(exchange, body);
884                    }
885                }
886                        
887                if (resultQName != null) {
888                    if (document instanceof InputSource) {
889                        InputSource inputSource = (InputSource) document;
890                        answer = xpathExpression.evaluate(inputSource, resultQName);
891                    } else if (document instanceof DOMSource) {
892                        DOMSource source = (DOMSource) document;
893                        answer = xpathExpression.evaluate(source.getNode(), resultQName);
894                    } else {
895                        answer = xpathExpression.evaluate(document, resultQName);
896                    }
897                } else {
898                    if (document instanceof InputSource) {
899                        InputSource inputSource = (InputSource) document;
900                        answer = xpathExpression.evaluate(inputSource);
901                    } else if (document instanceof DOMSource) {
902                        DOMSource source = (DOMSource) document;
903                        answer = xpathExpression.evaluate(source.getNode());
904                    } else {
905                        answer = xpathExpression.evaluate(document);
906                    }
907                }
908            } catch (XPathExpressionException e) {
909                String message = getText();
910                if (ObjectHelper.isNotEmpty(getHeaderName())) {
911                    message = message + " with headerName " + getHeaderName();
912                }
913                throw new InvalidXPathExpression(message, e);
914            } finally {
915                // IOHelper can handle if is is null
916                IOHelper.close(is);
917            }
918    
919            if (LOG.isTraceEnabled()) {
920                LOG.trace("Done evaluating exchange: {} as: {} with result: {}", new Object[]{exchange, resultQName, answer});
921            }
922            return answer;
923        }
924    
925        /**
926         * Creates a new xpath expression as there we no available in the pool.
927         * <p/>
928         * This implementation must be synchronized to ensure thread safety, as this XPathBuilder instance may not have been
929         * started prior to being used.
930         */
931        protected synchronized XPathExpression createXPathExpression() throws XPathExpressionException, XPathFactoryConfigurationException {
932            // ensure we are started
933            try {
934                start();
935            } catch (Exception e) {
936                throw new RuntimeExpressionException("Error starting XPathBuilder", e);
937            }
938    
939            // XPathFactory is not thread safe
940            XPath xPath = getXPathFactory().newXPath();
941    
942            if (!logNamespaces && LOG.isTraceEnabled()) {
943                LOG.trace("Creating new XPath expression in pool. Namespaces on XPath expression: {}", getNamespaceContext().toString());
944            } else if (logNamespaces && LOG.isInfoEnabled()) {
945                LOG.info("Creating new XPath expression in pool. Namespaces on XPath expression: {}", getNamespaceContext().toString());
946            }
947            xPath.setNamespaceContext(getNamespaceContext());
948            xPath.setXPathVariableResolver(getVariableResolver());
949    
950            XPathFunctionResolver parentResolver = getFunctionResolver();
951            if (parentResolver == null) {
952                parentResolver = xPath.getXPathFunctionResolver();
953            }
954            xPath.setXPathFunctionResolver(createDefaultFunctionResolver(parentResolver));
955            return xPath.compile(text);
956        }
957    
958        protected synchronized XPathExpression createTraceNamespaceExpression() throws XPathFactoryConfigurationException, XPathExpressionException {
959            // XPathFactory is not thread safe
960            XPath xPath = getXPathFactory().newXPath();
961            return xPath.compile(OBTAIN_ALL_NS_XPATH);
962        }
963    
964        protected DefaultNamespaceContext createNamespaceContext(XPathFactory factory) {
965            DefaultNamespaceContext context = new DefaultNamespaceContext(factory);
966            populateDefaultNamespaces(context);
967            return context;
968        }
969    
970        /**
971         * Populate a number of standard prefixes if they are not already there
972         */
973        protected void populateDefaultNamespaces(DefaultNamespaceContext context) {
974            setNamespaceIfNotPresent(context, "in", IN_NAMESPACE);
975            setNamespaceIfNotPresent(context, "out", OUT_NAMESPACE);
976            setNamespaceIfNotPresent(context, "env", Namespaces.ENVIRONMENT_VARIABLES);
977            setNamespaceIfNotPresent(context, "system", Namespaces.SYSTEM_PROPERTIES_NAMESPACE);
978            setNamespaceIfNotPresent(context, "function", Namespaces.FUNCTION_NAMESPACE);
979        }
980    
981        protected void setNamespaceIfNotPresent(DefaultNamespaceContext context, String prefix, String uri) {
982            if (context != null) {
983                String current = context.getNamespaceURI(prefix);
984                if (current == null) {
985                    context.add(prefix, uri);
986                }
987            }
988        }
989    
990        protected XPathFunctionResolver createDefaultFunctionResolver(final XPathFunctionResolver parent) {
991            return new XPathFunctionResolver() {
992                public XPathFunction resolveFunction(QName qName, int argumentCount) {
993                    XPathFunction answer = null;
994                    if (parent != null) {
995                        answer = parent.resolveFunction(qName, argumentCount);
996                    }
997                    if (answer == null) {
998                        if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), IN_NAMESPACE)
999                            || isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), DEFAULT_NAMESPACE)) {
1000                            String localPart = qName.getLocalPart();
1001                            if (localPart.equals("body") && argumentCount == 0) {
1002                                return getBodyFunction();
1003                            }
1004                            if (localPart.equals("header") && argumentCount == 1) {
1005                                return getHeaderFunction();
1006                            }
1007                        }
1008                        if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), OUT_NAMESPACE)) {
1009                            String localPart = qName.getLocalPart();
1010                            if (localPart.equals("body") && argumentCount == 0) {
1011                                return getOutBodyFunction();
1012                            }
1013                            if (localPart.equals("header") && argumentCount == 1) {
1014                                return getOutHeaderFunction();
1015                            }
1016                        }
1017                        if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), FUNCTION_NAMESPACE)) {
1018                            String localPart = qName.getLocalPart();
1019                            if (localPart.equals("properties") && argumentCount == 1) {
1020                                return getPropertiesFunction();
1021                            }
1022                            if (localPart.equals("simple") && argumentCount == 1) {
1023                                return getSimpleFunction();
1024                            }
1025                        }
1026                    }
1027                    return answer;
1028                }
1029            };
1030        }
1031    
1032        /**
1033         * Checks whether we need an {@link InputStream} to access the message body.
1034         * <p/>
1035         * Depending on the content in the message body, we may not need to convert
1036         * to {@link InputStream}.
1037         *
1038         * @param exchange the current exchange
1039         * @return <tt>true</tt> to convert to {@link InputStream} beforehand converting afterwards.
1040         */
1041        protected boolean isInputStreamNeeded(Exchange exchange) {
1042            Object body = exchange.getIn().getBody();
1043            return isInputStreamNeededForObject(exchange, body);
1044        }
1045        
1046        /**
1047         * Checks whether we need an {@link InputStream} to access the message header.
1048         * <p/>
1049         * Depending on the content in the message header, we may not need to convert
1050         * to {@link InputStream}.
1051         *
1052         * @param exchange the current exchange
1053         * @return <tt>true</tt> to convert to {@link InputStream} beforehand converting afterwards.
1054         */
1055        protected boolean isInputStreamNeeded(Exchange exchange, String headerName) {
1056            Object header = exchange.getIn().getHeader(headerName);
1057            return isInputStreamNeededForObject(exchange, header);
1058        }
1059    
1060        /**
1061         * Checks whether we need an {@link InputStream} to access this object
1062         * <p/>
1063         * Depending on the content in the object, we may not need to convert
1064         * to {@link InputStream}.
1065         *
1066         * @param exchange the current exchange
1067         * @return <tt>true</tt> to convert to {@link InputStream} beforehand converting afterwards.
1068         */
1069        protected boolean isInputStreamNeededForObject(Exchange exchange, Object obj) {
1070            if (obj == null) {
1071                return false;
1072            }
1073    
1074            if (obj instanceof WrappedFile) {
1075                obj = ((WrappedFile<?>) obj).getFile();
1076            }
1077            if (obj instanceof File) {
1078                // input stream is needed for File to avoid locking the file in case of errors etc
1079                return true;
1080            }
1081    
1082            // input stream is not needed otherwise
1083            return false;
1084        }
1085        
1086        /**
1087         * Strategy method to extract the document from the exchange.
1088         */
1089        protected Object getDocument(Exchange exchange, Object body) {
1090            try {
1091                return doGetDocument(exchange, body);
1092            } catch (Exception e) {
1093                throw ObjectHelper.wrapRuntimeCamelException(e);
1094            } finally {
1095                // call the reset if the in message body is StreamCache
1096                MessageHelper.resetStreamCache(exchange.getIn());
1097            }
1098        }
1099    
1100        protected Object doGetDocument(Exchange exchange, Object body) throws Exception {
1101            if (body == null) {
1102                return null;
1103            }
1104    
1105            Object answer = null;
1106    
1107            Class<?> type = getDocumentType();
1108            Exception cause = null;
1109            if (type != null) {
1110                // try to get the body as the desired type
1111                try {
1112                    answer = exchange.getContext().getTypeConverter().convertTo(type, exchange, body);
1113                } catch (Exception e) {
1114                    // we want to store the caused exception, if we could not convert
1115                    cause = e;
1116                }
1117            }
1118    
1119            // okay we can try to remedy the failed conversion by some special types
1120            if (answer == null) {
1121                // let's try coercing some common types into something JAXP work with the best for special types
1122                if (body instanceof WrappedFile) {
1123                    // special for files so we can work with them out of the box
1124                    InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, exchange, body);
1125                    answer = new InputSource(is);
1126                } else if (body instanceof BeanInvocation) {
1127                    // if its a null bean invocation then handle that specially
1128                    BeanInvocation bi = exchange.getContext().getTypeConverter().convertTo(BeanInvocation.class, exchange, body);
1129                    if (bi.getArgs() != null && bi.getArgs().length == 1 && bi.getArgs()[0] == null) {
1130                        // its a null argument from the bean invocation so use null as answer
1131                        answer = null;
1132                    }
1133                } else if (body instanceof String) {
1134                    answer = new InputSource(new StringReader((String) body));
1135                }
1136            }
1137    
1138            if (type == null && answer == null) {
1139                // fallback to get the body as is
1140                answer = body;
1141            } else if (answer == null) {
1142                // there was a type, and we could not convert to it, then fail
1143                if (cause != null) {
1144                    throw cause;
1145                } else {
1146                    throw new NoTypeConversionAvailableException(body, type);
1147                }
1148            }
1149    
1150            return answer;
1151        }
1152    
1153        private MessageVariableResolver getVariableResolver() {
1154            return variableResolver;
1155        }
1156    
1157        @Override
1158        public void doStart() throws Exception {
1159            if (xpathFactory == null) {
1160                xpathFactory = createXPathFactory();
1161            }
1162            if (namespaceContext == null) {
1163                namespaceContext = createNamespaceContext(xpathFactory);
1164            }
1165            for (Map.Entry<String, String> entry : namespaces.entrySet()) {
1166                namespaceContext.add(entry.getKey(), entry.getValue());
1167            }
1168    
1169            // create default functions if no custom assigned
1170            if (bodyFunction == null) {
1171                bodyFunction = createBodyFunction();
1172            }
1173            if (headerFunction == null) {
1174                headerFunction = createHeaderFunction();
1175            }
1176            if (outBodyFunction == null) {
1177                outBodyFunction = createOutBodyFunction();
1178            }
1179            if (outHeaderFunction == null) {
1180                outHeaderFunction = createOutHeaderFunction();
1181            }
1182            if (propertiesFunction == null) {
1183                propertiesFunction = createPropertiesFunction();
1184            }
1185            if (simpleFunction == null) {
1186                simpleFunction = createSimpleFunction();
1187            }
1188        }
1189    
1190        @Override
1191        public void doStop() throws Exception {
1192            pool.clear();
1193            poolLogNamespaces.clear();
1194        }
1195    
1196        protected synchronized XPathFactory createXPathFactory() throws XPathFactoryConfigurationException {
1197            if (objectModelUri != null) {
1198                xpathFactory = XPathFactory.newInstance(objectModelUri);
1199                LOG.info("Using objectModelUri " + objectModelUri + " when created XPathFactory {}", xpathFactory);
1200                return xpathFactory;
1201            }
1202    
1203            if (defaultXPathFactory == null) {
1204                initDefaultXPathFactory();
1205            }
1206            return defaultXPathFactory;
1207        }
1208    
1209        protected void initDefaultXPathFactory() throws XPathFactoryConfigurationException {
1210            if (defaultXPathFactory == null) {
1211                if (objectModelUri != null) {
1212                    defaultXPathFactory = XPathFactory.newInstance(objectModelUri);
1213                    LOG.info("Using objectModelUri " + objectModelUri + " when created default XPathFactory {}", defaultXPathFactory);
1214                }
1215    
1216                if (defaultXPathFactory == null) {
1217                    // read system property and see if there is a factory set
1218                    Properties properties = System.getProperties();
1219                    for (Map.Entry<Object, Object> prop : properties.entrySet()) {
1220                        String key = (String) prop.getKey();
1221                        if (key.startsWith(XPathFactory.DEFAULT_PROPERTY_NAME)) {
1222                            String uri = ObjectHelper.after(key, ":");
1223                            if (uri != null) {
1224                                defaultXPathFactory = XPathFactory.newInstance(uri);
1225                                LOG.info("Using system property {} with value {} when created default XPathFactory {}", new Object[]{key, uri, defaultXPathFactory});
1226                            }
1227                        }
1228                    }
1229                }
1230    
1231                defaultXPathFactory = XPathFactory.newInstance();
1232                LOG.info("Created default XPathFactory {}", defaultXPathFactory);
1233            }
1234        }
1235    
1236    }