Chapter 95. XPath
Chapter 95. XPath Camel supports XPath to allow an Expression or Predicate to be used in the DSL . For example, you could use XPath to create a predicate in a Message Filter or as an expression for a Recipient List . 95.1. XPath Language options The XPath language supports 10 options, which are listed below. Name Default Java Type Description documentType String Name of class for document type The default value is org.w3c.dom.Document. resultType Enum Sets the class name of the result type (type from output) The default result type is NodeSet. Enum values: NUMBER STRING BOOLEAN NODESET NODE saxon Boolean Whether to use Saxon. factoryRef String References to a custom XPathFactory to lookup in the registry. objectModel String The XPath object model to use. logNamespaces Boolean Whether to log namespaces which can assist during troubleshooting. headerName String Name of header to use as input, instead of the message body. threadSafety Boolean Whether to enable thread-safety for the returned result of the xpath expression. This applies to when using NODESET as the result type, and the returned set has multiple elements. In this situation there can be thread-safety issues if you process the NODESET concurrently such as from a Camel Splitter EIP in parallel processing mode. This option prevents concurrency issues by doing defensive copies of the nodes. It is recommended to turn this option on if you are using camel-saxon or Saxon in your application. Saxon has thread-safety issues which can be prevented by turning this option on. preCompile Boolean Whether to enable pre-compiling the xpath expression during initialization phase. pre-compile is enabled by default. This can be used to turn off, for example in cases the compilation phase is desired at the starting phase, such as if the application is ahead of time compiled (for example with camel-quarkus) which would then load the xpath factory of the built operating system, and not a JVM runtime. trim Boolean Whether to trim the value to remove leading and trailing whitespaces and line breaks. 95.2. Namespaces You can easily use namespaces with XPath expressions using the Namespaces helper class. 95.3. Variables Variables in XPath is defined in different namespaces. The default namespace is http://camel.apache.org/schema/spring . Namespace URI Local part Type Description http://camel.apache.org/xml/in/ in Message the message http://camel.apache.org/xml/out/ out Message deprecated the output message (do not use) http://camel.apache.org/xml/function/ functions Object Additional functions http://camel.apache.org/xml/variables/environment-variables env Object OS environment variables http://camel.apache.org/xml/variables/system-properties system Object Java System properties http://camel.apache.org/xml/variables/exchange-property Object the exchange property Camel will resolve variables according to either: namespace given no namespace given 95.3.1. Namespace given If the namespace is given then Camel is instructed exactly what to return. However, when resolving Camel will try to resolve a header with the given local part first, and return it. If the local part has the value body then the body is returned instead. 95.3.2. No namespace given If there is no namespace given then Camel resolves only based on the local part. Camel will try to resolve a variable in the following steps: from variables that has been set using the variable(name, value) fluent builder from message.in.header if there is a header with the given key from exchange.properties if there is a property with the given key 95.4. Functions Camel adds the following XPath functions that can be used to access the exchange: Function Argument Type Description in:body none Object Will return the message body. in:header the header name Object Will return the message header. out:body none Object deprecated Will return the out message body. out:header the header name Object deprecated Will return the out message header. function:properties key for property String To use a . function:simple simple expression Object To evaluate a language. Note function:properties and function:simple is not supported when the return type is a NodeSet , such as when using with a Split EIP. Here's an example showing some of these functions in use. 95.4.1. Functions example If you prefer to configure your routes in your Spring XML file then you can use XPath expressions as follows <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <camelContext id="camel" xmlns="http://activemq.apache.org/camel/schema/spring" xmlns:foo="http://example.com/person"> <route> <from uri="activemq:MyQueue"/> <filter> <xpath>/foo:person[@name='James']</xpath> <to uri="mqseries:SomeOtherQueue"/> </filter> </route> </camelContext> </beans> Notice how we can reuse the namespace prefixes, foo in this case, in the XPath expression for easier namespace based XPath expressions. 95.5. Stream based message bodies If the message body is stream based, which means the input it receives is submitted to Camel as a stream. That means you will only be able to read the content of the stream once . So often when you use XPath as Message Filter or Content Based Router then you need to access the data multiple times, and you should use Stream Caching or convert the message body to a String prior which is safe to be re-read multiple times. from("queue:foo"). filter().xpath("//foo")). to("queue:bar") from("queue:foo"). choice().xpath("//foo")).to("queue:bar"). otherwise().to("queue:others"); 95.6. Setting result type The XPath expression will return a result type using native XML objects such as org.w3c.dom.NodeList . However, many times you want a result type to be a String . To do this you have to instruct the XPath which result type to use. In Java DSL: xpath("/foo:person/@id", String.class) In XML DSL you use the resultType attribute to provide the fully qualified classname. <xpath resultType="java.lang.String">/foo:person/@id</xpath> Note Classes from java.lang can omit the FQN name, so you can use resultType="String" Using @XPath annotation: @XPath(value = "concat('foo-',//order/name/)", resultType = String.class) String name) Where we use the xpath function concat to prefix the order name with foo- . In this case we have to specify that we want a String as result type, so the concat function works. 95.7. Using XPath on Headers Some users may have XML stored in a header. To apply an XPath to a header's value you can do this by defining the 'headerName' attribute. <xpath headerName="invoiceDetails">/invoice/@orderType = 'premium'</xpath> And in Java DSL you specify the headerName as the 2nd parameter as shown: xpath("/invoice/@orderType = 'premium'", "invoiceDetails") 95.8. Example Here is a simple example using an XPath expression as a predicate in a Message Filter : from("direct:start") .filter().xpath("/person[@name='James']") .to("mock:result"); And in XML <route> <from uri="direct:start"/> <filter> <xpath>/person[@name='James']</xpath> <to uri="mock:result"/> </filter> </route> 95.9. Using namespaces If you have a standard set of namespaces you wish to work with and wish to share them across many XPath expressions you can use the org.apache.camel.support.builder.Namespaces when using Java DSL as shown: Namespaces ns = new Namespaces("c", "http://acme.com/cheese"); from("direct:start") .filter(xpath("/c:person[@name='James']", ns)) .to("mock:result"); Notice how the namespaces are provided to xpath with the ns variable that are passed in as the 2nd parameter. Each namespace is a key=value pair, where the prefix is the key. In the XPath expression then the namespace is used by its prefix, eg: /c:person[@name='James'] The namespace builder supports adding multiple namespaces as shown: Namespaces ns = new Namespaces("c", "http://acme.com/cheese") .add("w", "http://acme.com/wine") .add("b", "http://acme.com/beer"); When using namespaces in XML DSL then its different, as you setup the namespaces in the XML root tag (or one of the camelContext , routes , route tags). In the XML example below we use Spring XML where the namespace is declared in the root tag beans , in the line with xmlns:foo="http://example.com/person" : <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:foo="http://example.com/person" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd "> <camelContext xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="direct:start"/> <filter> <xpath logNamespaces="true">/foo:person[@name='James']</xpath> <to uri="mock:result"/> </filter> </route> </camelContext> </beans> This namespace uses foo as prefix, so the <xpath> expression uses /foo: to use this namespace. 95.10. Using @XPath Annotation for Bean Integration You can use Bean Integration to invoke a method on a bean and use various languages such as @XPath to extract a value from the message and bind it to a method parameter. Note The default @XPath annotation has SOAP and XML namespaces available. public class Foo { @Consume(uri = "activemq:my.queue") public void doSomething(@XPath("/person/@name") String name, String xml) { // process the inbound message here } } 95.11. Using XPathBuilder without an Exchange You can now use the org.apache.camel.language.xpath.XPathBuilder without the need for an Exchange . This comes handy if you want to use it as a helper to do custom XPath evaluations. It requires that you pass in a CamelContext since a lot of the moving parts inside the XPathBuilder requires access to the Camel Type Converter and hence why CamelContext is needed. For example, you can do something like this: boolean matches = XPathBuilder.xpath("/foo/bar/@xyz").matches(context, "<foo><bar xyz='cheese'/></foo>")); This will match the given predicate. You can also evaluate as shown in the following three examples: String name = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>cheese</bar></foo>", String.class); Integer number = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>123</bar></foo>", Integer.class); Boolean bool = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>true</bar></foo>", Boolean.class); Evaluating with a String result is a common requirement and make this simpler: String name = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>cheese</bar></foo>"); 95.12. Using Saxon with XPathBuilder You need to add camel-saxon as dependency to your project. It's now easier to use Saxon with the XPathBuilder which can be done in several ways as shown below Using a custom XPathFactory Using ObjectModel 95.12.1. Setting a custom XPathFactory using System Property Camel now supports reading the JVM system property javax.xml.xpath.XPathFactory that can be used to set a custom XPathFactory to use. This unit test shows how this can be done to use Saxon instead: Camel will log at INFO level if it uses a non default XPathFactory such as: XPathBuilder INFO Using system property javax.xml.xpath.XPathFactory:http://saxon.sf.net/jaxp/xpath/om with value: net.sf.saxon.xpath.XPathFactoryImpl when creating XPathFactory To use Apache Xerces you can configure the system property -Djavax.xml.xpath.XPathFactory=org.apache.xpath.jaxp.XPathFactoryImpl 95.12.2. Enabling Saxon from XML DSL Similarly to Java DSL, to enable Saxon from XML DSL you have three options: Referring to a custom factory: <xpath factoryRef="saxonFactory" resultType="java.lang.String">current-dateTime()</xpath> And declare a bean with the factory: <bean id="saxonFactory" class="net.sf.saxon.xpath.XPathFactoryImpl"/> Specifying the object model: <xpath objectModel="http://saxon.sf.net/jaxp/xpath/om" resultType="java.lang.String">current-dateTime()</xpath> And the recommended approach is to set saxon=true as shown: <xpath saxon="true" resultType="java.lang.String">current-dateTime()</xpath> 95.13. Namespace auditing to aid debugging Many XPath-related issues that users frequently face are linked to the usage of namespaces. You may have some misalignment between the namespaces present in your message, and those that your XPath expression is aware of or referencing. XPath predicates or expressions that are unable to locate the XML elements and attributes due to namespaces issues may simply look like they are not working , when in reality all there is to it is a lack of namespace definition. Namespaces in XML are completely necessary, and while we would love to simplify their usage by implementing some magic or voodoo to wire namespaces automatically, truth is that any action down this path would disagree with the standards and would greatly hinder interoperability. Therefore, the utmost we can do is assist you in debugging such issues by adding two new features to the XPath Expression Language and are thus accessible from both predicates and expressions. 95.13.1. Logging the Namespace Context of your XPath expression/predicate Every time a new XPath expression is created in the internal pool, Camel will log the namespace context of the expression under the org.apache.camel.language.xpath.XPathBuilder logger. Since Camel represents Namespace Contexts in a hierarchical fashion (parent-child relationships), the entire tree is output in a recursive manner with the following format: Any of these options can be used to activate this logging: Enable TRACE logging on the org.apache.camel.language.xpath.XPathBuilder logger, or some parent logger such as org.apache.camel or the root logger Enable the logNamespaces option as indicated in the following section, in which case the logging will occur on the INFO level 95.13.2. Auditing namespaces Camel is able to discover and dump all namespaces present on every incoming message before evaluating an XPath expression, providing all the richness of information you need to help you analyse and pinpoint possible namespace issues. To achieve this, it in turn internally uses another specially tailored XPath expression to extract all namespace mappings that appear in the message, displaying the prefix and the full namespace URI(s) for each individual mapping. Some points to take into account: The implicit XML namespace ( xmlns:xml="http://www.w3.org/XML/1998/namespace" ) is suppressed from the output because it adds no value Default namespaces are listed under the DEFAULT keyword in the output Keep in mind that namespaces can be remapped under different scopes. Think of a top-level 'a' prefix which in inner elements can be assigned a different namespace, or the default namespace changing in inner scopes. For each discovered prefix, all associated URIs are listed. You can enable this option in Java DSL and XML DSL: Java DSL: XPathBuilder.xpath("/foo:person/@id", String.class).logNamespaces() XML DSL: <xpath logNamespaces="true" resultType="String">/foo:person/@id</xpath> The result of the auditing will be appeared at the INFO level under the org.apache.camel.language.xpath.XPathBuilder logger and will look like the following: 2012-01-16 13:23:45,878 [stSaxonWithFlag] INFO XPathBuilder - Namespaces discovered in message: {xmlns:a=[http://apache.org/camel], DEFAULT=[http://apache.org/default], xmlns:b=[http://apache.org/camelA, http://apache.org/camelB]} 95.14. Loading script from external resource You can externalize the script and have Camel load it from a resource such as "classpath:" , "file:" , or "http:" . This is done using the following syntax: "resource:scheme:location" , eg to refer to a file on the classpath you can do: .setHeader("myHeader").xpath("resource:classpath:myxpath.txt", String.class) 95.15. Dependencies To use XPath in your camel routes you need to add the dependency on camel-xpath which implements the XPath language. If you use maven you could just add the following to your pom.xml, substituting the version number for the latest & greatest release (see the download page for the latest versions). <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-xpath</artifactId> <version>{CamelSBProjectVersion}</version> </dependency> 95.16. Spring Boot Auto-Configuration When using xpath with Spring Boot make sure to use the following Maven dependency to have support for auto configuration: <dependency> <groupId>org.apache.camel.springboot</groupId> <artifactId>camel-xpath-starter</artifactId> </dependency> The component supports 9 options, which are listed below. Name Description Default Type camel.language.xpath.document-type Name of class for document type The default value is org.w3c.dom.Document. String camel.language.xpath.enabled Whether to enable auto configuration of the xpath language. This is enabled by default. Boolean camel.language.xpath.factory-ref References to a custom XPathFactory to lookup in the registry. String camel.language.xpath.log-namespaces Whether to log namespaces which can assist during troubleshooting. false Boolean camel.language.xpath.object-model The XPath object model to use. String camel.language.xpath.pre-compile Whether to enable pre-compiling the xpath expression during initialization phase. pre-compile is enabled by default. This can be used to turn off, for example in cases the compilation phase is desired at the starting phase, such as if the application is ahead of time compiled (for example with camel-quarkus) which would then load the xpath factory of the built operating system, and not a JVM runtime. true Boolean camel.language.xpath.saxon Whether to use Saxon. false Boolean camel.language.xpath.thread-safety Whether to enable thread-safety for the returned result of the xpath expression. This applies to when using NODESET as the result type, and the returned set has multiple elements. In this situation there can be thread-safety issues if you process the NODESET concurrently such as from a Camel Splitter EIP in parallel processing mode. This option prevents concurrency issues by doing defensive copies of the nodes. It is recommended to turn this option on if you are using camel-saxon or Saxon in your application. Saxon has thread-safety issues which can be prevented by turning this option on. false Boolean camel.language.xpath.trim Whether to trim the value to remove leading and trailing whitespaces and line breaks. true Boolean
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <camelContext id="camel" xmlns="http://activemq.apache.org/camel/schema/spring" xmlns:foo="http://example.com/person"> <route> <from uri="activemq:MyQueue"/> <filter> <xpath>/foo:person[@name='James']</xpath> <to uri="mqseries:SomeOtherQueue"/> </filter> </route> </camelContext> </beans> from("queue:foo"). filter().xpath("//foo")). to("queue:bar") from("queue:foo"). choice().xpath("//foo")).to("queue:bar"). otherwise().to("queue:others"); xpath("/foo:person/@id", String.class) <xpath resultType="java.lang.String">/foo:person/@id</xpath> @XPath(value = "concat('foo-',//order/name/)", resultType = String.class) String name) <xpath headerName="invoiceDetails">/invoice/@orderType = 'premium'</xpath> xpath("/invoice/@orderType = 'premium'", "invoiceDetails") from("direct:start") .filter().xpath("/person[@name='James']") .to("mock:result"); <route> <from uri="direct:start"/> <filter> <xpath>/person[@name='James']</xpath> <to uri="mock:result"/> </filter> </route> Namespaces ns = new Namespaces("c", "http://acme.com/cheese"); from("direct:start") .filter(xpath("/c:person[@name='James']", ns)) .to("mock:result"); /c:person[@name='James'] Namespaces ns = new Namespaces("c", "http://acme.com/cheese") .add("w", "http://acme.com/wine") .add("b", "http://acme.com/beer"); <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:foo="http://example.com/person" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd "> <camelContext xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="direct:start"/> <filter> <xpath logNamespaces="true">/foo:person[@name='James']</xpath> <to uri="mock:result"/> </filter> </route> </camelContext> </beans> public class Foo { @Consume(uri = "activemq:my.queue") public void doSomething(@XPath("/person/@name") String name, String xml) { // process the inbound message here } } boolean matches = XPathBuilder.xpath("/foo/bar/@xyz").matches(context, "<foo><bar xyz='cheese'/></foo>")); String name = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>cheese</bar></foo>", String.class); Integer number = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>123</bar></foo>", Integer.class); Boolean bool = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>true</bar></foo>", Boolean.class); String name = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>cheese</bar></foo>"); XPathBuilder INFO Using system property javax.xml.xpath.XPathFactory:http://saxon.sf.net/jaxp/xpath/om with value: net.sf.saxon.xpath.XPathFactoryImpl when creating XPathFactory -Djavax.xml.xpath.XPathFactory=org.apache.xpath.jaxp.XPathFactoryImpl <xpath factoryRef="saxonFactory" resultType="java.lang.String">current-dateTime()</xpath> <bean id="saxonFactory" class="net.sf.saxon.xpath.XPathFactoryImpl"/> <xpath objectModel="http://saxon.sf.net/jaxp/xpath/om" resultType="java.lang.String">current-dateTime()</xpath> <xpath saxon="true" resultType="java.lang.String">current-dateTime()</xpath> [me: {prefix -> namespace}, {prefix -> namespace}], [parent: [me: {prefix -> namespace}, {prefix -> namespace}], [parent: [me: {prefix -> namespace}]]] XPathBuilder.xpath("/foo:person/@id", String.class).logNamespaces() <xpath logNamespaces="true" resultType="String">/foo:person/@id</xpath> 2012-01-16 13:23:45,878 [stSaxonWithFlag] INFO XPathBuilder - Namespaces discovered in message: {xmlns:a=[http://apache.org/camel], DEFAULT=[http://apache.org/default], xmlns:b=[http://apache.org/camelA, http://apache.org/camelB]} .setHeader("myHeader").xpath("resource:classpath:myxpath.txt", String.class) <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-xpath</artifactId> <version>{CamelSBProjectVersion}</version> </dependency> <dependency> <groupId>org.apache.camel.springboot</groupId> <artifactId>camel-xpath-starter</artifactId> </dependency>