| = XPath Language | |
| :doctitle: XPath | |
| :shortname: xpath | |
| :artifactid: camel-xpath | |
| :description: Evaluates an XPath expression against an XML payload | |
| :since: 1.1 | |
| :supportlevel: Stable | |
| :tabs-sync-option: | |
| *Since Camel {since}* | |
| Camel supports http://www.w3.org/TR/xpath[XPath] to allow an | |
| xref:manual::expression.adoc[Expression] or xref:manual::predicate.adoc[Predicate] to be | |
| used in the xref:manual::dsl.adoc[DSL]. | |
| For example, you could use XPath to create a | |
| predicate in a xref:eips:filter-eip.adoc[Message Filter] | |
| or as an expression for a xref:eips:recipientList-eip.adoc[Recipient List]. | |
| == XPath Language options | |
| // language options: START | |
| include::partial$language-options.adoc[] | |
| // language options: END | |
| == Namespaces | |
| You can use namespaces with XPath expressions using the `Namespaces` helper class. | |
| === Sharing namespaces via a registry bean | |
| When the same prefix to URI mappings are needed by several expressions, you can register a | |
| single `org.apache.camel.support.builder.Namespaces` bean and refer to it from each | |
| expression with the `namespacesRef` option, instead of repeating the mappings inline. | |
| This is intended for XML and YAML DSL, where a `Namespaces` instance cannot be passed | |
| programmatically. In Java DSL you keep passing the builder directly. | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| Namespaces ns = new Namespaces("c", "http://acme.com/cheese") | |
| .add("w", "http://acme.com/wine"); | |
| from("direct:start") | |
| .filter(xpath("/c:number = 55", ns)) | |
| .to("mock:result"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <camel xmlns="http://camel.apache.org/schema/xml-io"> | |
| <bean name="myNamespaces" type="org.apache.camel.support.builder.Namespaces"> | |
| <properties> | |
| <property key="namespaces[c]" value="http://acme.com/cheese"/> | |
| <property key="namespaces[w]" value="http://acme.com/wine"/> | |
| </properties> | |
| </bean> | |
| <route> | |
| <from uri="direct:start"/> | |
| <filter> | |
| <xpath namespacesRef="myNamespaces">/c:number = 55</xpath> | |
| <to uri="mock:result"/> | |
| </filter> | |
| </route> | |
| </camel> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - beans: | |
| - name: myNamespaces | |
| type: org.apache.camel.support.builder.Namespaces | |
| properties: | |
| namespaces[c]: "http://acme.com/cheese" | |
| namespaces[w]: "http://acme.com/wine" | |
| - route: | |
| from: | |
| uri: "direct:start" | |
| steps: | |
| - filter: | |
| expression: | |
| xpath: | |
| expression: "/c:number = 55" | |
| namespacesRef: "myNamespaces" | |
| steps: | |
| - to: "mock:result" | |
| ---- | |
| ==== | |
| Each prefix is configured as a `namespaces[prefix]` property on the bean, with the namespace | |
| URI as the value. | |
| If an expression declares namespaces inline as well, then the inline namespaces win and | |
| `namespacesRef` is ignored. | |
| The `namespacesRef` option is also available on the xref:xquery-language.adoc[XQuery] and | |
| xref:xtokenize-language.adoc[XML Tokenize] languages. | |
| == Variables | |
| Variables in XPath are defined in different namespaces. | |
| The default | |
| namespace is `\http://camel.apache.org/schema/spring`. | |
| [width="100%",cols="10%,10%,10%,70%",options="header",] | |
| |=== | |
| |Namespace URI |Local part |Type |Description | |
| |http://camel.apache.org/xml/in/[http://camel.apache.org/xml/in/] |in |Message |the message | |
| |http://camel.apache.org/xml/out/[http://camel.apache.org/xml/out/] |out |Message |*deprecated* the output message (do not use) | |
| |http://camel.apache.org/xml/function/[http://camel.apache.org/xml/function/] |functions |Object |Additional functions | |
| |http://camel.apache.org/xml/variables/environment-variables[http://camel.apache.org/xml/variables/environment-variables] |env |Object |OS environment variables | |
| |http://camel.apache.org/xml/variables/system-properties[http://camel.apache.org/xml/variables/system-properties] |system |Object |Java System properties | |
| |http://camel.apache.org/xml/variables/exchange-property[http://camel.apache.org/xml/variables/exchange-property] | | Object |the exchange property | |
| |=== | |
| Camel will resolve variables according to either: | |
| * namespace given | |
| * no namespace given | |
| === 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. | |
| === 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 | |
| * from `exchange.variables` if there is a variable with the given key | |
| ==== Example of using variables, header and body in XPath expression | |
| You can combine headers, properties, body and variables in an XPath expression as shown in the example below: | |
| The variables and the header are converted to a `Document` (`resultType`), so XPath can navigate them: | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| from("timer:java?period=1000&repeatCount=1") | |
| .setBody() | |
| .simple("<dummy>YES</dummy>") | |
| .setVariable("Foo", simple("<hello><id>123</id></hello>", Document.class)) | |
| .setVariable("Bar", simple("<holla><id>123</id></holla>", Document.class)) | |
| .setHeader("Baz", simple("<hallo><id>123</id></hallo>", Document.class)) | |
| .choice() | |
| .when(xpath("($Foo/hello/id = $Bar/holla/id) and /dummy='YES' and $Baz/hallo/id=123")) | |
| .log("EQUAL") | |
| .otherwise() | |
| .log("DIFFERENT") | |
| .end(); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <route> | |
| <from uri="timer:java?period=1000&repeatCount=1"/> | |
| <setBody> | |
| <simple><dummy>YES</dummy></simple> | |
| </setBody> | |
| <setVariable name="Foo"> | |
| <simple resultType="org.w3c.dom.Document"><hello><id>123</id></hello></simple> | |
| </setVariable> | |
| <setVariable name="Bar"> | |
| <simple resultType="org.w3c.dom.Document"><holla><id>123</id></holla></simple> | |
| </setVariable> | |
| <setHeader name="Baz"> | |
| <simple resultType="org.w3c.dom.Document"><hallo><id>123</id></hallo></simple> | |
| </setHeader> | |
| <choice> | |
| <when> | |
| <xpath>($Foo/hello/id = $Bar/holla/id) and /dummy='YES' and $Baz/hallo/id=123</xpath> | |
| <log message="EQUAL"/> | |
| </when> | |
| <otherwise> | |
| <log message="DIFFERENT"/> | |
| </otherwise> | |
| </choice> | |
| </route> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: timer:java | |
| parameters: | |
| period: 1000 | |
| repeatCount: 1 | |
| steps: | |
| - setBody: | |
| expression: | |
| simple: | |
| expression: "<dummy>YES</dummy>" | |
| - setVariable: | |
| name: Foo | |
| expression: | |
| simple: | |
| expression: "<hello><id>123</id></hello>" | |
| resultType: org.w3c.dom.Document | |
| - setVariable: | |
| name: Bar | |
| expression: | |
| simple: | |
| expression: "<holla><id>123</id></holla>" | |
| resultType: org.w3c.dom.Document | |
| - setHeader: | |
| name: Baz | |
| expression: | |
| simple: | |
| expression: "<hallo><id>123</id></hallo>" | |
| resultType: org.w3c.dom.Document | |
| - choice: | |
| when: | |
| - expression: | |
| xpath: | |
| expression: "($Foo/hello/id = $Bar/holla/id) and /dummy='YES' and $Baz/hallo/id=123" | |
| steps: | |
| - log: | |
| message: EQUAL | |
| otherwise: | |
| steps: | |
| - log: | |
| message: DIFFERENT | |
| ---- | |
| ==== | |
| == Functions | |
| Camel adds the following XPath functions that can be used to access the | |
| exchange: | |
| [width="100%",cols="10%,10%,10%,70%",options="header",] | |
| |=== | |
| |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 xref:manual:ROOT:using-propertyplaceholder.adoc[Property Placeholder]. | |
| |`function:simple` |simple expression |Object |To evaluate a xref:simple-language.adoc[Simple] language. | |
| |=== | |
| CAUTION: `function:properties` and `function:simple` is not supported | |
| when the return type is a `NodeSet`, such as when using with a xref:eips:split-eip.adoc[Split] EIP. | |
| Here's an example showing some of these functions in use. | |
| === Functions example | |
| If you prefer to configure your routes in your Spring | |
| XML file, then you can use XPath expressions as follows | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| Namespaces ns = new Namespaces("foo", "http://example.com/person"); | |
| from("activemq:MyQueue") | |
| .filter(xpath("/foo:person[@name='James']", ns)) | |
| .to("mqseries:SomeOtherQueue"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <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://camel.apache.org/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> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: activemq:MyQueue | |
| steps: | |
| - filter: | |
| expression: | |
| xpath: | |
| expression: "/foo:person[@name='James']" | |
| steps: | |
| - to: | |
| uri: mqseries:SomeOtherQueue | |
| ---- | |
| ==== | |
| Notice how we can reuse the namespace prefixes, *foo* in this case, in | |
| the XPath expression for easier namespace-based XPath expressions. | |
| == 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*. | |
| You often need to access the data multiple times when you use | |
| xref:xpath-language.adoc[XPath] as a xref:xpath-language.adoc[Message Filter] or | |
| Content-Based Router. | |
| Then, you should use Stream Caching or convert the message body to | |
| a `String` prior, which is safe to be re-read multiple times. | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| from("queue:foo") | |
| .filter().xpath("//foo") | |
| .to("queue:bar"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <route> | |
| <from uri="queue:foo"/> | |
| <filter> | |
| <xpath>//foo</xpath> | |
| <to uri="queue:bar"/> | |
| </filter> | |
| </route> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: queue:foo | |
| steps: | |
| - filter: | |
| expression: | |
| xpath: | |
| expression: //foo | |
| steps: | |
| - to: | |
| uri: queue:bar | |
| ---- | |
| ==== | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| from("queue:foo") | |
| .choice().when(xpath("//foo")).to("queue:bar") | |
| .otherwise().to("queue:others"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <route> | |
| <from uri="queue:foo"/> | |
| <choice> | |
| <when> | |
| <xpath>//foo</xpath> | |
| <to uri="queue:bar"/> | |
| </when> | |
| <otherwise> | |
| <to uri="queue:others"/> | |
| </otherwise> | |
| </choice> | |
| </route> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: queue:foo | |
| steps: | |
| - choice: | |
| when: | |
| - expression: | |
| xpath: | |
| expression: //foo | |
| steps: | |
| - to: | |
| uri: queue:bar | |
| otherwise: | |
| steps: | |
| - to: | |
| uri: queue:others | |
| ---- | |
| ==== | |
| == Setting a 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: | |
| ._Java-only: XPath expression builder API_ | |
| [source,java] | |
| ---- | |
| xpath("/foo:person/@id", String.class) | |
| ---- | |
| In XML DSL you use the *resultType* attribute to provide the fully qualified classname. | |
| [source,xml] | |
| ---- | |
| <xpath resultType="java.lang.String">/foo:person/@id</xpath> | |
| ---- | |
| TIP: Classes from `java.lang` can omit the FQN name, so you can use `resultType="String"` | |
| Using `@XPath` annotation: | |
| ._Java-only: Java bean integration annotation_ | |
| [source,java] | |
| ---- | |
| @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. | |
| == 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 `source` attribute with the `header:` prefix | |
| (`variable:` and `property:` work the same way for an exchange variable or property). | |
| [source,xml] | |
| ---- | |
| <xpath source="header:invoiceDetails">/invoice/@orderType = 'premium'</xpath> | |
| ---- | |
| And in Java DSL you specify the source with the fluent expression builder as shown: | |
| ._Java-only: XPath expression builder API_ | |
| [source,java] | |
| ---- | |
| expression().xpath("/invoice/@orderType = 'premium'").source("header:invoiceDetails").end() | |
| ---- | |
| == Example | |
| Here is a simple example using an XPath expression as a predicate in a | |
| xref:eips:filter-eip.adoc[Message Filter]: | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| from("direct:start") | |
| .filter().xpath("/person[@name='James']") | |
| .to("mock:result"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <route> | |
| <from uri="direct:start"/> | |
| <filter> | |
| <xpath>/person[@name='James']</xpath> | |
| <to uri="mock:result"/> | |
| </filter> | |
| </route> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: direct:start | |
| steps: | |
| - filter: | |
| expression: | |
| xpath: | |
| expression: "/person[@name='James']" | |
| steps: | |
| - to: | |
| uri: mock:result | |
| ---- | |
| ==== | |
| == Using namespaces | |
| An XPath expression that uses a namespace prefix needs the namespace declared. In Java DSL use | |
| `org.apache.camel.support.builder.Namespaces`, which is passed to `xpath` as the second parameter. In XML DSL | |
| declare the prefix on a root element (`routes` or `route`), and in YAML DSL list the namespaces on the expression: | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| Namespaces ns = new Namespaces("c", "http://acme.com/cheese"); | |
| from("direct:start") | |
| .filter(xpath("/c:person[@name='James']", ns)) | |
| .to("mock:result"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <routes xmlns="http://camel.apache.org/schema/xml-io" xmlns:c="http://acme.com/cheese"> | |
| <route> | |
| <from uri="direct:start"/> | |
| <filter> | |
| <xpath>/c:person[@name='James']</xpath> | |
| <to uri="mock:result"/> | |
| </filter> | |
| </route> | |
| </routes> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: direct:start | |
| steps: | |
| - filter: | |
| expression: | |
| xpath: | |
| expression: "/c:person[@name='James']" | |
| namespace: | |
| - key: c | |
| value: http://acme.com/cheese | |
| steps: | |
| - to: | |
| uri: mock:result | |
| ---- | |
| ==== | |
| 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, e.g.: | |
| [source,xpath] | |
| ---- | |
| /c:person[@name='James'] | |
| ---- | |
| The namespace builder supports adding multiple namespaces as shown: | |
| ._Java-only: Namespaces builder API_ | |
| [source,java] | |
| ---- | |
| 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 it is different, as you set up 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"`: | |
| ._XML-only:_ | |
| [source,xml] | |
| ---- | |
| <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. | |
| == Using @XPath Annotation for Bean Integration | |
| You can use xref:manual::bean-integration.adoc[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. | |
| ._Java-only: Java bean integration annotation_ | |
| [source,java] | |
| ---- | |
| public class Foo { | |
| @Consume(uri = "activemq:my.queue") | |
| public void doSomething(@XPath("/person/@name") String name, String xml) { | |
| // process the inbound message here | |
| } | |
| } | |
| ---- | |
| == 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 xref:manual:ROOT:type-converter.adoc[Type Converter] | |
| and hence why `CamelContext` is needed. | |
| For example, you can do something like this: | |
| ._Java-only: XPathBuilder API_ | |
| [source,java] | |
| ---- | |
| 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: | |
| ._Java-only: XPathBuilder API_ | |
| [source,java] | |
| ---- | |
| 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: | |
| ._Java-only: XPathBuilder API_ | |
| [source,java] | |
| ---- | |
| String name = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>cheese</bar></foo>"); | |
| ---- | |
| == Using Saxon with XPathBuilder | |
| You need to add *camel-saxon* as dependency to your project. | |
| It's now easier to use http://saxon.sourceforge.net/[Saxon] with the | |
| XPathBuilder which can be done in several ways as shown below | |
| * Using a custom XPathFactory | |
| * Using ObjectModel | |
| The easy one | |
| === Setting a custom XPathFactory using System Property | |
| Camel now supports reading the | |
| http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/xpath/XPathFactory.html#newInstance(java.lang.String)[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 | |
| ---- | |
| === Enabling Saxon from XML DSL | |
| Similarly to Java DSL, to enable Saxon from XML DSL, you have three | |
| options: | |
| Referring to a custom factory: | |
| [source,xml] | |
| ---- | |
| <xpath factoryRef="saxonFactory" resultType="java.lang.String">current-dateTime()</xpath> | |
| ---- | |
| And declare a bean with the factory: | |
| [source,xml] | |
| ---- | |
| <bean id="saxonFactory" class="net.sf.saxon.xpath.XPathFactoryImpl"/> | |
| ---- | |
| Specifying the object model: | |
| [source,xml] | |
| ---- | |
| <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: | |
| [source,xml] | |
| ---- | |
| <xpath saxon="true" resultType="java.lang.String">current-dateTime()</xpath> | |
| ---- | |
| == 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 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, the 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. | |
| === 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: | |
| [source,text] | |
| ---- | |
| [me: {prefix -> namespace}, {prefix -> namespace}], [parent: [me: {prefix -> namespace}, {prefix -> namespace}], [parent: [me: {prefix -> namespace}]]] | |
| ---- | |
| 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 | |
| === 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 analyze 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 | |
| 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: | |
| ._Java-only: XPathBuilder API_ | |
| [source,java] | |
| ---- | |
| XPathBuilder.xpath("/foo:person/@id", String.class).logNamespaces() | |
| ---- | |
| XML DSL: | |
| [source,xml] | |
| ---- | |
| <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]} | |
| ---- | |
| == Loading script from external resource | |
| You can externalize the script and have Apache Camel load it from a resource | |
| such as `"classpath:"`, `"file:"`, or `"http:"`. | |
| This is done using the following syntax: `"resource:scheme:location"`, | |
| e.g., to refer to a file on the classpath you can do: | |
| ._Java-only: Java DSL expression syntax_ | |
| [source,java] | |
| ---- | |
| .setHeader("myHeader").xpath("resource:classpath:myxpath.txt", String.class) | |
| ---- | |
| == Transforming an XML message | |
| For basic XML transformation where you have a fixed structure, you can represent with a combination of using | |
| Camel simple and XPath language as: | |
| Given this XML body | |
| [source,xml] | |
| ---- | |
| <order id="123"> | |
| <item>Brake</item> | |
| <first>scott</first> | |
| <last>jackson</last> | |
| <address> | |
| <co>sweden</co> | |
| <zip>12345</zip> | |
| </address> | |
| </order> | |
| ---- | |
| Which you want to transform to a smaller structure: | |
| [source,xml] | |
| ---- | |
| <user> | |
| <rool>123</rool> | |
| <country>sweden</country> | |
| <fullname>scott</fullname> | |
| </user> | |
| ---- | |
| Then you can use simple as template and XPath to grab the content from the message payload, as shown in the route snippet below: | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| from("direct:start") | |
| .transform().simple(""" | |
| <user> | |
| <rool>${xpath(/order/@id)}</rool> | |
| <country>${xpath(/order/address/co/text())}</country> | |
| <fullname>${xpath(/order/first/text())}</fullname> | |
| </user>""") | |
| .to("mock:result"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <route> | |
| <from uri="direct:start"/> | |
| <transform> | |
| <simple><![CDATA[<user> | |
| <rool>${xpath(/order/@id)}</rool> | |
| <country>${xpath(/order/address/co/text())}</country> | |
| <fullname>${xpath(/order/first/text())}</fullname> | |
| </user>]]></simple> | |
| </transform> | |
| <to uri="mock:result"/> | |
| </route> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: direct:start | |
| steps: | |
| - transform: | |
| simple: | | |
| <user> | |
| <rool>${xpath(/order/@id)}</rool> | |
| <country>${xpath(/order/address/co/text())}</country> | |
| <fullname>${xpath(/order/first/text())}</fullname> | |
| </user> | |
| - to: | |
| uri: mock:result | |
| ---- | |
| ==== | |
| Notice how we use `${xpath(exp}` syntax in the simple template to use xpath that will be evaluated on the message body, | |
| to extract the content to be used in the output (see previous for output). | |
| Since the simple language can output anything, you can also use this to output in plain text or JSON, etc. | |
| [tabs] | |
| ==== | |
| Java:: | |
| + | |
| [source,java] | |
| ---- | |
| from("direct:start") | |
| .transform().simple("The order ${xpath(/order/@id)} is being shipped to ${xpath(/order/address/co/text())}") | |
| .to("mock:result"); | |
| ---- | |
| XML:: | |
| + | |
| [source,xml] | |
| ---- | |
| <route> | |
| <from uri="direct:start"/> | |
| <transform> | |
| <simple>The order ${xpath(/order/@id)} is being shipped to ${xpath(/order/address/co/text())}</simple> | |
| </transform> | |
| <to uri="mock:result"/> | |
| </route> | |
| ---- | |
| YAML:: | |
| + | |
| [source,yaml] | |
| ---- | |
| - route: | |
| from: | |
| uri: direct:start | |
| steps: | |
| - transform: | |
| simple: "The order ${xpath(/order/@id)} is being shipped to ${xpath(/order/address/co/text())}" | |
| - to: | |
| uri: mock:result | |
| ---- | |
| ==== | |
| == 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 add the following to your `pom.xml`, | |
| substituting the version number for the latest & greatest release. | |
| [source,xml] | |
| ---- | |
| <dependency> | |
| <groupId>org.apache.camel</groupId> | |
| <artifactId>camel-xpath</artifactId> | |
| <version>x.x.x</version> | |
| </dependency> | |
| ---- |