Tuesday, May 24, 2011

Supersize Activiti with Mule ESB and Apache Camel

The Activiti Engine provides a powerful Java API that makes it very easy to for example deploy process definitions, implement custom logic and unit test your processes. When you want to connect remotely to the Activiti Engine there's a REST API to communicate with the process engine. But what if you want to start a new process instance by sending a JMS message, or invoke a web service from a BPMN 2.0 process?

By default, the BPMN 2.0 specification provides support for doing web service calls via a specific web service task. The Activiti Engine also provides support for a web service task, but it may be a bit cumbersome to implement due to the large amount of additional XML elements needed. And this task does only SOAP web service calls, so JMS messages etc.

Luckily the Activiti community comes to the rescue. In the next release of Activiti you'll see two interesting new Activiti modules, one for Mule ESB integration and one for Apache Camel integration. A big thumbs up to Esteban (Mule ESB contribution) and Maciek (Apache Camel contribution). But if you already want to play around, just checkout the Activiti source code at http://svn.codehaus.org/activiti/. Let's walk through some simple examples to get a good overview of these modules.

Mule ESB
Let's start with the Mule ESB module. With the Activiti Mule ESB integration we have two options of deployment. Deployment option one is to run both Mule ESB as well as the Activiti Engine in one Spring container and the communication between Mule ESB and the Activiti Engine uses the Activiti Java API. The second deployment option is to run Mule ESB standalone and let Mule ESB communicate with the Activiti Engine through the REST API. The only difference is the Activiti connector, which is defined in the Mule configuration.

Embedded configuration:

<mule xmlns="http://www.mulesoft.org/schema/mule/core"
    xmlns:activiti="http://www.mulesoft.org/schema/mule/activiti-embedded">

  <activiti:connector name="actServer"
    repositoryService-ref="repositoryService"
    runtimeService-ref="runtimeService"
    taskService-ref="taskService"
    historyService-ref="historyService" />

  <!-- Rest of the code shown in the next snippets -->

</mule>
Remote configuration:

<mule xmlns="http://www.mulesoft.org/schema/mule/core"
    xmlns:activiti="http://www.mulesoft.org/schema/mule/activiti-remote"  

  <activiti:connector name="actServer"
    activitiServerURL="http://localhost:8080/activiti-rest/service/"
    username="kermit"
    password="kermit" />

</mule>
The embedded configuration references Spring beans defined in the Activiti Engine Spring configuration for the Mule ESB to communicate with the Activiti Engine. The remote configuration defines the location of the REST API and defines the authentication parameters so the Mule ESB can use the Activiti REST API to talk with the Activiti Engine.

Okay that's nice, but now let's actually do something with this Activiti connector. Let's start a new process instance of a very simple BPMN 2.0 process from a JMS message. Now we have setup the Activiti connector infrastructure this is really easy.

<jms:activemq-connector name="jmsConnector" brokerURL="tcp://localhost:61616"/>

<flow name="MuleCreateProcess">
  <jms:inbound-endpoint queue="in.create" />
  <logger message="Received message #[payload]" level="INFO" />
  <activiti:create-process parametersExpression="#[payload]" />
  <jms:outbound-endpoint queue="out.create" />
</flow>

When a message is sent to the in.create queue, the message is first logged with the #[payload] expression. Then the Mule ESB Activiti module is invoked to create a new process instance. In this example, the JMS message is expected to be a MapMessage and the Map is retrieved to get the process parameters with the parametersExpression. To be able to start a process instance, we have to specify a processDefinitionKey property in the MapMessage. The additional properties specified in the MapMessage are all translated to process variables. Finally the process instance gets created and the newly create process instance object is sent to another JMS queue (out.create). This JMS ObjectMessage contains for example the process instance ID that can be used to retrieve things like process variables etc.

To test this example we need a bit of JMS plumbing code. So if you're interested in running the code example yourself please look at the Google code repository. In addition to creating new process instances, you can also set new process variables, signal a process instance etc. For a full overview you can read the Mule ESB Activiti documentation.

In addition to communicating with the Activiti Engine from the Mule ESB, it's also possible to send messages from a BPMN process to the Mule ESB. This opens up possibilities to send for example JMS messages, or advanced integration logic from a BPMN process. The current implementation is limited to the embedded mode for this piece of functionality, but there's no reason why this can't be expanded to also supporting the standalone or remote setup. Let's look at a simple process containing a Mule send task.

<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:activiti="http://activiti.org/bpmn"
  targetNamespace="http://www.bpmnwithactiviti.org">

  <process id="helloWorldMule">
    <startEvent id="theStart" />
    <sequenceFlow sourceRef="theStart" targetRef="sendMule" />
    <sendTask id="sendMule" activiti:type="mule">
      <extensionElements>
       <activiti:field name="endpointUrl">
         <activiti:string>vm://in
       </activiti:field>
       <activiti:field name="language">
         <activiti:string>juel
       </activiti:field>
       <activiti:field name="payloadExpression">
         <activiti:expression>${processVariable1}
       </activiti:field>
       <activiti:field name="resultVariable">
         <activiti:string>processVariable2
       </activiti:field>
      </extensionElements>
    </sendTask>
    <sequenceFlow sourceRef="sendMule" targetRef="theEnd" />
    <endEvent id="theEnd" />
  </process>
</definitions>

In this example we sent a message to the in queue of the JVM transport in Mule (which is a JVM messaging component). The message contains the value of the processVariable1 process variable and the response (we use a request-response exchange pattern in the Mule flow configuration) is written to a new process variable named processVariable2. The Mule flow configuration listing to the in JVM queue looks like this.

