Saturday, December 4, 2010

Activiti Spring integration example

A few days ago Activiti 5.0 was released; an apache licensed clean, fast, rock-solid and easy to use business process management suite. In this post the powerful Spring integration that comes with Activiti is demonstrated with a small example. The Spring integration layer makes the Activiti engine available for use inside a Spring container and provides functionality to invoke Spring beans directly from service tasks and gives you all the Spring comfort with Activiti, like for example dependency injection and transaction support.

Let's work out an example now to show you how Spring's transaction support can be used together with Activiti and to demonstrate how you can use Spring beans in a BPMN 2.0 process. In the example the in-memory h2 database that is shipped with Activiti, is used to configure Activiti. We will declare a Spring bean in the definition of the BPMN2.XML to perform a service task and next to that another Spring bean, not used by the process itself, that will perform transactional functionality. Within the transaction the bean is using the Activiti API to start a process instance. Take a look at the code to see the bean with the transactional method we want to perform.

public class SomeBean {
private RuntimeService runtimeService;

@Transactional
public void doTransactionalStuff(boolean error){
runtimeService.startProcessInstanceByKey("springTransactionTest");
if(error){
throw new UnsupportedOperationException("Rollback needed!!");
}
}

public void setRuntimeService(RuntimeService runtimeService) {
this.runtimeService = runtimeService;
}
}


As we will see in a bit in the Spring configuration the RuntimeService instance attribute of SomeBean is injected by Spring. The method we are interested in is the doSomeTransactionalStuff method. It takes a boolean error parameter and based on its value it will throw a UnsupportedOperationException or move on after having started a process instance of the springTransactionTest process. The UnsupportedOperationException is a runtime exception that will force Spring to rollback the transaction when it is thrown, ensuring as well that the process instance will not be stored in the Activiti h2 database.
Take a look at the Spring configuration needed to run this example.

<beans>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource">
<bean class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
</property>
</bean>

<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<tx:annotation-driven transaction-manager="txManager"/>

<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="databaseType" value="h2" />
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="txManager" />
<property name="dbSchemaStrategy" value="create" />
<property name="deploymentResources"
value="classpath*:spring.test.bpmn20.xml" />
</bean>

<bean id="runtimeService" factory-bean="processEngine"
factory-method="getRuntimeService" />

<bean id="someBean" class="org.activitiinaction.SomeBean">
<property name="runtimeService" ref="runtimeService" />
</bean>

<bean id="someOtherBean" class="org.activitiinaction.SomeOtherBean" />

</beans>

You see the declaration of the in-memory h2 datasource, followed by the declaration of a transaction manager of type DataSourceTransactionManager that is tight to the defined datasource. Then we declare that the transaction manager is annotation driven and define the processEngine and runtimeService beans that will be used in the unit test and in SomeBean. Note that the deploymentResources property is used to point to the location of our example process. Finally the SomeBean is declared getting the runtimeService bean injected in its runtimeService attribute as well as SomeOtherBean, the implementation of the BPMN service task, that will be called by the process. In defining it we are using an Activiti expression.

SomeOtherBean is just a pojo with one simple method. There is nothing more to it, and it can be directly used in the process.

public class SomeOtherBean {

public void doSimpleStuff(){
System.out.println("In SomeOtherBean doingSimpleStuff");
}

}

The example process is straightforward. Besides the service task performed by SomeOtherBean, only writing some console output, it contains a single user task just for the reason we mentioned in earlier posts. In order to query the process engine database we need to put the process in a wait state. With only an automated task the process would simply end, so the user task keeps the process instance alive. The whole process declaration is shown below.

<definitions>
<process id="springTransactionTest">
<sequenceFlow id="sequenceflow1" sourceRef="start"
targetRef="simple" />
<serviceTask id="simple"
activiti:expression="#{someOtherBean.doSimpleStuff()}" />
<sequenceFlow id="sequenceflow2" sourceRef="simple"
targetRef="userTask" />
<userTask id="userTask" />
<sequenceFlow id="sequenceflow2" sourceRef="userTask"
targetRef="end" />
<endEvent id="end" name="End" />
</process>
</definitions>

