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