JBoss jBPM meets ESB
The combination of a process engine and an Enterprise Service Bus (ESB) is one interessting aspect of modern service oriented architectures (SOA). Therefor I wrote an article in the German Java Magazin about it. To have a practical showcase the integration is shown with a small example using JBoss jBPM, Mule ESB and the JBoss ESB. This Showcase I want to present in more depth here.
Hint: You should read the Java Magazin article first to get a better understanding of the context and the example. You can download it as PDF.
This blog entry only looks on the example with JBoss jBPM and the JBoss ESB. The example with Mule can be found on Markus blog: http://www.indoqa.com/de/people/markus.demolsky/blog/620.
Overview
The easy showcase implements the following example: Some event is generated and saved as a file (This may be an order, some incident, an alert, whatever). This file is picked up by the ESB and a new jbpm process is started. The process contains a human task, where somebody has to review the data of the event and decides, if that event can be ignored (e.g. a false alert) or if it has to be handled. In the latter case, the event is sent to an existing case management system via Web Service (could be Lotus Notes or something like that). The case management systems sends a JMS message as soon as the case is closed. This message is again picked up by the ESB and the right process instance is triggered (called “signaled” in jBPM). This is illustrated in the following picture.
The combination of jBPM and the JBoss ESB
The whole application
Our simple case management application
I developed a simple Mock-Casemanagementsystem consisting of one WebService which immidiately creates and delivers a JMS message to indicate the closing of the case. So this comes done to one easy class:
@Stateless @WebService(name="CaseManagementWS", targetNamespace="http://camunda.com/esbexample") public class CaseManagementService { @WebMethod public void createCase( @WebParam(name="content") String content, @WebParam(name="referenceId") long referenceId) { QueueConnection queueCon = null; try { Context ctx = new InitialContext(); QueueConnectionFactory qcf = (QueueConnectionFactory) ctx.lookup( "QueueConnectionFactory" ); queueCon = qcf.createQueueConnection(); QueueSession queueSession = queueCon.createQueueSession( false, Session.AUTO_ACKNOWLEDGE ); Queue queue = (Queue) ctx.lookup( "queue/CaseCompletionGatewayQueue" ); QueueSender sender = queueSession.createSender( queue ); Message msg = queueSession.createTextMessage("CASE CLOSED [" + content + "]"); msg.setLongProperty("referenceId", referenceId); sender.send( msg ); } catch (Exception ex) { throw new RuntimeException("got an exception in WebService", ex); } finally { // close the queue connection if( queueCon != null ) try { queueCon.close(); } catch (JMSException e) {} } } }
Deployed in the JBoss AS this will be exposed as Web Service and the WSDL is auto generated. Exactly what we need for our little Showcase.
The ESB configuration
The ESB configuration consists basically of (for details please read my JBoss ESB introduction article in the german Java Magazin):
- Services: Each service can fullfill a certain task. That is done by the internal Action-Pipeline of the service. To “invoke” a service, a Message has to be sent to it. For doing this, every Service listens to Providers.
- Action-Pipelines: Every Action in the Pipleline of a service gets the Message, can do whatever Java can do and returnes the Message, which is sent to the next Action in the Pipeline.
- Providers: A provider provides Messages. The normal case is to have Provider based on a JMS message queue, so every message delivered to the queue is pickued up by the service ,listening to the connected Provider. Another possible Provider, used in our example, is the FileSystemProvider which can pick up files from a directory.
Please note, that internally in the ESB messages are sent in an so called ESB-aware message format (XML or Java binary). So when an “external” message comes onto the bus (for example via JMS or FileSystem) it has to be wrapped in an ESB-aware format. This is done by the so calles Gateways. So every service listening to external messages need 2 listeners: one for the external message, which is the gateway, and one listening to the internal messages.
The whole ESB-configuration of the example can be downloaded here: jboss-esb.xml. I want to explain it step by step now, first, the overall structure:
<?xml version = "1.0" encoding = "UTF-8"?> <jbossesb parameterReloadSecs="5" > <providers> </providers> <services> </services> </jbossesb>
Now we need some providers:
- The FileSystem picking up files with events
- The Message Queue with the messages from the casemanagement application
- Three internal Queues for our three services
<providers> <!-- FileGateway --> <fs-provider name="firstFSprovider"> <fs-bus busid="EventFileProvider" > <fs-message-filter directory="dirs/input" input-suffix=".txt" work-suffix=".work" post-delete="false" post-directory="dirs/output" post-suffix=".done" error-delete="false" error-directory="dirs/error" error-suffix=".error"/> </fs-bus> </fs-provider> <jms-provider name="JBossMessaging" connection-factory="ConnectionFactory"> <jms-bus busid="FilePickupESBChannel"> <jms-message-filter dest-type="QUEUE" dest-name="FilePickupQueue"/> </jms-bus> <jms-bus busid="CreateCaseESBChannel"> <jms-message-filter dest-type="QUEUE" dest-name="CreateCaseQueue"/> </jms-bus> <jms-bus busid="CaseCompletionESBChannel"> <jms-message-filter dest-type="QUEUE" dest-name="CaseCompletionQueue"/> </jms-bus> <jms-bus busid="CaseCompletionGWChannel"> <jms-message-filter dest-type="QUEUE" dest-name="CaseCompletionGatewayQueue"/> </jms-bus> </jms-provider> </providers>
Obviously the used queues mustbe created, in the Showcase this is done via JBossMQ, the configuration file can be found here: jbmq-queue-service.xml.
The more interessting parts are the services now. As you can see in the overview picture, I configured three services:
- FilePickupService: This service picks up the file, puts the content in the ESB aware message and starts a process instance.
- CreateCaseServices: This services creates a case in the casemanagement system by calling a Web Service.This service is invoked from within the jbpm process.
- CaseCompletionService: This serviceis triggered by the JMS message from the casemanagement. It signals (triggers) the corresponding process instance. Correlation (find the right processs instance) is done in this case by passing the jbpm token id to the casemanagement application and back.
<services> <service category="EsbShowcase" name="FilePickupService"> <listeners> <fs-listener name="File-Gateway" busidref="EventFileProvider" maxThreads="1" is-gateway="true" schedule-frequency="10"/> <jms-listener name="ESB-Listener" busidref="FilePickupESBChannel" maxThreads="1"/> </listeners> <actions> <action name="readFile" class="com.camunda.esbex.esbaction.ReadFileContentAction"/> <action name="startProcessInstance" class="org.jboss.soa.esb.services.jbpm.actions.BpmProcessor"> <property name="command" value="StartProcessInstanceCommand" /> <property name="process-definition-name" value="EsbShowcase"/> <property name="object-paths"> <object-path esb="BODY_CONTENT" bpm="theBody" /> <object-path esb="content" bpm="caseContent" /> </property> </action> </actions> </service> <service category="EsbShowcase" name="CreateCaseService"> <listeners> <jms-listener name="ESB-Listener" busidref="CreateCaseESBChannel" maxThreads="1"/> </listeners> <actions> <action name="prepareWSParam" class="com.camunda.esbex.esbaction.PrepareWebserviceParameter" /> <action name="soapui-client-action" class="org.jboss.soa.esb.actions.soap.SOAPClient"> <property name="wsdl" value= "http://localhost:8080/casemanagement-casemanagement./CaseManagementService?wsdl"/> <property name="operation" value="createCase" /> <property name="responseAsOgnlMap" value="true" /> <property name="SOAPAction" value="createCase" /> </action> </actions> </service> <service category="EsbShowcase" name="CaseCompletionService"> <listeners> <jms-listener name="JMS-Gateway" busidref="CaseCompletionGWChannel" maxThreads="1" is-gateway="true" /> <jms-listener name="ESB-Listener" busidref="CaseCompletionESBChannel" maxThreads="1"/> </listeners> <actions> <action name="correlate" class="com.camunda.esbex.esbaction.CorrelateCaseToProcessAction" /> <action name="signalProcessInstance" class="org.jboss.soa.esb.services.jbpm.actions.BpmProcessor"> <property name="command" value="SignalCommand" /> <property name="object-paths"> <object-path esb="BODY_CONTENT" bpm="theBody" /> <object-path esb="body" bpm="theData" /> </property> </action> </actions> </service> </services>
The ESB Actions
As you can see in the configuration I used some own Actions, written by myself. Basically they have the task of reading and writing data from or to the appropriate place in the message. This could be done in many ways, for this showcase I used the most straightforward one.
An Action always has a public constructor which takes a “ConfigTree” parameter, which holds the inner configuration. The method called later always takes a Message as parameter and returns the Message (remember the Action Pipeline). The method name “process” is the default, if you use another name you have to specifiy it in the esb configuration.
Here our first action, which converts the content of the file from a byte array to a String:
public class ReadFileContentAction { public ReadFileContentAction(ConfigTree config) { } public Message process(Message m) { m.getBody().add( "content", new String( (byte[]) m.getBody().get() )); return m; } }
The next Action is used to create the OGNL map for the parameters of the Web Service call (don’t want to go into too much details here, hopefully you get the idea):
public Message process(Message m) { HashMap parameters = new HashMap(); parameters.put("createCase.referenceId", m.getBody().get(Constants.TOKEN_ID)); parameters.put("createCase.content", m.getBody().get("someVariableWithContent")); m.getBody().add( parameters ); return m; }
And our last action is used for the correlation of the closed case and the right process instance. As you can see in the last action, the token id of the started jbpm process is sent to the casemanagementsystem. And it is included in the resulting JMS message, so we can read it and write it to theright Propery of the JMS message. “The right property” is determied by its name, which can be taken from a constant provided by the ESB-jBPM-Integration module.
public Message process(Message m) { // the reference id was sent as property in the JMS message m.getBody().add( Constants.TOKEN_ID, m.getProperties().getProperty("referenceId") ); return m; }
The jBPM process
The process is really straightforward at the moment. To get around using some frontend to trigger the human tasks I didn’t create any task inside the TaskNode you see. If there is not Task in a TaskNode ut will not behave like a wait state and the process will just run through it. By doing so, no human can decide about which leaving transition to take, so it will be always the default one, which is “investigate” in our case. This is quite handy, so we can just start process instances, they will run to the “work on case” state and wait there. Later we can signal them again, so they will run from “work on case” to “end”.
In jBPM you can attach ActionHandler to a process. An ActionHandler is a small peace of Java-Code which will be executed when the process execution passes by a specific point in the process (called Events). In our case, we want to have such an ActionHandler when the execution arrives in the “work on case” state. Then we want to call the ESB service “CreateCaseService”. This is done via a nod-enter event on the “work on case” state, as you can see in the following, quite simple, processdefinition.xml:
<process-definition name="EsbShowcase"> <start-state name="start"> <transition to="review" /> </start-state> <task-node name="review"> <transition to="work on case" name="investigate" /> <transition to="end" name="ignore" /> </task-node> <state name="work on case"> <event type="node-enter"> <action name="call esb service" class="org.jboss.soa.esb.services.jbpm.actionhandlers.EsbActionHandler"> <esbCategoryName>EsbShowcase</esbCategoryName> <esbServiceName>CreateCaseService</esbServiceName> <jbpmToEsbVars> <mapping jbpm="caseContent" esb="someVariableWithContent" /> </jbpmToEsbVars> </action> </event> <transition to="end" name="case closed" /> </state> <end-state name="end" /> </process-definition>
The used ActionHandler is provided with the ESB and can be used out of the box.
Variables and data
Asyou can see in all the code above the data flow is like this:
- The file is read and stored as a byte array in the Message
- An Action converts the byte array into a string and stores it in the variable “content” of the ESB message
- “content” is copied into the process variable “caseContent” while starting the process instance
- When calling the ESB from jBPM, the process variable “caseContent” is copied into the ESB message variable “simeVariableWithContent”
- The data of “someVariableWithContent” is used as a parameter to the Web Service
I used different variable names all the time so you can see the mapping and believe that its working that way (and not somehow magic by some defaults).
Build process
In the current Showcase I used Ant to build everything. Basically we get two artefacts:
- esb archive (EsbJbpmShowcase.esb): This contains the ESB configuration and the ESB actions
- ear archive for the casemanagement (casemanagement.ear) : This contains the Web Service
The process itself is deployed via Ant directly to the jBPM database. I used the HSQL-Server in JBoss in this Showcase.
I used the JBoss ESB 4.2.1 GA, so you need it up and running for theshowcase. For the Webservice you need Java 6!
Please exchangethe default jbpm-ds.xml of the JBoss ESB to the one provided with the example, because it changes HSQL-DB to start a real server and listen to a port. This is necessary to deploy process definitions via ant.
After downloading and unzipping you just have to edit the build.properties and to link the right libraries (either copy the jboss client and jboss ESB libs to the lib folder or change the build file to refer to the correct places.
After that everything should work out of the box (it not please send me an email).
Download
Download the showcase as ZIP file: EsbJbpmExample.zip
Conclusion
The integration of jBPM and the ESB is technically very easy. In practice this makes sense in a lot of cases. By combining these technologies you don’t have to put business process logic in your ESB routing rules but also can keep the business process clean from integration stuff.
For some more thoughts on this, please refer to the Java Magazin article.
This is very good article, which is simplea nd straight forward for one who needs to work or use eai in BPM. Many thanks for the knowledge sharing Bernd Rücker.
Thanks,
Vasu
I just wanted to inform everybody: The shown example doesn’t run on the current ESB versions, since the SignalCommand isn’t any longer supported and was removed. This was done, because by just using a Signal you do not check if the process instance is still in the right state.
To do such kind of things you have to use the JbpmCallback, which operates on data, provided in the ESB message header. This information is written there by the EsbActionHandler if you call the ESB via jbpm.
if you have other requirements you are a bit out of luck here. This is, why I think the current correlation mechanism is too limited. But let’s see what imprrovements we can make here in future (so if you have use cases which doesn’t fit, please report them to the ESB forum so we can make up our minds for a good solution there).
Cheers and have fun
Bernd
Very good artical.It is very useful and fullfills the requirement completely.Thanx for writitng this artical.Keep on good work.
This concept is very good, but is it really possible to store content of file in process variables?
What happen if file size is to big?
Hi Niraw.
Two parts of an answer: Yes, it is possible. jBPM can handle even big byte arrays (files). But: It is not a good practice. Normally it makes more sense, to store the file somewhere (in you CMS, FTP or whatever) and just pass on an id/path/…
In most situations it is not advisable to store too much data in the process, better just store keys. But technically it would be possible.
Bernd.