Apache Camel: Integration Nirvana

While it is perfectly legitimate to use Camel as a standalone Java application, it is often useful to embed it in a container. In this case, we will be loading Camel from Spring. The Spring beans XML file is shown in Listing 2. First we start an embedded Apache ActiveMQ broker and connect Camel to it. We also load up some helper beans that we will reference from the DSL. Finally, the camelContext element tells Camel to look for routes in the org.fusesource.camel package. Routes are Java classes that extend the RouteBuilder class in Camel.

Listing 2: Spring XML file that configures an embedded ActiveMQ broker, several beans used in the Camel route, and initializes the Camel Context to search for routes in the org.fusesource.camel package.
<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-2.5.xsd
http://activemq.apache.org/camel/schema/spring
http://activemq.apache.org/camel/schema/spring/camel-spring.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd">

<broker xmlns="http://activemq.apache.org/schema/core" useJmx="false" persistent="false">
<transportConnectors>
<transportConnector uri="tcp://localhost:61616" />
</transportConnectors>
</broker>

<bean id="jms" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>

<bean id="normalizer" class="org.fusesource.camel.OrderNormalizer"/>
<bean id="orderHelper" class="org.fusesource.camel.OrderHelper"/>

<camelContext xmlns="http://activemq.apache.org/camel/schema/spring">
<package>org.fusesource.camel</package>
</camelContext>

</beans>



The real meat of the Camel implementation lies in the OrderRouter class (shown in Listing 3). This class extends RouteBuilder, so it will be automatically picked up and loaded by Camel's runtime.

Looking back at Figure 2, we need to receive orders from an FTP (substituted with File) and HTTP endpoint, formatted as shown in Listing 4. In the DSL we can specify these incoming endpoints with two from elements. Both from elements are connected to a to("jms:incomingOrderQueue") element, which will send the messages to a queue on the ActiveMQ broker.



Listing 3: Route definitions for the example. The routing rules are specified using a fluent API, referred to as Camel's DSL.

public class OrderRouter extends RouteBuilder {

@Override
public void configure() throws Exception {
JaxbDataFormat jaxb = new JaxbDataFormat("org.fusesource.camel");

// Receive orders from two endpoints
from("file:src/data?noop=true").to("jms:incomingOrderQueue");
from("jetty:http://localhost:8888/placeorder")
.inOnly().to("jms:incomingOrderQueue")
.transform().constant("OK");

// Do the normalization
from("jms:incomingOrderQueue")
.convertBodyTo(String.class)
.choice()
.when().method("orderHelper", "isXml")
.unmarshal(jaxb)
.to("jms:orderQueue")
.when().method("orderHelper", "isCsv")
.unmarshal().csv()
.to("bean:normalizer")
.to("jms:orderQueue");
}

}



In the case of the HTTP endpoint, there are a couple of extra things to mention. First off the HTTP client will be expecting a response from the application so we have to handle that. In Camel, we have full control over what the client gets back from the HTTP endpoint. Each response is determined by the last method in our current route definition (each Java statement is a route definition). In our case we use the transform method to set the response to the constant string "OK". Since we handle the response ourselves, we don’t want any response to come from the JMS incomingOrderQueue. To send to this queue in a fire-and-forget fashion we add the inOnly modifier.  

It is important to note at this point that when writing Camel DSL in a modern Java IDE, selection of the next processing step is easy because of auto complete. The auto complete feature basically gives you a list of processors (i.e. EIPs) to choose from at any point in your route. Since fluent APIs chain methods together, the only method you need to remember is the from; all other methods are shown via auto complete.



Listing 4: Incoming message formats; XML on top, CSV below.

<?xml version="1.0" encoding="UTF-8"?>
<order name="motor" amount="1"/>

"name", "amount"
"brake pad", "2"



The next section of DSL in Listing 3 specifies the Normalizer, complete with Content-Based Router and two Message Translators. First we specify that we want to consume messages from the incomingOrderQueue on the ActiveMQ broker. The content based routing of the messages is done with the choice and when methods. In our case, we want to send CSV messages to one Message Translator and XML messages to another. To check what type of message we have we will be using a simple Java bean shown in Listing 5. Of course, this is demonstration code only; for production cases you would want to add more thorough checking of content types.

Listing 5: Java bean that contains helper methods to be used in the DSL.