<flow name="MuleHello">
  <vm:inbound-endpoint path="in" exchange-pattern="request-response" />
  <logger message="Received message #[payload]" level="INFO" />
  <script:transformer>
    <script:script engine="groovy">return 'world'
  </script:transformer>
</flow>

The message is logged and a very simple Groovy script returns a response message with the value of 'world'. This shows how easy it is to send a message from a BPMN process to the Mule ESB and you can imagine that you can implement a lot of powerful integration logic that way. Let's take a look at the Apache Camel implementation.

Apache Camel
What a luxury we have in the Activiti project. Besides the powerful integration with Mule ESB, we have another great and widely used integration framework available to be used: Apache Camel. You understand that both Mule ESB as well as Apache Camel are capable of doing lots of similar integration logic. There are however also enough differences, and we'll highlight some of them by showing some examples.

One of the first differences is that the Camel integration always runs embedded with the Activiti Engine in the same Spring configuration. So you have to define a Spring XML configuration that includes an Activiti Engine config and a Camel context config. To be able to start new process instances from Camel the deployed process definition key is made available in the Camel context as you can see in the following snippet.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:camel="http://camel.apache.org/schema/spring">

  <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
    <property name="brokerURL" value="tcp://localhost:61616" />
  </bean>

  <bean id="camel" class="org.activiti.camel.CamelBehaviour">
    <constructor-arg index="0">
      <list>
        <bean class="org.activiti.camel.SimpleContextProvider">
          <constructor-arg index="0" value="helloCamelProcess" />
          <constructor-arg index="1" ref="camelProcess" />
        </bean>
      </list>
    </constructor-arg>
  </bean>

  <camelContext id="camelProcess" xmlns="http://camel.apache.org/schema/spring">
    <packageScan>
      <package>org.bpmnwithactiviti.blog.camel
    </packageScan>
  </camelContext>
</beans>

In this configuration we create a connection to an ActiveMQ broker we'll use later on. Then a SimpleContextProvider is defined that connects a deployed process definition on the Activiti Engine to a Camel context. You can define a list of SimpleContextProviders for each process definition that you want to connect to a Camel context. In the last part a Camel context is defined that scans for RouteBuilder classes in the configured package.

With the infrastructure in place we can now define integration logic in a Camel RouteBuilder class.

public class CamelHelloRoute extends RouteBuilder {

  @Override
  public void configure() throws Exception {

    from("activemq:in.create")
        .log(LoggingLevel.INFO, "Received message ${body}")
        .to("activiti:helloCamelProcess")
        .log(LoggingLevel.INFO, "Received message ${body}")
        .to("activemq:out.create");
    
    from("activiti:helloCamelProcess:serviceTask1")
        .log(LoggingLevel.INFO, "Received message on service task ${property.var1}")
        .setProperty("var2").constant("world")
        .setBody().properties();
  }
}

There are two so-called Camel routes defined in this RouteBuilder class. The first Camel route listens for new messages arriving at the in.create ActiveMQ queue. The message is logged and a new instance of the helloCamelProcess process definition is created and the process instance id is logged and sent to the out.create ActiveMQ queue. So now we can sent a JMS MapMessage to the in.create queue and all entries of the map are set as new process variables on the new process instance of the helloCamelProcess and the process instance id is sent to the out.create queue.

In the second route the Java service task logic of the helloCamelProcess is implemented (we'll see in a bit how this is implemented in BPMN 2.0 XML). First the process variable var1 is logged and then a new process variable var2 is created on the process instance. Of course we can implement far more complex integration logic here, like sending a JMS message or invoking a web service call.

Now let's look how the logic of the Java service task (serviceTask1) is delegated to this Camel route.

<definitions targetnamespace="http://www.bpmnwithactiviti.org"
    xmlns:activiti="http://activiti.org/bpmn"
    xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">

  <process id="helloCamelProcess">
    <startevent id="start">
    <sequenceflow sourceref="start" targetref="serviceTask1">
    <servicetask activiti:delegateexpression="${camel}" id="serviceTask1">
    <sequenceflow sourceref="serviceTask1" targetref="waitState">
    <receivetask id="waitState">
    <sequenceflow sourceref="waitState" targetref="theEnd">
    <endevent id="theEnd">
  </process>
</definitions>

As you can see the Camel route delegation is really simple. We only have to reference the CamelBehavior Spring bean (camel) we defined earlier. In the Google code repository you can find a unit test to run the full example.

Conclusion
With the availability of both integration modules there is a wide range of integration options that can be leveraged. The BPMN 2.0 specification already supported the web service task, the Activiti Engine added a powerful Java service task, but now the whole range of transports and enterprise integration patterns are available for you to be used. The only limitation is your imagination ;-).

4 comments:

  1. Hello, very nice post! I'm able to run the mule example from source code and Maven but what about running it from Java code? Since I'm almost new to the topic can you give me please some hints? Where should I start? Can I use the code from the test class? I tried but I have an error that says that the activiti process is not deployed.

    Thanks
    jovan

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hi,

    your post was very helpfull for me but I have one question. Is this possible to invoke activiti bpmn process from mule? Do you know how to do this?

    ReplyDelete
  4. Hi, i tried to use the activiti-remote but i got the next problem;

    org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate NamespaceHandler for namespace [http://www.mulesoft.org/schema/mule/activiti-remote]

    ReplyDelete