gravitonian

Using the Camel task to invoke a Camel route

Blog Post created by gravitonian Employee on Oct 31, 2016

Table of Contents

 

Introduction

Apache Camel is a powerful integration framework that allows you to implement the Enterprise Integration Patterns (EAI). Camel provides all the plumbing for you so you can focus on the business logic of the integration.

 

In Alfresco Process Services (i.e. the Enterprise edition of Activiti) there is a Camel service task available in the BPMN Editor that will get you going with using Camel from an Activiti process. There is actually two different ways we can use Camel with Activiti. We can call Activiti from Camel, and we can call Camel from Activiti. We will look at both cases in this article by using the Camel task to both send messages and describe how you can start an Activiti process from a Camel route.

 

What you need to know though is that in Activiti Enterprise 1.5.0 the Camel support is not included like it is in the open source Activiti releases. What I mean by this is that there are no Camel related libraries available in the tomcat/webapps/activiti-app/WEB-INF/lib directory. The only Camel support is the Camel task in the BPMN Editor.

 

Prerequisites

This section takes you through how to set up an environment to be used when testing Activiti Enterprise with Apache Camel.

 

Install and Run Alfresco Process Services

Read this section in the Activiti Enterprise Getting started guide. And download, install, and start up the Activiti Enterprise server.

 

Add Apache Camel to Alfresco Process Services

As mentioned in the introduction, the libraries needed for the Activiti <-> Camel integration are not available in Activiti Enterprise, so we need to install them. More specifically, we need a Camel Integration Engine and Router running that we can send and receive messages via. However, we don’t need to download or run anything as we will include the Camel runtime as part of the Activiti Server .

 

To get to the Camel libraries that we need download the Apache Camel distribution from this page.

 

We are going to set up Camel routes that uses the file: endpoint so we need the following libraries copied into the Activiti runtime:

 

martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/Downloads/apache-camel-2.18.0/lib/camel-core-2.18.0.jar .

 

File endpoint:

 

martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/Downloads/apache-camel-2.18.0/lib/camel-stream-2.18.0.jar .

 

Note. for each Camel endpoint that you are using in your route configuration you need to also install the library (or libraries) supporting it. When you start the Activiti server it will tell you if some classes are not found, so you get a heads up before you start running any processes.

 

Add the Activiti Camel Lib

As mentioned in the introduction, the Activiti Camel library is not part of the Enterprise distribution so we need to grab it from a Community release. This is to be able to use the activiti: endpoint, or more specifically component, in a Camel route configuration. This is similar to how we added a library for the file endpoint. The Activiti Camel service task implementation (org.activiti.camel.impl.CamelBehaviorDefaultImpl) is also contained in this lib.

 

So you need to first download latest version of Activiti Community version 5.x from this page.

 

Then copy the following library from the Community distribution to the Activiti Enterprise installation:

 

martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/Downloads/activiti-camel-5.21.0.jar .

 

Implementing a process with a Camel task

The following section will go through how to implement a simple process that just has one Camel task that invokes a Camel route.

 

Define the Process Model

The process looks like this (see this section for how to define a new process):

 

The full process definition XML looks like this:

 

<?xml version='1.0' encoding='UTF-8'?>

<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef" xmlns:modeler="http://activiti.com/modeler" modeler:version="1.0en" modeler:exportDateTime="20161023132109074" modeler:modelId="2002" modeler:modelVersion="2" modeler:modelLastUpdated="1477211114281">

  <process id="testCamelTask" name="Test Camel Task" isExecutable="true">

    <documentation>Just testing a process with a Camel task</documentation>

   

    <startEvent id="startEvent1" name="Start">

      <extensionElements>

        <modeler:editor-resource-id><![CDATA[startEvent1]]></modeler:editor-resource-id>

      </extensionElements>

    </startEvent>

   

    <sequenceFlow id="sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D" sourceRef="startEvent1" targetRef="sendMsgToCamel">

      <extensionElements>

<modeler:editor-resource-id><![CDATA[sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D]]></modeler:editor-resource-id>

      </extensionElements>

    </sequenceFlow>

    <serviceTask id="sendMsgToCamel" name="Send a message to Apache Camel" activiti:type="camel">

      <extensionElements>

        <activiti:field name="camelContext">

          <activiti:string><![CDATA[camelContext]]></activiti:string>

        </activiti:field>

<modeler:editor-resource-id><![CDATA[sid-6C07BEED-A0AE-4C0D-8A18-AE5F8DE50C61]]></modeler:editor-resource-id>

      </extensionElements>

    </serviceTask>

    <sequenceFlow id="sid-55000554-41F4-4069-810F-07F264D639E6" sourceRef="sendMsgToCamel" targetRef="sid-19B79A92-E86E-4E51-B42C-376798EDDBA9">

      <extensionElements>