public class OrderHelper {
public boolean isCsv(String body) {
return !body.contains("<?xml");
}

public boolean isXml(String body) {
return body.contains("<?xml");
}
}



If the message has XML content, we use the JAXB data format to unmarshal the XML payload into an Order object. As shown in Listing 6, the Order object has JAXB annotations to describe the mapping to XML. You of course don't need to use JAXB here; it just makes things very easy as you don't have to do any nasty XML parsing by hand.

Listing 6: The Order domain class with JAXB annotations for easy mapping to and from XML.

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Order implements Serializable {
@XmlAttribute
private String name;
@XmlAttribute
private int amount;

public Order() {
}

public Order(String name, int amount) {
this.name = name;
this.amount = amount;
}

@Override
public String toString() {
return "Order[" + name + " , " + amount + "]";
}
}



For the transformation from CSV to Order object, we don't have a nice JAXB analogue. We do have support in Camel for unmarhsaling CSV content into a List though. We use this in combination with a custom bean to do the complete transformation. The OrderNormalizer bean shown in Listing 7 takes the List of Lists created by the CSV unmarshaler and creates a new Order object from it.

Listing 7: Java bean that takes the CSV data and creates a new Order domain object from it.

public class OrderNormalizer {
public Order fromCsvToOrder(List<List<String>> body) {
List<String> orderHeaders = body.get(0);
List<String> orderValues = body.get(1);
return new Order(orderValues.get(0), Integer.parseInt(orderValues.get(1)));
}
}



At this point, successfully normalized messages are sent to the orderQueue for processing by some other application at the Rider Auto Parts business.

Conclusion

In this article I've shown two common problems that an integration developer may face: dealing with the specifics of applications and transports, and coming up with good solutions to integration problems. The Apache Camel project provides a nice answer to both of these problems. As the example has shown, solving integration problems with Camel is straight forward and results in relatively concise code. In my opinion it is the closest thing to integration nirvana that we have today.

Links
  1. Apache Camel – http://camel.apache.org
  2. FUSE Mediation Router (based on Apache Camel) – http://fusesource.com/products/enterprise-camel
  3. Enterprise Integration Patterns – http://www.enterpriseintegrationpatterns.com
  4. Jon’s Blog – http://janstey.blogspot.com
  5. Camel in Action book - http://www.manning.com/ibsen
  6. Article source code - http://repo.fusesource.com/maven2/org/fusesource/examples/rider-auto-example/1.0/rider-auto-example-1.0.zip
Author

Jonathan Anstey is a senior engineer working for Progress Software Corporation specializing in the enterprise integration space. Jon focuses mostly on Apache Camel and its Progress endorsed likeness, FUSE Mediation Router. He also works on the Apache ActiveMQ and Apache ServiceMix projects 

 

AttachmentSize
camelArch1.jpg47.49 KB
riderAutoEips1.jpg25.64 KB
DZone_Camel_Article_JonathanAnstey.pdf647.95 KB
0
Average: 4.7 (3 votes)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Craig replied on Tue, 2009/03/24 - 4:37am

Any chance you could provide a word document or PDF version of this article? I'd like to print it out but need something more "printer" friendly.

James Strachan replied on Tue, 2009/03/24 - 4:58am

Great article Jon! BTW the image just before "Figure 1" isn't being displayed - nor is the image on the second page

davsclaus replied on Tue, 2009/03/24 - 6:09am

Great article Jonathan. Maybe the first DSL example (only 1 line of code) can be split into two lines to avoid it being clipped by the button bar.

janstey replied on Tue, 2009/03/24 - 7:04am in response to: jstrachan

Weird. No clue why the images broke - it was working yesterday! I've hopefully fixed them for good now.

janstey replied on Tue, 2009/03/24 - 7:06am in response to: davsclaus

Good suggestion. Its fixed now.

janstey replied on Tue, 2009/03/24 - 7:49am in response to: codecraig

Craig,

 I just uploaded a PDF version of this article. Enjoy!

 http://architects.dzone.com/sites/all/files/DZone_Camel_Article_JonathanAnstey.pdf

java developer replied on Tue, 2009/03/24 - 10:29am

Simply Awesome! Could you expand on this and give design guidelines about non-functional requirements such as security. For example, how would one secure (or add authentication) to the http endpoint used in your example. Other interesting issues in the integration space are dealing with extremely large input files, what is the best way to deal with large files and what are Camels true capabilities.

