XPathPage edited by Claus IbsenChanges (1)
Full ContentXPathCamel supports XPath to allow an _expression_ or Predicate to be used in the DSL or Xml Configuration. For example you could use XPath to create an Predicate in a Message Filter or as an _expression_ for a Recipient List. from("queue:foo"). filter().xpath("//foo")). to("queue:bar")
Namespace givenIf the namespace is given then Camel is instructed exactly what to return. However when resolving either in or out 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 givenIf 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:
FunctionsCamel adds the following XPath functions that can be used to access the exchange:
Notice: function:properties and function:simple is not supported when the return type is a NodeSet, such as when using with a Splitter EIP. Here's an example showing some of these functions in use. from("direct:start").choice() .when().xpath("in:header('foo') = 'bar'").to("mock:x") .when().xpath("in:body() = '<two/>'").to("mock:y") .otherwise().to("mock:z"); And the new functions introduced in Camel 2.5: // setup properties component PropertiesComponent properties = new PropertiesComponent(); properties.setLocation("classpath:org/apache/camel/builder/xml/myprop.properties"); context.addComponent("properties", properties); // myprop.properties contains the following properties // foo=Camel // bar=Kong from("direct:in").choice() // $type is a variable for the header with key type // here we use the properties function to lookup foo from the properties files // which at runtime will be evaluted to 'Camel' .when().xpath("$type = function:properties('foo')") .to("mock:camel") // here we use the simple language to evaluate the _expression_ // which at runtime will be evaluated to 'Donkey Kong' .when().xpath("//name = function:simple('Donkey ${properties:bar}')") .to("mock:donkey") .otherwise() .to("mock:other") .end(); Using XML configurationIf 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! See also this discussion on the mailinglist about using your own namespaces with xpath Setting result typeThe XPath _expression_ will return a result type using native XML objects such as org.w3c.dom.NodeList. But 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 Spring DSL you use the resultType attribute to provide a fully qualified classname: <xpath resultType="java.lang.String">/foo:person/@id</xpath> In @XPath:
@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 HeadersAvailable as of Camel 2.11 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. In XML DSL: <camelContext id="xpathHeaderNameTest" xmlns="http://camel.apache.org/schema/blueprint"> <route> <from uri="direct:in"/> <choice> <when> <!-- use headerName attribute to refer to a header --> <xpath headerName="invoiceDetails">/invoice/@orderType = 'premium'</xpath> <to uri="mock:premium"/> </when> <when> <!-- use headerName attribute to refer to a header --> <xpath headerName="invoiceDetails">/invoice/@orderType = 'standard'</xpath> <to uri="mock:standard"/> </when> <otherwise> <to uri="mock:unknown"/> </otherwise> </choice> </route> </camelContext> ExamplesHere 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"); If you have a standard set of namespaces you wish to work with and wish to share them across many different XPath expressions you can use the NamespaceBuilder as shown in this example // lets define the namespaces we'll need in our filters Namespaces ns = new Namespaces("c", "http://acme.com/cheese") .add("xsd", "http://www.w3.org/2001/XMLSchema"); // now lets create an xpath based Message Filter from("direct:start"). filter(ns.xpath("/c:person[@name='James']")). to("mock:result"); In this sample we have a choice construct. The first choice evaulates if the message has a header key type that has the value Camel. from("direct:in").choice() // using $headerName is special notation in Camel to get the header key .when().xpath("$type = 'Camel'") .to("mock:camel") // here we test for the body name tag .when().xpath("//name = 'Kong'") .to("mock:donkey") .otherwise() .to("mock:other") .end(); And the spring XML equivalent of the route: <camelContext xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="direct:in"/> <choice> <when> <xpath>$type = 'Camel'</xpath> <to uri="mock:camel"/> </when> <when> <xpath>//name = 'Kong'</xpath> <to uri="mock:donkey"/> </when> <otherwise> <to uri="mock:other"/> </otherwise> </choice> </route> </camelContext> XPath injectionYou 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. The default XPath annotation has SOAP and XML namespaces available. If you want to use your own namespace URIs in an XPath _expression_ you can use your own copy of the XPath annotation to create whatever namespace prefixes you want to use. import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.w3c.dom.NodeList; import org.apache.camel.component.bean.XPathAnnotationExpressionFactory; import org.apache.camel.language.LanguageAnnotation; import org.apache.camel.language.NamespacePrefix; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @LanguageAnnotation(language = "xpath", factory = XPathAnnotationExpressionFactory.class) public @interface MyXPath { String value(); // You can add the namespaces as the default value of the annotation NamespacePrefix[] namespaces() default { @NamespacePrefix(prefix = "n1", uri = "http://example.org/ns1"), @NamespacePrefix(prefix = "n2", uri = "http://example.org/ns2")}; Class<?> resultType() default NodeList.class; } i.e. cut and paste upper code to your own project in a different package and/or annotation name then add whatever namespace prefix/uris you want in scope when you use your annotation on a method parameter. Then when you use your annotation on a method parameter all the namespaces you want will be available for use in your XPath _expression_. For example public class Foo { @MessageDriven(uri = "activemq:my.queue") public void doSomething(@MyXPath("/ns1:foo/ns2:bar/text()") String correlationID, @Body String body) { // process the inbound message here } } Using XPathBuilder without an ExchangeAvailable as of Camel 2.3 You can now use the org.apache.camel.builder.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 for example 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 thus you can do it a bit simpler: String name = XPathBuilder.xpath("foo/bar").evaluate(context, "<foo><bar>cheese</bar></foo>"); Using Saxon with XPathBuilderAvailable as of Camel 2.3 You need to add camel-saxon as dependency to your project. Its now easier to use Saxon with the XPathBuilder which can be done in several ways as shown below. Using a factory // create a Saxon factory XPathFactory fac = new net.sf.saxon.xpath.XPathFactoryImpl(); // create a builder to evaluate the xpath using the saxon factory XPathBuilder builder = XPathBuilder.xpath("tokenize(/foo/bar, '_')[2]").factory(fac); // evaluate as a String result String result = builder.evaluate(context, "<foo><bar>abc_def_ghi</bar></foo>"); assertEquals("def", result); Using ObjectModel // create a builder to evaluate the xpath using saxon based on its object model uri XPathBuilder builder = XPathBuilder.xpath("tokenize(/foo/bar, '_')[2]").objectModel("http://saxon.sf.net/jaxp/xpath/om"); // evaluate as a String result String result = builder.evaluate(context, "<foo><bar>abc_def_ghi</bar></foo>"); assertEquals("def", result); The easy one // create a builder to evaluate the xpath using saxon XPathBuilder builder = XPathBuilder.xpath("tokenize(/foo/bar, '_')[2]").saxon(); // evaluate as a String result String result = builder.evaluate(context, "<foo><bar>abc_def_ghi</bar></foo>"); assertEquals("def", result); Setting a custom XPathFactory using System PropertyAvailable as of Camel 2.3 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: // set system property with the XPath factory to use which is Saxon System.setProperty(XPathFactory.DEFAULT_PROPERTY_NAME + ":" + "http://saxon.sf.net/jaxp/xpath/om", "net.sf.saxon.xpath.XPathFactoryImpl"); // create a builder to evaluate the xpath using saxon XPathBuilder builder = XPathBuilder.xpath("tokenize(/foo/bar, '_')[2]"); // evaluate as a String result String result = builder.evaluate(context, "<foo><bar>abc_def_ghi</bar></foo>"); assertEquals("def", result); 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 Spring DSLAvailable as of Camel 2.10 Similarly to Java DSL, to enable Saxon from Spring DSL you have three options: Specifying the factory <xpath factoryRef="saxonFactory" resultType="java.lang.String">current-dateTime()</xpath> Specifying the object model <xpath objectModel="http://saxon.sf.net/jaxp/xpath/om" resultType="java.lang.String">current-dateTime()</xpath> Shortcut <xpath saxon="true" resultType="java.lang.String">current-dateTime()</xpath> Namespace auditing to aid debuggingAvailable as of Camel 2.10 A large number of 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 accesible from both predicates and expressions. Logging the Namespace Context of your XPath _expression_/predicateEvery 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.builder.xml.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: [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:
Auditing namespacesCamel 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:
You can enable this option in Java DSL and Spring DSL. Java DSL: XPathBuilder.xpath("/foo:person/@id", String.class).logNamespaces() Spring DSL: <xpath logNamespaces="true" resultType="String">/foo:person/@id</xpath> The result of the auditing will be appear at the INFO level under the org.apache.camel.builder.xml.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 resourceAvailable as of Camel 2.11 You can externalize the script and have Camel load it from a resource such as "classpath:", "file:", or "http:". .setHeader("myHeader").xpath("resource:classpath:myxpath.txt", String.class) DependenciesThe XPath language is part of camel-core.
Change Notification Preferences
View Online
|
View Changes
|
Add Comment
|
- [CONF] Apache Camel > XPath confluence
- [CONF] Apache Camel > XPath Claus Ibsen (Confluence)