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