<modeler:editor-resource-id><![CDATA[sid-55000554-41F4-4069-810F-07F264D639E6]]></modeler:editor-resource-id>

      </extensionElements>

    </sequenceFlow>

    <endEvent id="sid-19B79A92-E86E-4E51-B42C-376798EDDBA9" name="End">

      <extensionElements>

<modeler:editor-resource-id><![CDATA[sid-19B79A92-E86E-4E51-B42C-376798EDDBA9]]></modeler:editor-resource-id>

      </extensionElements>

    </endEvent>

  </process>

  <bpmndi:BPMNDiagram id="BPMNDiagram_testCamelTask">

    <bpmndi:BPMNPlane bpmnElement="testCamelTask" id="BPMNPlane_testCamelTask">

      <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">

        <omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"/>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="sendMsgToCamel" id="BPMNShape_sendMsgToCamel">

        <omgdc:Bounds height="80.0" width="100.36219727999998" x="240.0" y="138.0"/>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="sid-19B79A92-E86E-4E51-B42C-376798EDDBA9" id="BPMNShape_sid-19B79A92-E86E-4E51-B42C-376798EDDBA9">

        <omgdc:Bounds height="28.0" width="28.0" x="450.0" y="164.0"/>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNEdge bpmnElement="sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D" id="BPMNEdge_sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D">

        <omgdi:waypoint x="130.0" y="178.0"/>

        <omgdi:waypoint x="240.0" y="178.0"/>

      </bpmndi:BPMNEdge>

      <bpmndi:BPMNEdge bpmnElement="sid-55000554-41F4-4069-810F-07F264D639E6" id="BPMNEdge_sid-55000554-41F4-4069-810F-07F264D639E6">

        <omgdi:waypoint x="340.36219728" y="178.0"/>

        <omgdi:waypoint x="449.1306593325" y="178.0"/>

      </bpmndi:BPMNEdge>

    </bpmndi:BPMNPlane>

  </bpmndi:BPMNDiagram>

</definitions>

 

The important stuff in the process definition is the process id (testCamelTask) and the Camel service task id (sendMsgToCamel) as these ids will be used later on when you define Camel routes starting with the activiti: endpoint. Note also that the Camel service task has activiti:type="camel", which will pinpoint the actual service class implementation. It is also worth mentioning the camelContext field on the Camel task, it is used to tell Activiti what bean id to look for that contains the Camel Spring context. So we need to match that id later on when configuration the Camel spring context.

 

So not much is actually needed in the form of process definition to try out the Camel support in Activiti. We however need to implement a number of Java classes and put them on the Activiti server’s classpath. And for this we need an extension project.

 

Create an Extension Project with the Camel config and routes

This section goes through how to generate an Activiti extension project and how to add the  necessary classes to it to support an Activiti -> Camel integration.

 

Create an Extension Project

To set up an Activiti JAR extension project is quite easy. We will assume that you have Java and Maven installed. At the command line do the following to verify that these tools are installed and configured:

 

martin@gravitonian:~$ mvn --version

Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T12:57:37+01:00)

Maven home: /home/martin/apps/apache-maven-3.3.3

Java version: 1.8.0_91, vendor: Oracle Corporation

Java home: /usr/lib/jvm/java-8-oracle/jre

Default locale: en_GB, platform encoding: UTF-8

OS name: "linux", version: "4.2.0-42-generic", arch: "amd64", family: "unix"

 

If there is a problem check out the official docs for how to set things up: installing Java, setting JAVA_HOME, installing Maven, setting MAVEN_OPTS.

 

Now, create a directory to hold your Activiti extension project, for example activiti-jar-camel. Then create a Maven pom.xml file in this directory with the following content:

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <groupId>com.activiti.extension</groupId>
   <artifactId>activiti-jar-camel</artifactId>
   <version>1.0-SNAPSHOT</version>
   <name>Activiti Jar Module with Camel integration</name>
   <description>Activiti JAR Module that produces a JAR file with Java extensions such as service task delegates.</description>
   <packaging>jar</packaging>

   <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

       <!-- Properties used in dependency declarations -->
       <activiti.groupId>com.activiti</activiti.groupId>
       <activiti.version>1.5.1</activiti.version>
       <activiti.engine.version>5.21.0.2</activiti.engine.version>

       <!-- Compile with Java 7, default is 5 -->
       <maven.compiler.source>1.7</maven.compiler.source>
       <maven.compiler.target>1.7</maven.compiler.target>
   </properties>

   <dependencies>
       <!-- The main Activiti Enterprise application dependency that brings in all
            needed classes to compile your customizations -->

       <dependency>
           <groupId>${activiti.groupId}</groupId>
           <artifactId>activiti-app-logic</artifactId>
           <version>${activiti.version}</version>
       </dependency>

       <!-- Testing -->
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.11</version>
           <scope>test</scope>
       </dependency>
   </dependencies>

   <build>
       <resources>
           <!-- Filter the resource files in this project and
                  do property substitutions -->

           <resource>
               <directory>src/main/resources</directory>
               <filtering>true</filtering>
           </resource>
       </resources>
       <testResources>
           <!-- Filter the test resource files in this project and
                do property substitutions -->

           <testResource>
               <directory>src/test/resources</directory>
               <filtering>true</filtering>
           </testResource>
       </testResources>
   </build>

   <!--
       Alfresco Maven Repositories
       -->

   <repositories>
       <!-- Activiti Enterprise Edition Artifacts,
            put username/pwd for server in settings.xml -->

       <repository>
           <id>activiti-private-repository</id>
           <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-enterprise-releases</url>
       </repository>
   </repositories>
