Camel XML XPath XSLT Aggregator

This mouth I went to Greece for a customer of mine. I worked at Athens on Camel integration with Mainframe legacy stuff. Customer would like to use XML message format during the full Camel route process. His wish was to reuse some Camel routes as new service components. Fortunately, Camel framework meets all of this requirements out of the box. We will this in this simple sample how to do it.

a Camel sample with XML messages and XSL transformation

Camel framework

Camel project is managed by the Apache foundation. Camel empowers you to define routing and mediation rules in a variety of domain-specific languages, including a Java-based Fluent API, Spring or Blueprint XML Configuration files, and a Scala DSL. This means you get smart completion of routing rules in your IDE, whether in a Java, Scala or XML editor. (extract of the Camel website)

Apache Camel uses URIs to work directly with any kind of Transport or messaging model such as HTTP, ActiveMQ, JMS, JBI, SCA, MINA or CXF, as well as pluggable Components and Data Format options. Apache Camel is a small library with minimal dependencies for easy embedding in any Java application. Apache Camel lets you work with the same API regardless which kind of Transport is used - so learn the API once and you can interact with all the Components provided out-of-box. (extract of the Camel website)

Message

A Camel message contains headers and body part. Headers can be use to add some new informations (meta data) for the camel current process or next components. Body is the payload of the Camel message. During Camel process, the payload could change on each step and sometime payload is replace by a new one.

Aggregator Strategy

Camel provides enrich pattern with aggregator strategy. With this component both messages (old and new one) can be merge to a new one. Developers can implement his own version of aggregator. In this example, we will see how to use the XSLT Aggregator into a Camel route.

Use case

Customer and product

The use case is very simple, we send a message to enrich customer information and product information. The route receives a message via the file system, checks xml validity and calls two services to retrieve more customer and product informations. Because we would like to keep the full payload anytime and avoid any payload backup into header we have to use enrich pattern. Route last step is to write the final result to a new file.

Route

Here is the main route. You can notice <enrich> elements for calling service.

  <route id="service">
      <from uri="file:target/in?noop=true" />
      <to uri="validator:service.xsd" />
      <convertBodyTo type="String" />
      <to uri="log:net.a.g.camel" />
      <enrich strategyRef="xasCM">
          <constant>direct:customerService</constant>
      </enrich>
      <to uri="log:net.a.g.camel" />
      <enrich strategyRef="xasPM">
          <constant>direct:productService</constant>
      </enrich>
      <to uri="log:net.a.g.camel" />
      <to uri="validator:service.xsd" />
      <to uri="file:target/out" />
  </route>

Service

Here is the service route to retrieve informations. You can notice <xpath> elements for extract the only information we need for the service. We just mock the end of service with a constant payload to send back. In real life, you could call a database or a soap/rest service for sure.

        <route id="customer">
            <from uri="direct:customerService" />
            <to uri="log:net.a.g.camel" />
            <setBody>
                <xpath>//s:customer</xpath>
            </setBody>
            <to uri="log:net.a.g.camel" />
            <transform>
                <constant><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
                  <s:customer xmlns:s="urn://example.org/service/2016/11">
                      <s:firstName>Jean</s:firstName>
                      <s:lastName>Paul</s:lastName>
                      <s:title>M.</s:title>
                  </s:customer>]]></constant>
            </transform>
        </route>

        <route id="product">
            <from uri="direct:productService" />
            <to uri="log:net.a.g.camel" />
            <setBody>
                <xpath>//s:product</xpath>
            </setBody>
            <to uri="log:net.a.g.camel" />
            <transform>
                <constant><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
                  <s:product xmlns:s="urn://example.org/service/2016/11">
                          <s:id>123</s:id>
                          <s:amount>122</s:amount>
                          <s:price>8495</s:price>
                          <s:name>LOUKOUM</s:name>
                   </s:product>]]></constant>
            </transform>
        </route>

XSLT

Here is the XSLT Aggregator for merging the pre-call service message with the new message from the return of the service. By default, Camel passes the old message to XSLT and adds new message as a XSLT parameter new-exchange. The XSL is very simple, we use identity algorithm to copy all previous message (the first xsl:template block) and when we need to inject new data we fork to an other algorithm (the second xsl:template block). The merging stuff is very simple even if it is a demo.

  <?xml version="1.0" encoding="UTF-8"?>
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  	version="2.0" xmlns:s="urn://example.org/service/2016/11">

  	<xsl:param name="new-exchange" />

  	<xsl:template match="@* | node()">
  		<xsl:copy>
  			<xsl:apply-templates select="@* | node()" />
  		</xsl:copy>
  	</xsl:template>

  	<xsl:template match="s:customer">
  		<s:customer>
  			<xsl:copy-of select="s:id" />
  			<xsl:copy-of select="$new-exchange//s:firstName" />
  			<xsl:copy-of select="$new-exchange//s:lastName" />
  			<xsl:copy-of select="$new-exchange//s:title" />
  			<xsl:copy-of select="s:country" />
  		</s:customer>
  	</xsl:template>

  </xsl:stylesheet>

Into the main route, you need to add the strategy in <xpath> element, in this case we just use XsltAggregationStrategy, it comes with camel-core.

    <bean id="xasCM"
        class="org.apache.camel.util.toolbox.XsltAggregationStrategy">
        <constructor-arg index="0" value="customerMerge.xslt" />
    </bean>

Conclusion

The camel framework is very useful and pleasant to work with. Its flexibility brings a very cool leverage to implements more complex stuff in the real life. The XsltAggregationStrategy can help you to use XML during the whole camel route. Using camel route as service is very helpful to add more malleable development, you can reuse service anytime. You must keep in mind XML schema (IN/OUT message) validation and the versioning management.

Tag

Date