janstey replied on Tue, 2009/03/24 - 12:08pm in response to: an114162

To secure the HTTP endpoint you could use SSL. See http://camel.apache.org/jetty.html for more details.

To handle large messages you could do a couple of things...

1. Make sure you use input streams instead of loading messages into memory. For the HTTP endpoint example, the message is streamed by default. However, you may need to tune your route for these kinds of messages. For instance, in Listing 3 the convertBodyTo(String.class) method is used to convert the incoming stream into a String so that we can do things like JAXB unmarshalling on it. This will of course load the message into memory so we may have to do something else here to handle the large message.

2. Use the Claim Check EIP (http://camel.apache.org/claim-check.html) to store the larger portion of your message for retrieval later.

For other Camel capabilities, you can take a peek at the online documentation, Camel User Guide, and FUSE Mediation Router documentation. For specific inquires, you can alway find help on the Apache and FUSE forums.

Scott Stanlick replied on Thu, 2009/03/26 - 7:26am

Great article brother! Can you list a few of the competitors? Is Mule competing in this space? Peace, Scott

janstey replied on Thu, 2009/04/02 - 1:26pm in response to: stanlick

Glad you enjoyed it! Yeah, Mule is the other competitor with a few years under the belt like Camel. Two new projects that implement EIPs are Spring Integration and Project Fuji.

 I should also mention ServiceMix here which has had support for EIPs since before Camel was started. The latest version of ServiceMix supports EIPs through Camel in favour of the old EIP component. 

Charles Moulliard replied on Tue, 2009/03/31 - 5:47am

Hi John,

Congratulations for this awesome article. You have described in a 4 pages document the "quintessence" of what Camel is.

BTW : May I suggest that you add also in your dataformat section : Camel-bindy which is a new DataFormat component who allows you to map CSV file directly to your pojo (using Java Annotation), Fix messages format, and more in the future.

remark : I will publish soon a tutorial on Camel with Bindy, OSGI, CXF and ServiceMix4

Regards,

Charles

janstey replied on Tue, 2009/03/31 - 7:36am in response to: charliem

Charles,

Just added it to the list. I actually wanted to use camel-bindy in the example but it is a new feature in Camel 2.0, which is not released yet (I'm using Camel 1.6). To give readers a tease, with camel-bindy you can add annotations for CSV to POJO mapping just like we did for the XML to POJO mapping. The Order class would have extra annotations like

@CsvRecord(separator = ",")
public class Order implements Serializable {
    @DataField(pos = 0)
    private String name;
    @DataField(pos = 1)
    private int amount;

 and then in our OrderRouter we can simplify the CSV transformation as follows

public class OrderRouter extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        JaxbDataFormat jaxb = new JaxbDataFormat("org.fusesource.camel");
        BindyCsvDataFormat csv = new BindyCsvDataFormat("org.fusesource.camel");        
        ...
        from("jms:incomingOrderQueue")
         .convertBodyTo(String.class)
         .choice()
           .when().method("orderHelper", "isXml")
             .unmarshal(jaxb)
             .to("jms:orderQueue")
           .when().method("orderHelper", "isCsv")
             .unmarshal(csv)        
             .to("jms:orderQueue");
    }
}

Notice how we have added a new bindy DataFormat and used it instead of the default CSV unmarshaller. In this case we have also elimintaed the need for the OrderNormalizer bean as the bindy DataFormat handles all of the CSV to POJO transformation.

petitstream replied on Tue, 2009/03/31 - 1:17pm

Where can we get the beautifully IEP icons in riderAutoEips1_0.jpg ?

janstey replied on Tue, 2009/03/31 - 2:18pm in response to: petitstream

I used the Microsoft Visio scencil from the EIP books website

http://www.enterpriseintegrationpatterns.com/download/EIP_Visio_stencil.zip

 Of course, I added a lot of extra beautification too, which just takes time :)

petitstream replied on Tue, 2009/03/31 - 4:24pm in response to: janstey

Some weeks ago I tried this stencil for visio but your beautification made it look completely different. Great work !

davsclaus replied on Fri, 2009/04/03 - 8:12am

Bruce Snyder posted a blog entry about EIP pattern stencils for OmniGraffle

Link to blog entry: eip-patterns-in-omnigraffle

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.