A Groovy ride on Camel
Join the DZone community and get the full member experience.
Join For FreeApache Camel is a routing and mediation engine which implements the Enterprise Integration Patterns. But don't let the words Enterprise Integration scare you off. Camel is designed to be really light weight and has a small footprint. It can be reused anywhere, whether in a servlet, in a Web services stack, inside a full ESB or a standalone messaging application.
Camel makes it really simple to implement messaging application. So there are not many reasons why you could not use it in non-enterprise application. In fact, it is possible to use Camel as a tool, similar to the way you use scripting languages. For example, you could fire up the Camel Web Console and define a messaging application without write a single line of code.
This article is a getting-started type of tutorial. As you might have guessed, I'm going to use Groovy as the programming language. And the programs in this article are intend to be ran with Groovysh, the Groovy Shell. Reasons:
- Groovy is concise, expressive and has less noice than Java. All programs in this article are just a couple dozen lines long and should be real easy to follow along.
- Using Groovysh allows the reader to interact with the application.
I'm a Linux guy and am comfortable with VIM and working in command line. So bare with me.
Putting the pieces together
First, I'm going to write a simple program to make sure I'm able to talk to Camel in Groovy. I'm not using an IDE like Eclipse, nor creating a project, nor going to use any build tools like Maven. Any text editor will be sufficient. Save the following code to a file named CamelDemo.groovy (source download).
import groovy.grape.Grape
Grape.grab(group:"org.apache.camel", module:"camel-core", version:"2.0.0")
class MyRouteBuilder extends org.apache.camel.builder.RouteBuilder {
void configure() {
from("direct://foo").to("mock://result")
}
}
mrb = new MyRouteBuilder()
ctx = new org.apache.camel.impl.DefaultCamelContext()
ctx.addRoutes mrb
ctx.start()
p = ctx.createProducerTemplate()
p.sendBody "direct:foo", "Camel Ride for beginner"
e = ctx.getEndpoint("mock://result")
ex = e.exchanges.first()
println "INFO> ${ex}"
This little program does a couple things:
- Imports the camel-core jar using Grape.grab()
- Defines a our custom RouteBuilder, which defines a simple route between a direct:foo and a mock:result enpoints.
- Instantiates the CamelContext, adds our custom RouteBuilder to it and starts Camel by ctx.start().
- Tests the route by sending a message exchange using the producerTemplate obtained from the CamelContext
- Lookups the mock:result endpoint (ctx.getEndpoint("mock:result")) and dislays the first Exchange, which should contain the message we just sent.
Now start the groovysh in a command window and load the program:
$ groovysh
groovy:000> load CamelDemo.groovy
you should see a bunch of output and then the output from the script:
INFO> Exchange[Message: Camel Ride for beginner]
===> null
groovy:000>
At this point, you can interact with the program via groovysh. For example the following shows a few things you can do.
groovy:000> ctx.routes
===> [EventDrivenConsumerRoute[Endpoint[seda://foo] -> UnitOfWork(Channel[sendTo(Endpoint[mock://result])])]]
groovy:000> ctx.components
===> {mock=org.apache.camel.component.mock.MockComponent@14f2bd7, seda=org.apache.camel.component.seda.SedaComponent@c759f5}
groovy:000> ctx.endpoints
===> [Endpoint[seda://foo], Endpoint[mock://result]]
groovy:000> ctx.endpoints[1].exchanges
===> [Exchange[Message: Camel Ride for beginner]]
groovy:000> ctx.endpoints[1].exchanges[0].in.body
===> Camel Ride for beginner
groovy:000> p.sendBody("seda:foo", "Camel Kicking")
===> null
groovy:000> e.exchanges
===> [Exchange[Message: Camel Ride for beginner], Exchange[Message: Camel Kicking]]
This is it for our first Groovy/Camel program. For the curious, you can actually modify the program and reload it without terminating and restarting groovysh.
Camel Stock Quote
This is a simple stock quote application. Initially, I planed to walk you thru the development steps, from adding a simple bean as a Processor to transforming it to a Multi-Channel, Multi-Data-Format service application.
But after I've finished developing the program, it turns out that it is too simple to justify for such elaboration. To save your time and mine, I'm just going to show you the final version right here. Take a look at it, and if you can understand what it does, then may be you should skip the rest of this article :)
Save the following code to a file named StockQuote.groovy (source download).
import groovy.grape.Grape
Grape.grab(group:"org.apache.camel", module:"camel-core", version:"2.0.0")
Grape.grab(group:"org.apache.camel", module:"camel-jetty", version:"2.0.0")
Grape.grab(group:"org.apache.camel", module:"camel-freemarker", version:"2.0.0")
class QuoteServiceBean {
public String usStock(String symbol) {
"${symbol}: 123.50 US\$"
}
public String hkStock(String symbol) {
"${symbol}: 90.55 HK\$"
}
}
class MyRouteBuilder extends org.apache.camel.builder.RouteBuilder {
void configure() {
from("direct://quote").choice()
.when(body().contains(".HK")).bean(QuoteServiceBean.class, "hkStock")
.otherwise().bean(QuoteServiceBean.class, "usStock")
.end().to("mock://result")
from("direct://xmlquote").transform().xpath("//quote/@symbol", String.class).to("direct://quote")
//curl -H "Content-Type: text/xml" http://localhost:8080/quote?symbol=IBM
from('jetty:http://localhost:8080/quote').transform()
.simple('<quote symbol="${header.symbol}"></quote>').to("direct://xmlquote").choice()
.when(header("Content-Type").isEqualTo("text/xml")).to("freemarker:xmlquote.ftl")
.otherwise().to("freemarker:htmlquote.ftl")
.end()
}
}
ctx = new org.apache.camel.impl.DefaultCamelContext()
mrb = new MyRouteBuilder()
ctx.addRoutes mrb
ctx.start()
p = ctx.createProducerTemplate()
//p.sendBody("direct:quote", "00005.HK")
//p.sendBody("direct:xmlquote", "<quote symbol='IBM'/>")
//p.sendBody("direct:xmlquote", "<quote symbol='00005.HK'/>")
e = ctx.getEndpoint("mock://result")
//e.exchanges.each { ex ->
// println "INFO> in.body='${ex.in.body}'"
//}
OK, you are still here.
It is assumed that:
- We have two market data providers, one for U.S. market and the other for Hong Kong market.
- An existing QuoteServiceBean class has been implemented as a POJO. It has two methods, usStock() and hkStock(). It is part of a legacy system, it works great, it hides the underlying details of interacting with the data providers. No one understands it and no one dares to modify it.
- We would like to use the existing QuoteServiceBean to provide a stock quote service that can be consume easily. i.e. Multi-Channel and Multi-Data-Format.
Content Based Router and Message Translator
from("direct://quote").choice()
.when(body().contains(".HK")).bean(QuoteServiceBean.class, "hkStock")
.otherwise().bean(QuoteServiceBean.class, "usStock")
.end().to("mock://result")
The first route (start at line 17) represented by the direct:quote endpoint. It routes the message according to the content of the body of the exchange, which it's assumed to contain the stock symbol. When the body of the exchange contains the string ".HK" the hkStock(String symbol) of QuoteServiceBean is called, otherwise the usStock(String symbol) of QuoteServiceBean is called. Notice that the route DSL almost reads like plain English!
Let us try it out. First start groovysh, load the program and send two messages to the direct:quote endpoint:
jack@localhost tmp]$ groovysh
Groovy Shell (1.6.6, JVM: 1.6.0_11)
groovy:000> load StockQuote.groovy
..............
groovy:000> p.sendBody("direct:quote", "00001.HK")
===> null
groovy:000> e.exchanges.last()
===> Exchange[Message: 00001.HK: 90.55 HK$]
groovy:000> p.sendBody("direct:quote", "SUNW")
===> null
groovy:000> e.exchanges.last()
===> Exchange[Message: SUNW: 123.50 US$]
groovy:000>
That is it, our simple content-based router successfully routes request to the corresponding processor methods.
XML Quote Request, message Transform
from("direct://xmlquote").transform().xpath("//quote/@symbol", String.class).to("direct://quote")
This next route simply accepts requests in XML, transforms the request and chains it to direct://quote. With this, we've added the capability to accept requests in XML format!
We are using XPath here to expression our transform. Check out the hosts of Expression Langauges supported by Camel.
Let us try it out:
groovy:000> p.sendBody("direct:xmlquote", "<quote symbol='GOOG'/>")
===> null
groovy:000> e.exchanges.last()
===> Exchange[Message: GOOG: 123.50 US$]
groovy:000>
Multi-Channel, Multi-Data-Format Provisioning
Grape.grab(group:"org.apache.camel", module:"camel-jetty", version:"2.0.0")
Grape.grab(group:"org.apache.camel", module:"camel-freemarker", version:"2.0.0")
// ........... lines removed for brevity .............
from('jetty:http://localhost:8080/quote').transform()
.simple('<quote symbol="${header.symbol}"></quote>').to("direct://xmlquote").choice()
.when(header("Content-Type").isEqualTo("text/xml")).to("freemarker:xmlquote.ftl")
.otherwise().to("freemarker:htmlquote.ftl")
.end()
Here we use the camel-jetty to expose an endpoint jetty:http://localhost:8080/quote to our quote service. Note that camel-jetty is not part of camel-core. That is why we have to grab it into our program.
The HTTP request is translate into XML using simple expression. Note that the camel-jetty has kindly extracted the request parameters as well as the HTTP header and placed them on the Message header. So the request parameter symbol is access as ${header.symbol} in the expression.
Next we simply chain the message exchange to the direct:xmlquote endpoint.
The result from direct:xmlquote went thru another translation, which depends on the content-type of the orginating HTTP request. Here, I make use of the camel-freemarker to generate the desire output. So we need to create the two Freemarker templates:
htmlquote.ftl<html>
<head>
</head>
<body>
<em>${body}</em>
</body>
</html>
xmlquote.ftl<quote-result symbol='${headers.symbol}'>
${body}
</quote-result>
So let us see it in action, I'm going to use curl to make HTTP requests. Do this on another command window:
[jack@localhost tmp]$ curl http://localhost:8080/quote?symbol=IBM
<html>
<head>
</head>
<body>
<em>IBM: 123.50 US$</em>
</body>
</html>
[jack@localhost tmp]$ curl http://localhost:8080/quote?symbol=00001.HK
<html>
<head>
</head>
<body>
<em>00001.HK: 90.55 HK$</em>
</body>
</html>
[jack@localhost tmp]$
And to request XML content:
[jack@localhost tmp]$ curl -H "Content-Type: text/xml" http://localhost:8080/quote?symbol=IBM
<quote-result symbol='IBM'>
IBM: 123.50 US$
</quote-result>
[jack@localhost tmp]$
That's about it for our multi-channel/multi-data-format ser vice provision.
Summary
In this tutorial, I hoped to illustrate how Camel supports message passing paradigm style of application development. Camel provides all sort of components to help you build processing pipelines. All you need is to implement your business logic as simple POJOs and let Camel handle all the translating, routing, filtering, spliting and forwarding for you.
Not shown in this tutorial is how to consume external resources and services from within a route. No sweat, it is just as easy.
Camel integrates nicely with Spring as well as Guice, but works nicely on its own. It won't be in your way if you don't need DI support in your application. As they say: Keep the simple easy.
Camel works nicely in a JBI environment like ServiceMix and OpenESB. Camel is OSGI-ready and tracks newly deployed bundles for Route definitions at runtime. So you can gear it all to way up to be part of an enterprise SOA infrastructure.
Disclaimer, I'm not an experienced Camel user and still learning. Thank you for staying up with me.
Opinions expressed by DZone contributors are their own.
Comments