Note how Activiti leverages Spring to use the SomeOtherBean instance declared as a Spring bean earlier on. Because we run the Activiti engine within the Spring container, we can directly reference Spring beans from a service task. In the service task doSimpleStuff() is called, in this case, to print a statement to the console.

In the code needed to test all this we will call upon a SomeBean instance to let it perform some transactional stuff. We call the bean twice. Once with a error value of false and the second call will have an error value of true. Take a look at the code.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-test-application-context.xml")
public class SpringTest {

@Autowired
private RuntimeService runtimeService;

@Autowired
private SomeBean someBean;

@Test
public void simpleProcessTest() {
try {
someBean.doTransactionalStuff(false);
someBean.doTransactionalStuff(true);
} catch (RuntimeException re) {
System.out.println("Catching RuntimeException for testing purposes..");
}
assertEquals(1, runtimeService.createProcessInstanceQuery().count());
}
}

As you can see, the unit test is really simple since we don’t have to create the Activiti engine ourselves. With the standard Spring annotations @RunWith and @ContextConfiguration, the Spring configuration we defined is used as part of this unit test. The @Autowired annotation let's the Spring container inject an instance of the RuntimeService in the test as well as a SomeBean instance.

When you run the test you will see that two process instances are started. When the SomeBean's doTransactionalStuff() is called with a true value for the error parameter though, SomeBean throws a RuntimeException. This forces Spring to rollback the running transaction and will not commit the process state to the Activiti database. The test makes sure that all this went right, so it checks that indeed only one process instance is returned as the result of the process instance query on the Activiti DB.

Next time I will post a small example about deploying Activiti in Apache Karaf to show how you can use Activiti in a OSGI container.

To be complete below follows the pom.xml if you want to create the example yourself:

<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>org.activitiinactioni</groupId>
<artifactId>Activiti Examples</artifactId>
<version>0.5-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BPMN 2.0 with Activiti - Examples</name>

<properties>
<activiti-version>5.0</activiti-version>
<spring-version>3.0.3.RELEASE</spring-version>
</properties>

<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti-version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti-version}</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.2.132</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
</dependency>
</dependencies>

<repositories>
<repository>
<id>Activiti</id>
<url>http://maven.alfresco.com/nexus/content/repositories/activiti</url>
</repository>
<repository>
<id>jboss-public-repository-group</id>
<name>JBoss Public Maven Repository Group</name>
<url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
</repository>
</repositories>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

13 comments:

  1. Hi there,

    You can achieve similar thing with Grails Activiti Plugin and more at http://code.google.com/p/grails-activiti-plugin/

    Regards,
    Chee Kin

    ReplyDelete
  2. Hi Ron,

    nice article.
    I m new to activiti and somehow this example is not easy to understand because i dont know where to put the xml files.

    can u help pls?

    thx

    regards

    daniel

    ReplyDelete
  3. Hi Daniel,

    Did you look at Activiti in Action book from Manning? That would be a good resource to start learning about Activiti. Which XML files are you referring to?

    Best regards,

    Tijs

    ReplyDelete
  4. Please provide the zip for more understanding.

    ReplyDelete
  5. Hi Tijs,

    are there artifats to start a maven activiti project from scratch ?
    Is there a Maven plugin or goal to build and deploy the ".bar" process definiton?

    Many thanks,
    Denis.

    ReplyDelete
  6. yes, a zip file would be nice...

    ReplyDelete
  7. A zip file would be nice.

    =)

    ReplyDelete
  8. This code is all part of the Activiti in Action book's source code available here https://code.google.com/p/activitiinaction/

    ReplyDelete
  9. Thanks for the valuable post .


    how to assign task for particular user in Activiti...?? In my application I have assigned task to USER by Activiti Designer , and when I am trying to access it By Taskservice . it was showing number of tasks assigned to USER is Zero . so could you please help me on how to Assign the task to user ,in activity ? and is there any alternate way for it ?


    Thanks


    ReplyDelete
  10. Hi Tijs Rademakers


    could you please provide any sample project which has Activiti integration with spring ....??



    Thanks

    ReplyDelete
  11. hi i need a example on mail task, if u don't mind will u publish in your bloack

    ReplyDelete
  12. hi i am new to activiti, please help me to run this program

    ReplyDelete
  13. hi
    how to implement a process with JPA extensions can you explain the process how to implement this one with an example

    ReplyDelete