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