Friday, February 13, 2009

Combining ApacheCamel+BSF to make JBoss ESB polyglot

Some time ago I published here one of the ways I built for integrating services such as Apache Camel into JBoss ESB.

Hopefully Tom Fennelly (JBoss ESB CoreDev) also published an awesome documentation in how create new Listeners into JBoss via Schedulers, as I had done, in addition showing how do that using Groovy, which in my opinion is much better, safer and easier, I will show you the new implementation here right now.

When do I need new Listener into JBoss ESB?


Many many times I faced situations where some customers have unpredictable scenarios related with integration, that's why I liked Apache Camel since the first moment I had contact with! You can find out a clear and easy programming model(using DSLs), and a very nice set of components for integrating even with some providers not available in JBoss, like JBI(ServiceMix), Esper(CEP/ESP) etc.


Groovy in Action into ESB

Once the events in Camel happens into an specific RouteBuilder object, I just want to make it available as a "Service", so once JBoss ESB is running, the Camel is ready to answer any event.

To make it possible, I just created a classes called ApacheCamelListener, where I must fill some life-cycle methods, in order to ensure that everything will be executed properly in runtime, see the class: ApacheCamelListener.java:



package org.jboss.soa.esb.integration.apache.camel;

import java.io.File;
import java.util.logging.Logger;

import org.apache.bsf.BSFEngine;
import org.apache.bsf.BSFManager;
import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.message.ActionProcessingPipeline;
import org.jboss.soa.esb.util.FileUtil;

public class ApacheCamelListener {

private ActionProcessingPipeline pipeline;

protected CamelContext context = new DefaultCamelContext();

protected Logger log;

protected boolean started = false;

public ApacheCamelListener() {
log = Logger.getLogger(ApacheCamelListener.class.getName());
}

public void start(ConfigTree config) throws ConfigurationException {
// Create and initialize the pipeline..
pipeline = new ActionProcessingPipeline(config);
pipeline.initialise();
log.info("Initilizing ApacheCamel into JBoss Esb Server ....");
if (!started) {
CamelContext context = new DefaultCamelContext();

try {

log.info("...Adding Routes");

File scriptsDir = new File((config.getAttribute("scripts-folder")));

File[] scripts = scriptsDir.listFiles();

if (null != scripts && scripts.length>0) {

String theScript = null;

JBossEsbRoute router = null;

BSFManager manager = new BSFManager();

BSFEngine bsfEngine = manager.loadScriptingEngine(config.getAttribute("script-language"));

for (File file : scripts) {

theScript = FileUtil.readTextFile(file);

router = (JBossEsbRoute) bsfEngine.eval(config.getAttribute("script-language"), 0, 0, theScript);

log.info(router.toString());

context.addRoutes(new ScriptingRoute(router));

}

}

} catch (Exception e) {

e.printStackTrace();

}

try {
context.start();
started = true;
log.info("Camel is ready and waiting events");

} catch (Exception e) {

e.printStackTrace();
}
}

}

public void stop() {
try {

started = false;

try {
context.stop();

} catch (Exception e) {

log.info("Error trying close Camel Context: " + e.getMessage());
}

} finally {
if (pipeline != null) {
pipeline.destroy();
}
}
}

}

Time for Innovating : Putting Ruby, Groovy and other scripting language to dispatch messages to existing Services

You can use JBoss jBPM Actions to call deployed Services, based in the service name, service category and the variables, so what I did is basically is allow users via Camel listen many others components and call an existing Service, the following image can describe my idea:


Basically when I configure my ApacheCamelListener, I can configure the "scripts-folder" and "script-language" properties for my listener, these propertis basically works to tell where in the filesystem this listener will looking for "other dinamic listeners", and then you can tell the orign of the event, and when some message/even happens in the provider, it will be forwarded via ServiceInvoker converting a Camel Message to an ESB Message Aware. See the Listener configuration:




Or you can simply see the jboss-esb.xml configuration here:



Now, I can use a Ruby Script to invoke some service from my ESB Server, and using a IRC room to interact with JBoss ESB.


require 'java'
include_class 'org.jboss.soa.esb.integration.apache.camel.JBossEsbRoute'
route = JBossEsbRoute.new
route.from = 'irc:localhost:667#room1'
route.to = 'irc:localhost:667#room2'
route.serviceCategory= 'Transformadores'
route.serviceName = 'TransformaChamadaXMLparaISO'
return route




In Apache Camel, I must pass a org.apache.camel.builder.RouteBuilder, to say how interact with the message arrived in the configured protocol. To do that, I extended this class for a JBossEsbRoute, as you can see in the following code-listing:



package org.jboss.soa.esb.integration.apache.camel;

public class JBossEsbRoute {

private String serviceName;
private String serviceCategory;
private String from;
private String to;


public JBossEsbRoute() {
// TODO Auto-generated constructor stub
}

@Override
public String toString() {
return String.format("This ServiceInvoker will listen events from %s " +
"and will forward to Service %s from category %s and route to %s",
this.from,this.serviceName,this.serviceCategory, this.to);
}

public JBossEsbRoute(String serviceName, String category, String from, String to) {

this.serviceName = serviceName;
this.serviceCategory = category;
this.from = from;
this.to = to;
}


Here is my extension for Camel understands any script written in PHP, ruby or perl etc, and make it integrated with JBoss ESB:



package org.jboss.soa.esb.integration.apache.camel;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;

public class ScriptingRoute extends org.apache.camel.builder.RouteBuilder {

protected JBossEsbRoute route;

public ScriptingRoute() {
// TODO Auto-generated constructor stub
}

public ScriptingRoute(JBossEsbRoute r) {

this.route = r;

}


@Override
public void configure() throws Exception {

from(route.getFrom()).process(
new Processor() {
public void process(Exchange e) {

Object message = e.getIn().getBody();

System.out.println("#########" +
e.getContext().getExchangeConverter().convertTo(String.class, e));


System.out.println("Received event: " + message);


}
}).to(route.getTo());


}



The Ruby Script, basically creates a new Instance of JBossEsbRoute, which I can use to register a new Event Listener as well as a Invoker for an existing Services hosted into JBoss ESB. The following code-listing will allow you figure out how it is done:




String theScript = null;

JBossEsbRoute router = null;

BSFManager manager = new BSFManager();

BSFEngine bsfEngine = manager.loadScriptingEngine(config.getAttribute("script-language"));

for (File file : scripts) {

theScript = FileUtil.readTextFile(file);

router = (JBossEsbRoute) bsfEngine.eval(config.getAttribute("script-language"), 0, 0, theScript);

log.info(router.toString());

context.addRoutes(new ScriptingRoute(router));

}

}


Now, any language supported by Bean Scripting Framework can be used to invoke services into JBoss ESB, using Apache Camel for Events notifying and even forwarding after get processed into JBoss ESB pipelines.

I will be updating the sources and publishing in GitHub, if you are interested in that idea, reach me via email into edgarsilva (using) gmail.com.

Hope you enjoy!

ps- JBoss Esb 4.5 with Embedded Console is really great!

No comments: