Wednesday, December 31, 2008

Synchronous JBI with Apache Camel and OpenESB

Introduction

The present tutorial shows how to expose a JBI InOut synchronous endpoint through CamelSE, the Apache Camel Service Engine of OpenESB.
OpenESB works with a collection of different Service Engines, the mainly known are JavaEE-SE and the BPEL-SE. Have a look at the Components Catalogue for more. Some useful SE are in the incubator phase, I'm especially interested in the POJO-SE and CamelSE because they expose a simple and clean programming model. I'm personally not the biggest fan of BPEL as I feel much more comfortable with programming by textual representations instead of the arrow-and-boxes visual flows of BPEL (much like I usually find more effective to write actual code instead of UML drawings, while I like the option to create drawings from working code). In this article I want to point out some very nice possibilities provided by alternatives and also underline that it is not mandatory to use BPEL inside OpenESB at all.

Unfortunately (IMHO) most efforts of the OpenESB community are still headed to the BPEL stuff and optimization of the related SE, while I think majority of developers are more interested in classical textual programming paradigms, first of all because of a much better productivity when implementing typical Enterprise Integration Patterns and Event-Driven SOA solutions.

Here I want to present how to expose a JBI endpoint from the CamelSE, as the sample service definition provided by the CamelSE wizard still can create a One-way operation only. In a future example I will show how to rewrite the Report Incident Camel tutorial within OpenESB, and I will show why I think OpenESB is probably the most effective environment to develop Apache Camel based EIP.

Apache Camel

Apache Camel is a Spring based Integration Framework which implements the Enterprise Integration Patterns with powerful Bean Integration. Camel lets you create the Enterprise Integration Patterns to implement routing and mediation rules in either a Java based Domain Specific Language (or Fluent API), via Spring based Xml Configuration files or via the Scala DSL. This means you get smart completion of routing rules in your IDE whether in your Java, Scala or XML editor. Apache Camel uses URIs so that it can easily work directly with any kind of Transport or messaging model such as HTTP, ActiveMQ, JMS, JBI, SCA, MINA or CXF Bus API together with working with pluggable Data Format options. Apache Camel is a small library which has minimal dependencies for easy embedding in any Java application.

CamelSE

Apache Camel JBI Service Engine (a.k.a CamelSE) is a JBI Component that can be used to run Apache Camel Application in a JBI platform such as OpenESB. The CamelSE also enables Camel Applications (via Camel endpoints) to do message exchange with the service providers and consumers deployed in other JBI Components such as BPEL SE, HTTP BC etc. by providing a Camel Component to the Camel framework that can create Camel Endpoints mapped to the JBI Service Endpoints (consumer or provider) in the CamelSE.

The Example

Preparation

Follow the standard CamelSE installation instructions. I tested this with Camel 1.5.0 and OpenESB nigthly build # 20081209 (based on Netbeans 6.1), but it should work with the latest GlassfishESB as well.

CamelSE Project

1. In Netbeans click "New Project", then select "Camel JBI Module" from "Service Oriented Architecture" folder:


2. Choose a project name (I went for "CamelInOut") and leave other options as default. Click finish button. The wizard creates the typical CamelSE project structure:


3. The default jbi2Camel.wsdl contains a one-way operation only, it is necessary to modify it to create a request-reply operation to implement our scenario.

So change the original jbi2camel.wsdl, replacing the one-way operation
<portType name="CamelInOut_interface">
<operation name="oneWay">
<input name="oneWayIn" message="tns:anyMsg"/>
</operation>
</portType>



With a request-reply
<portType name="CamelInOut_interface">
<operation name="exchange">
<input name="exchangeIn" message="tns:anyMsg"/>
<output name="exchangeOut" message="tns:anyMsg"/>
</operation>
</portType>



4. Edit the default AppRouteBuilder.java

package cameljbimodule1;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.spring.Main;

/**
 * A Camel Router
 * @author maurizio
 */
public class AppRouteBuilder extends RouteBuilder {

    /**
     * A main() so we can easily run these routing rules in our IDE
     */
    public static void main(String... args) {
        Main.main(args);
    }

    /**
     * Lets configure the Camel routing rules using Java code...
     */
    public void configure() {
// Use this route when receiving messages from jbi endpoint
// jbi uri format = "jbi://

        String jbiURI = "jbi:http://openesb.org/jbi2camel/CamelInOut/CamelInOut_service/jbi2camel_endpoint";

        System.out.println("@@@ jbiURI=" + jbiURI);

        from(jbiURI).multicast().to("seda:log").process(new Processor() {

            public void process(Exchange exchange) throws Exception {
                String outBody = "OK";
                exchange.getOut().setBody(outBody, String.class);
                System.out.println("@@@ return: " + outBody);
            }
        });

        from("seda:log").process(new Processor() {

            public void process(Exchange exchange) throws Exception {
                String inBody = exchange.getIn().getBody(String.class);
                System.out.println("@@@ received: " + inBody);
            }
        });
    }
}

The idea is to use the Camel JBI URI as the entry point for the service:
String jbiURI = "jbi:http://openesb.org/jbi2camel/CamelInOut/CamelInOut_service/jbi2camel_endpoint";
This is the default jbiURI string as created by the wizard.

Then add a Camel multicast() method to start a parallel flow of execution. There are two legs of the multicast: first is a to("seda:log"), then a Processor inline class which actually sends back the response as a JBI Exchange:

from(jbiURI).multicast().to("seda:log").process(new Processor() {
    public void process(Exchange exchange) throws Exception {
        String outBody = "OK";
        exchange.getOut().setBody(outBody, String.class);
        System.out.println("@@@ return: " + outBody);
    }
});
The seda: component provides asynchronous SEDA behavior so that messages are exchanged on a BlockingQueue and consumers are invoked in a separate thread to the producer.

Next the seda:log queue is read in a separate thread, so that the service response anf further processing are excute physically in parallel
from("seda:log").process(new Processor() {
    public void process(Exchange exchange) throws Exception {
        String inBody = exchange.getIn().getBody(String.class);
        System.out.println("@@@ received: " + inBody);
    }
});
5. Create a new Composite Application (CA), drag and drop the CamelInOut project into the CASA window, add a SOAP-BC, connect it to the JBI module then build and deploy:


6. Create and run a Test Case to see what is going on. As usual, right-click over the Test node of the CA to create a New Test Case. Your input.xml of this test could be something as follow:
<soapenv:Envelope
xsi:schemaLocation="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cam="http://openesb.org/jbi2camel/message/CamelInOut">
<soapenv:Body>
<cam:AnyMessage>Here is the input message</cam:AnyMessage>
</soapenv:Body>
</soapenv:Envelope>

The output.xml after executing the test, should be like this:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<cam:AnyMessage xmlns:cam="http://openesb.org/jbi2camel/message/CamelInOut"
xmlns:msgns="http://openesb.org/jbi2camel/CamelInOut">OK</cam:AnyMessage>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The flow is really executed in multiple threads, as you can verify by checking the server.log of your Glassfish domain:
[#|2008-12-31T11:56:29.012+0100|INFO|sun-appserver9.1|javax.enterprise.system.stream.out
|_ThreadID=31;_ThreadName=caCamelInOut-CamelInOut;|
@@@ jbiURI=jbi:http://openesb.org/jbi2camel/CamelInOut/CamelInOut_service/jbi2camel_endpoint|#]

....

[#|2008-12-31T11:57:11.307+0100|INFO|sun-appserver9.1|javax.enterprise.system.stream.out
|_ThreadID=35;_ThreadName=pool-5-thread-3;|@@@ return: OK|#]

[#|2008-12-31T11:57:11.309+0100|INFO|sun-appserver9.1|javax.enterprise.system.stream.out
|_ThreadID=33;_ThreadName=seda:log thread:2;|@@@ received: Here is the input message|#]

[#|2008-12-31T11:57:11.314+0100|INFO|sun-appserver9.1
|camel-jbi-se.org.openesb.components.camelse.CamelSEComponentLifeCycle
|_ThreadID=36;_ThreadName=pool-5-thread-4;| Message Exchange Provider received DONE : END of service invocation|#]

Conclusions

This short tutorial shows how easy it is to use Apache Camel together with OpenESB, allowing for a simple and very effective implementation of a routing and transformation ESB language. It also demonstrate how to glue JBI endpoints with Camel, exposing JBI request-reply synchronous services directly from CamelSE modules. In a second tutorial I will rewrite a classic Apache Camel example to show that using OpenESB it requires less coding and efforts.

References

- Apache Camel Tutorials
- Implementing Fuji integration scenario using Camel SE [Louis Polycarpou's blog]

No comments:

Post a Comment