Saturday, January 8, 2011

Grails, Camel and adding routes at runtime

Since it's been of some interest on the net to figure out how to add routes at runtime to Camel provided by the grails-routing plugin here's an example of how it might be implemented:
import org.apache.camel.builder.*
import org.apache.camel.model.*

class HomeController {
// 1
def camelContext

def index = {
// 2
println camelContext.routes.size()
// 3
if (camelContext.routes.size() == 0)
// 4
camelContext.addRouteDefinitions(
new MyRouteBuilder().routeDefinitions
)
render text: camelContext.routes.size().toString(), contentType: "text/plain"
}

def send = {
// 5
sendMessage("seda:input", "Hello, world! from dynamically added rotue")
render text: "sent", contentType: "text/plain"
}
}

// 6
class MyRouteBuilder extends RouteBuilder {
// need to override this to make RouteBuilder inheritance happy
void configure() { 
} 

// generate a new route
List getRouteDefinitions() {
[ from("seda:input").to("stream:out") ]
}
}

1 - Make the CamelContext instance available


the def camelContext makes the bean of class CamelContext available for us in this controller

2 - Debugging


For debugging purposes we're printing out to console how many routes are defined. The first time it will print 0 as there are no routes defined yet.

3 - Check if the route has already been added


We don't want to add the same route over and over again, right? :)

4 - Adding a new route


Using an instance of MyRouteBuilder get a new route definition and stuff it into the current set of routes.

5 - The send action


In this action we'll test the newly created route to see if it works

As you can see the only odd thing is that MyRouteBuilder doesn't do anything in configure since it's not required. We're inheriting from RouteBuilder only to have access to specific DSL methods (like the from one in this example).

To try it out make sure you have no other routes defined in your application, fire up the /home/index and note the number of routes printed out to console and displayed on the page. The first one should read 0 and the second one (since it's evaluated after the new route has been added) should read 1.
Then navigate to /home/send and observe in console a nice message received on seda:input and forwarded to stream:out

EDIT on 21 January 2012

There's a better way of doing this sort of things that works with all kind of routes:
def index = {
    if (camelContext.routes.size() == 0) {
        camelContext.addRoutes(new RouteBuilder() {
            @Override void configure() {
                from("seda:input")
                    .filter({ it.in.body.contains('from') })
                    .to("stream:out")
            }
        });
    }
    render text: camelContext.routes.size().toString(), contentType: "text/plain"
}


Hope this will help :)

5 comments:

Matthias Hryniszak said...

LOL, I didn't even realize... That's the 100th post on my blog :)

Andhuvan said...

great! it worked and helped me get started on camel!

steffan.eckhardt said...

Thanks, great post. Worked in a new grails project but not in existing project due, seemingly, to class/jar conflicts? Are there any dependency excludes I can add to BuildConfig.groovy for the following conflict?


[main] ERROR context.GrailsContextLoader - Error executing bootstraps: java.lang.LinkageError: loader constraint violation: when resolving field "DATETIME" the class loader (instance of org/codehaus/groovy/grails/cli/support/GrailsRootLoader) of the referring class, javax/xml/datatype/DatatypeConstants, and the class loader (instance of ) for the field's resolved type, javax/xml/namespace/QName, have different Class objects for that type
org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.LinkageError: loader constraint violation: when resolving field "DATETIME" the class loader (instance of org/codehaus/groovy/grails/cli/support/GrailsRootLoader) of the referring class, javax/xml/datatype/DatatypeConstants, and the class loader (instance of ) for the field's resolved type, javax/xml/namespace/QName, have different Class objects for that type

Matthias Hryniszak said...

Take a look at the plugin sources, specifically at the file "dependencies.groovy". There you'll see all the dependencies (including what's excluded).

Your issue sounds very exotic at first glance. Could you reproduce it in an example so that I can look at it?

steffan.eckhardt said...

We're using a lot of plugins (by way of BuildConfig.groovy)...the issue of transitive dependencies was the culprit. A colleague excluded 'stax-api' and 'commons-logging-api' to resolve the problem for now (plugin added via BuildConfig.groovy). Thanks again for the nice plugin.


plugins {
runtime (':routing:1.1.1') {
excludes "stax-api", "commons-logging-api"
}
}