Camel, Groovy and Beans

Last year I did an article on using Apache Camel as a switchboard for home monitoring – and a bit of a nod to IoT perhaps. One of the decisions I’d made was, as far as possible, I’d use Spring XML to configure rather than compile my solution, as I was interested in whether I could use Camel as a general tool. [more on Artisan use and tools here] So far, it’s worked out pretty well, until I wanted to upload some files via HTPP POST.

The plan.

Untitled

I’ve a Raspberry PI with a camera module, to take stop-motion images. There’s not much room on the PI, so it’s attached to the WiFi and uploads the photo after taking it, every minute or so. My Camel engine (2.12) is sitting on the server as a servlet inside a copy of Tomcat 7.

Now you might say that all I need is a bit of PHP or a servlet or similar to just dump the file. But, if I did that, not only would I get a ‘point-solution’ just for this need, I’m also reducing my choices as to what  can be done with the data afterwards. What if I want to send every 100th one to Flickr or SMS my phone if the PI stops sending? If I can pull the image (and it’s meta-data) into a Camel route, not only can I just save them, they’re ready for anything else I might want/think of to do with them later.

The technical problem is that the Camel Servlet component I’m using works fine for GET as you, well get, the parameters as Headers. If however you POST, as you need to to upload a file, then you get one long stream of data in the message body with everything mashed together as “Multipart Form-data” or RFC1867. What I need is a way to parse out the image file and headers myself and there’s even a Java library to do it called Commons File Upload. In the normal scheme of things you would create a Java Bean which would be called in the pipeline to do the work for you. But it seems a little against the configure-only theme, so, I need a way to write code, without writing “code”, i.e. script it in.

UntitledNote: This Bean doesn’t need to save the image, just move it into the body in a format where I can use modules like File to save it or route it somewhere else later.

Going Groovy

In my previous article, I’d already used a bit of Javascript in my route, just in-line with the XML. Now another Camel supported script language is Groovy. If you’ve not come across Groovy before, it’s worth a look, Java engine underneath but with the rough edges taken off and a rather nicer syntax. Happily, it also understands straight Java as well as cool Groovy constructs like closures, so you can simply drop code in and it works. You can use Groovy in-line in predicates, filters, expressions etc in routes and everything will be lovely, but it is also supported by Spring (and if you want, you can read about Dynamic Language Beans here) so you can create a Bean with it which seems just the ticket.
Dynamic, flexible and easily shared, drop-in Beans. That’s more like it.

I’m using the Camel Servlet-Tomcat example as the basis for my current engine. To use Groovy, you have to have the following added to your pom.xml :

<dependency>
 <groupId>org.apache.camel</groupId>
 <artifactId>camel-script</artifactId>
</dependency>

 <dependency>
 <groupId>org.apache.camel</groupId>
 <artifactId>camel-groovy</artifactId>
</dependency>

Build the war file and deploy it onto Tomcat. Try the built-in example to make sure it’s all working. Next add the following route in camel-config.xml (in WEB-INF/classes) :

<route>
    <from uri="servlet:///gmh" />
        <setBody>
        <groovy>
            def props = exchange.getIn().getHeaders()
            response ="&lt;doc&gt;"
            props.entrySet().each {
                response += "&lt;header key='" + it.getKey() + "'&gt;" + 
                it.getValue() +"&lt;/header&gt;"
            }
            response += "&lt;/doc&gt;"
            response
        </groovy>
        </setBody>
    <setHeader headerName="Content-Type">
    <simple>text/xml</simple>
    </setHeader>
</route>

Now try http://localhost:8080/[YOUR SERVLET]/camel/gme and you should get a nice XML list of headers and a warm feeling that Groovy is working.

Adding Beans

The problem with adding code in-line is that it not only becomes unwieldy pretty easily, you’re also limited in how you can put it together. This is where Beans come in. They not only allow you to hide/reuse even share groovy recipe code, but work at the Exchange level giving you far more options. To set up the code snippet above as a Bean is pretty easy. a) Create a folder in /classes called /groovy. b) In that create a file (or download) called gmh.groovy.
c) Into that file drop the following code:

import org.apache.camel.Exchange
class handler {
    public void gmh(Exchange exchange) {
        def props = exchange.getIn().getHeaders()
        def response = "<doc>"
        props.entrySet().each {
            response += "<header key='" + it.getKey() + "'>" + it.getValue() + "</header>"
        }
        response += "</doc>" 
        exchange.getIn().setBody(response)
    }
}

Notice that you now need to declare things that were previously hard-wired for in-line code, like the exchange, response etc. Also, there is now a class wrapped around the code which is itself now in a method plus I’ve had to set the body explicitly. But, you don’t need all that tedious XML escaping, and the whole Exchange is available to play with.

To wire gmh.groovy up into the route you need to add the following above the camelContext entry:

<lang:groovy id="gmh" script-source="classpath:groovy/gmh.groovy"/>

Note you may need to declare the “lang” namespace at the top of the file before it will work in which case add the following:

xmlns:lang="http://www.springframework.org/schema/lang"

Lastly, the route can be altered to get rid of the in-line code and use the bean instead:

<route>
 <from uri="servlet:///gmh" />
 <to uri="bean:gmh?method=gmh"/>
 <setHeader headerName="Content-Type"><simple>text/xml</simple></setHeader>
 </route>

Note it’s the bean:id that gets used in the uri, not the class name, – only the method name is in the route.

Now is you re-start the servlet and re-try the url, you should get the same answer as before. There’s of course a lot more you can do with this, there always is, but that’s the basics and it works. So, where does that leave me with my POST problem?

Getting the POST

Suffice to say, it’s not much more difficult, once Groovy is running, it’s just a bit more script. I used the Apache Commons File Upload libraries, and a bit of code (60 lines). Basically, it reads the encoded data in the body and does one of two things. a) If it’s a header, it creates a Exchange.Property called groovy.[header]. If it’s a file, it creates a property with the file name and turns the stream into byte array which gets put in the body.
That gave me a new postMe.groovy script which I wired into this route (Here’s the camel-config as well) :

<route id="POSTTest">
 <from uri="servlet:///posttest" />
 <to uri="bean:PostMe?method=getPost"/>
 <to uri="file:outbox"/>
 </route>

If you set this up and call curl with a file to upload like so:

curl -i -F filedata=@image.jpg http://localhost:8080/[SERVLET]/camel/posttest

Then you should see your file  in  the /outbox folder. You can also add headers like -F stuff=foo and throw in the previous bean to show them in the list as groovy.stuff=foo.

Last thoughts

Groovy is well, groovy and fixes my POST issue nicely. However, it’s proved that running Camel without doing some coding maybe optimistic. Having said that, once you have a Groovy-enabled .war deployed, it’s a great way to add code snippets into your route XML, and use it to create Beans to open out a much wider range of possibilities that can be shared. I can see it would be fairly easy to create a “toolkit” Camel with all these things in and perhaps SQL, SEDA (for queues) etc and a few of these Beans as well as ‘recipes’.

Advertisements