</project>

 

Then create the following project directory structure:

 

src/activiti-jar-camel$ tree

.

├── pom.xml

└── src

    ├── main

    │   ├── java

    │   │   └── com

    │   │       └── activiti

    │   │           └── extension

    │   │               ├── bean

    │   │               ├── camel

    │   │               │   └── route

    │   │               └── conf

    │   │                   └── camel

    │   └── resources

    └── test

        └── java

            └── com

                └── activiti

                    └── extension

                        └── bean

 

The different packages that we use for Java classes are important as only some packages are scanned by Activiti, such as the com.activiti.extension.bean package, which is scanned for Spring beans implementing things like service tasks and task listeners. And the com.activiti.extension.conf package, which is scanned for new Spring context configurations. So it is good practice to use these packages as a starting point.

 

If you want to look at the source code for this article have a look at this GitHub Project.

 

Adding the Camel dependencies to the Project

For us to be able to use any Camel classes in our extension project we need to add a dependency on Camel. Open up the activiti-jar-camel/pom.xml file and add the following dependency:

 

<dependency>
   <groupId>${activiti.groupId}</groupId>
   <artifactId>activiti-camel</artifactId>
   <version>${activiti.engine.version}</version>
</dependency>

 

This will bring in all the needed Camel libraries.

 

Implement the Camel Spring Context configuration

By default the Activiti Engine looks for a camelContext bean in the Spring container. The camelContext bean defines the Camel routes that will be loaded by the Camel container and available to Activiti to send and receive messages through.

 

Activiti looks for Spring context configurations in the com.activiti.extension.conf package. So let’s add a CamelConfiguration bean as follows in this package:

 

package com.activiti.extension.conf.camel;
import com.activiti.extension.bean.SomeService;
import com.activiti.extension.camel.route.CamelTaskRouteBuilder;
import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.SimpleRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Configure Spring with a new Camel spring context.
*
* @author martin.bergljung@alfresco.com
*/

@Configuration
public class CamelConfiguration {
   @Bean(name = "camelContext")
   public CamelContext camel() throws Exception{
       SimpleRegistry registry = new SimpleRegistry();
       registry.put("someService", new SomeService());
       CamelContext camelContext = new DefaultCamelContext(registry);
       camelContext.addRoutes(new CamelTaskRouteBuilder());
       camelContext.start();
       return camelContext;
   }
}

 

The SimpleRegistry is used to register Spring beans that should be used in Camel routes by using the bean: endpoint, we will do that to show further examples of Camel integration.

 

The bean registry is passed into the CamelContext when you create it. And then you add the routes that should be active in the context. Note that different Camel Service tasks can use different Camel contexts with different route configurations.

 

There are two more classes that we need to implement, the CamelTaskRouteBuilder, which defines the Camel routes, and the SomeService bean, which is an example of a Spring bean used in a Camel route.

 

Note here that Activiti Enterprise uses Spring annotations to define Configuration, Beans, and for wiring beans. And scans for Spring configurations in the com.activiti.extension.conf package or subpackages. There is no XML files with Spring context definitions, and hence no <camelContext entity in Spring XML.

 

Implement the Camel Routes

The next thing we need to implement, and the interesting part, are the Camel routes that sets up how messages are routed between different endpoints/systems. Create the following class to do that:

 

package com.activiti.extension.camel.route;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;

public class CamelTaskRouteBuilder extends RouteBuilder {

   @Override
   public void configure() throws Exception {
       // Activiti endpoint
       // camel endpoint:processId:camelServiceTaskId?
       String fromActivitiEndPoint = "activiti:testCamelTask:sendMsgToCamel?copyCamelBodyToBody=true";

       // File endpoints
       String dirSource = "/home/martin/temp/";
       String dirTarget = dirSource + "target/";
       String fileName = "some.txt";
       String logMsg = "Testing Apache Camel route invocation from an Activiti Camel service task.";
       String logMsg2 = "Testing Apache Camel route invocation from a file.";
       String fromFileEndpoint = String.format("file://%s?fileName=%s&noop=true", dirSource, fileName);
       String toFileEndpoint = String.format("file://%s?fileName=%s", dirTarget, fileName);
       String toBeanEndpoint = "bean:someService?method=process";
       from(fromActivitiEndPoint).log(LoggingLevel.INFO, logMsg).to(toBeanEndpoint);
       from(fromFileEndpoint).log(LoggingLevel.INFO, logMsg2).to(toFileEndpoint);
   }
}

 

