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