Apache Camel: Integration Nirvana
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- Apache Camel – http://camel.apache.org
- FUSE Mediation Router (based on Apache Camel) – http://fusesource.com/products/enterprise-camel
- Enterprise Integration Patterns – http://www.enterpriseintegrationpatterns.com
- Jon’s Blog – http://janstey.blogspot.com
- Camel in Action book - http://www.manning.com/ibsen
- Article source code - http://repo.fusesource.com/maven2/org/fusesource/examples/rider-auto-example/1.0/rider-auto-example-1.0.zip
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
- « first
- ‹ previous
- 1
- 2
- 3
| Attachment | Size |
|---|---|
| camelArch1.jpg | 47.49 KB |
| riderAutoEips1.jpg | 25.64 KB |
| DZone_Camel_Article_JonathanAnstey.pdf | 647.95 KB |
(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
James Strachan replied on Tue, 2009/03/24 - 4:58am
davsclaus replied on Tue, 2009/03/24 - 6:09am
janstey replied on Tue, 2009/03/24 - 7:04am
in response to: jstrachan
janstey replied on Tue, 2009/03/24 - 7:06am
in response to: davsclaus
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
janstey replied on Tue, 2009/03/24 - 12:08pm
in response to: an114162
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
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
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
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
davsclaus replied on Fri, 2009/04/03 - 8:12am
Link to blog entry: eip-patterns-in-omnigraffle