This class sets up two Camel routes, one that will be triggered by the Activiti Camel Service task:

 

from(fromActivitiEndPoint).log(LoggingLevel.INFO, logMsg).to(toBeanEndpoint);

 

and one that will be triggered via a file in a directory:

 

from(fromFileEndpoint).log(LoggingLevel.INFO, logMsg2).to(toFileEndpoint);

 

The Activiti Camel Task will trigger a route that writes a log message and then calls a method in the SomeService bean. We have not yet implemented this bean and this is the bean that was added to the Camel ServiceRegistry.

 

The interesting part here is how we set up the Activiti Camel endpoint:

 

activiti:testCamelTask:sendMsgToCamel

 

It starts with the Activiti Camel component/endpoint identifier (i.e. activiti:), then we specify the process id (testCamelTask), and then the Camel Service task id (sendMsgToCamel).

 

Implement the Service Bean

One of our Camel routes uses a bean call so we need to implement this Camel bean, which is not the same thing as a Spring bean. Here is how it looks like:

 

package com.activiti.extension.bean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SomeService {
   private static Logger logger = LoggerFactory.getLogger(SomeService.class);

   public String process() {
       logger.info("Doing some processing...");
       return "Finished processing!";
   }
}

 

This bean is not doing much more than writing a simple log message. But we should see this message when the Camel route is invoked via the Activiti Camel Service task.

 

This bean is defined in the com.activiti.extension.bean package, which means it could easily be converted to a Spring bean used by other service tasks in the process, the only thing we would need to do is add the @Component annotation. The com.activiti.extension.bean package is automatically scanned by Activiti for beans.

 

Build the Extension Project JAR

Standing in the project directory, execute the following command to build the project and create a JAR with all the necessary classes:

 

~/src/activiti-jar-camel$ mvn clean install

 

Run the Process with a Camel Task

This section shows (or give links) to how to run the process with the Camel task.

 

Create a file to trigger one of the routes

One of the routes is triggered via a file called some.txt located in a directory called /home/martin/temp. Create this directory and the file. Or change the filename and directory to something else in the CamelTaskRouteBuilder class.

 

Copy the Extension Project JAR into the Activiti Server and Restart

Before we can use the process definition with the Camel Task we need to install the extension JAR with the necessary classes:

 

martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/src/activiti-jar-camel/target/activiti-jar-camel-1.0-SNAPSHOT.jar .

 

Then restart as follows:

 

martin@gravitonian:/opt/activiti15$ /opt/activiti15/tomcat/bin/catalina.sh stop

martin@gravitonian:/opt/activiti15$ /opt/activiti15/h2/stop-h2.sh

martin@gravitonian:/opt/activiti15$ ./start-activiti.sh

 

When we restart the Activiti server it should log a message triggered via the file from endpoint:

 

02:18:41,069 [activiti-app-rest-Executor-1] INFO  com.activiti.service.runtime.integration.alfresco.AlfrescoOnPremiseTicketService  - The size of this cache is determined by the 'cache.alfresco-tickets.max.size' and 'cache.alfresco-tickets.max.age' property.

02:18:41,230 [Camel (camel-1) thread #0 - file:///home/martin/temp/] INFO  route2  - Testing Apache Camel route invocation from a file.

23-Oct-2016 14:18:43.841 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive /opt/activiti15/tomcat/webapps/activiti-app.war has finished in 40,410 ms

 

Running the Process with the Camel Task

For information on how to run a process in Activiti Enterprise have a look at this page. Basically, you need to embed your business process model in an Application before you can use it, which is described here.

 

When we run the process we should see the following log messages:

 

02:15:17,243 [http-nio-8080-exec-6] INFO  route1  - Testing Apache Camel route invocation from an Activiti Camel service task.

02:15:17,248 [http-nio-8080-exec-6] INFO  com.activiti.extension.bean.SomeService  - Doing some processing...

 

Start a Process from Camel route

It is possible to kick off an Activiti process from a Camel route. You just need to know the process id and then define the route something like this:

 

// Starting an Activiti process
from("direct:start")
.log(LoggingLevel.INFO, "Testing starting workflow from Camel route")
.to("activiti:testCamelTask");

 

In this case the process id is the same we used in this article (i.e. testCamelTask).

Outcomes