Spring integration in jBPM 4

From Tomek Bujok's IT Notepad

Jump to: navigation, search

I would like to show here how to integrate jBPM with Spring and Maven2 in order to create a robust BPM application stack. I have seen that there are many questions concerning that issue, so I would like to clarify and systematize it a bit.

Contents

Prerequisites / Technologies used

  • jBPM v.4.2
  • Spring v.2.5.6.SEC01
  • Maven 2.0.9
  • JDK 1.6.0_17

Maven Project Configuration [pom.xml]

First, we have to create a sample Maven project.

mvn archetype:create -DgroupId=info.bujok.tomek.jbpm.spring -DartifactId=jbpm-spring-integration

Our Maven project is ready to go. Now, delete App.java and AppTest.java files generated during project creation. Then, we have to edit pom.xml in order to add proper entries. The most important sections are the ones with jBPM and Spring dependencies. The whole pom.xml file should look like this:

<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>info.bujok.tomek.jbpm.spring</groupId>
	<artifactId>jbpm-spring</artifactId>
	<packaging>jar</packaging>
	<name>jBPM + Spring Evaluation Project</name>
	<version>1.0</version>
 
	<properties>
		<jbpm.version>4.2</jbpm.version>
		<spring.version>2.5.6.SEC01</spring.version>
 
		<hsqldb.version>1.8.0.10</hsqldb.version>
		<junit.version>4.7</junit.version>
		<log4j.version>1.2.15</log4j.version>
		<hibernate.core.version>3.3.1.GA</hibernate.core.version>
		<hibernate.entitymanager.version>3.4.0.GA</hibernate.entitymanager.version>
		<hibernate.annotations.version>3.4.0.GA</hibernate.annotations.version>
		<hibernate.validator.version>3.1.0.GA</hibernate.validator.version>
		<commons.lang.version>2.4</commons.lang.version>
		<commons.logging.version>1.1.1</commons.logging.version>
		<cglib.version>2.2</cglib.version>
	</properties>
 
	<dependencies>
 
		<!-- JBPM dependencies -->
		<dependency>
			<groupId>org.jbpm.jbpm4</groupId>
			<artifactId>jbpm-jpdl</artifactId>
			<version>${jbpm.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.core.version}</version>
			<exclusions>
				<exclusion>
					<groupId>xml-apis</groupId>
					<artifactId>xml-apis</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.transaction</groupId>
					<artifactId>jta</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
 
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-annotations</artifactId>
			<version>${hibernate.annotations.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.entitymanager.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>${hibernate.validator.version}</version>
		</dependency>
 
		<!-- DB dependencies -->
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>${hsqldb.version}</version>
		</dependency>
 
		<!-- SPRING dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring</artifactId>
			<version>${spring.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</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-orm</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>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-agent</artifactId>
			<version>${spring.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${spring.version}</version>
		</dependency>
 
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>${cglib.version}</version>
		</dependency>
 
		<!-- utilities -->
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>${commons.lang.version}</version>
		</dependency>
 
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>${commons.logging.version}</version>
			<type>jar</type>
			<exclusions>
				<exclusion>
					<groupId>javax.servlet</groupId>
					<artifactId>servlet-api</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
 
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
			<type>jar</type>
		</dependency>
 
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<type>jar</type>
		</dependency>
 
	</dependencies>
 
	<repositories>
		<repository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Repository for Maven</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
		<repository>
			<id>jboss-repository</id>
			<name>JBoss Repository</name>
			<url>http://repository.jboss.com/maven2/</url>
			<layout>default</layout>
		</repository>
	</repositories>	
</project>


Logging configuration [log4j.xml]

In order to trace the behavior of our system logging has to be configured. Config file should be located in the resources folder (src/main/resources or src/test resources). Here's a sample logging setup:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
	<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p | %-10t | %-15.15c{1} | %-30.30C %4L | %m%n" />
		</layout>
	</appender>
 
	<logger name="info.bujok.tomek">
		<level value="TRACE" />
		<appender-ref ref="CONSOLE" />
	</logger>
 
	<logger name="org.jbpm">
		<level value="INFO" />
		<appender-ref ref="CONSOLE" />
	</logger>
 
	<logger name="org.springframework">
		<level value="WARN" />
		<appender-ref ref="CONSOLE" />
	</logger>
 
	<logger name="org.hibernate">
		<level value="WARN" />
		<appender-ref ref="CONSOLE" />
	</logger>
 
</log4j:configuration>


jBPM Configuration [jbpm.cfg.xml]

Next, we have to create a Spring-aware jBPM configuration file. It should be also located in the resources folder. Content of the file with essential comments in-line is outlined below:

<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<jbpm-configuration xmlns="http://jbpm.org/xsd/cfg">
 
	<import resource="jbpm.jpdl.cfg.xml" />
	<import resource="jbpm.identity.cfg.xml" />
	<import resource="jbpm.jobexecutor.cfg.xml" />
 
	<process-engine-context>
		<repository-service />
		<repository-cache />
		<execution-service />
		<history-service />
		<management-service />
		<identity-service />
		<task-service />
 
		<command-service name="newTxRequiredCommandService">
			<retry-interceptor />
			<environment-interceptor policy="requiresNew" />
			<spring-transaction-interceptor />
		</command-service>
 
		<command-service name="txRequiredCommandService">
			<retry-interceptor />
			<environment-interceptor />
			<spring-transaction-interceptor />
		</command-service>
 
 
		<object class="org.jbpm.pvm.internal.id.DatabaseDbidGenerator">
			<field name="commandService">
				<ref object="txRequiredCommandService" />
			</field>
		</object>
 
		<object class="org.jbpm.pvm.internal.id.DatabaseIdComposer"
			init="eager">
		</object>
 
		<!-- Added spring as read-context -->
		<script-manager default-expression-language="juel"
			default-script-language="juel"
			read-contexts="execution, environment, process-engine, spring"
			write-context="">
			<script-language name="juel"
				factory="org.jbpm.pvm.internal.script.JuelScriptEngineFactory" />
		</script-manager>
 
		<authentication />
 
		<id-generator />
 
		<types resource="jbpm.variable.types.xml" />
		<address-resolver />
	</process-engine-context>
 
	<transaction-context>
		<repository-session />
		<db-session />
 
		<message-session />
		<timer-session />
		<history-session />
 
		<!--
			Need to set explicitly that we don't want jbpm to create sessions
		-->
		<hibernate-session current="true" close="false" />
	</transaction-context>
</jbpm-configuration>


Spring Configuration [applicationContext.xml]

To finish the configuration process we have to create a Spring application context. When it comes to the analyzed issue there are two crucial sections of the file that should be taken into account:

  • definition of the sessionFactory bean that contains a list of the jBPM hbm.xml files;
  • definition of the jbpmConfiguration bean which contains the name of the jbpm configuration file as a constructor parameter;

Get acquainted with the factory-method design pattern sicne jBPM uses it a lot. jbpmConfiguration bean is a factory of processEngine, which is a factory of all jBPM services (such as taskService, executionService, etc.)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
           http://www.springframework.org/schema/aop 
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location" value="classpath:applicationContext.properties" />
	</bean>
 
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
 
		<property name="mappingLocations">
			<list>
				<value>classpath:jbpm.execution.hbm.xml</value>
				<value>classpath:jbpm.repository.hbm.xml</value>
				<value>classpath:jbpm.task.hbm.xml</value>
				<value>classpath:jbpm.history.hbm.xml</value>
				<value>classpath:jbpm.identity.hbm.xml</value>
			</list>
		</property>
 
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
				<prop key="hibernate.query.substitutions">true=1 false=0</prop>
				<prop key="hibernate.bytecode.use_reflection_optimizer">false</prop>
				<prop key="hibernate.show_sql">false</prop>
				<prop key="hibernate.format_sql">false</prop>
				<prop key="hibernate.use_sql_comments">false</prop>
				<prop key="hibernate.hbm2ddl.auto">update</prop>
			</props>
		</property>
 
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
	</bean>
 
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
 
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
		<property name="url" value="${db.url}" />
		<property name="username" value="${db.username}" />
		<property name="password" value="${db.password}" />
	</bean>
 
	<bean id="jbpmConfiguration" class="org.jbpm.pvm.internal.cfg.SpringConfiguration">
		<constructor-arg value="jbpm.cfg.xml" />
	</bean>
 
	<bean id="processEngine" factory-bean="jbpmConfiguration"
		factory-method="buildProcessEngine">
	</bean>
 
	<bean id="executionService" factory-bean="processEngine"
		factory-method="getExecutionService">
	</bean>
 
	<bean id="repositoryService" factory-bean="processEngine"
		factory-method="getRepositoryService">
	</bean>
 
	<bean id="taskService" factory-bean="processEngine"
		factory-method="getTaskService">
	</bean>
 
	<bean id="historyService" factory-bean="processEngine"
		factory-method="getHistoryService">
	</bean>
 
	<bean id="identityService" factory-bean="processEngine"
		factory-method="getIdentityService">
	</bean>
 
	<bean id="managementService" factory-bean="processEngine"
		factory-method="getManagementService">
	</bean>
 
</beans>

As you have already noticed some of the configuration values are externalized to the properties file (applicationContext.properties) which looks like this:

db.url=jdbc:hsqldb:mem:.
db.username=sa
db.password=

Now, we are ready to create a sample test case!

Sample Test Case

Right here, we have a very simple test scenario, which:

  • defines and deploys a sample jPDL business process (sample process definition was taken from jBPM examples).
  • starts a sample process instance
  • executes all steps of the process - checking if the process flow is routed as expected.


package info.bujok.tomek.jbpm.spring;
 
import org.jbpm.api.Execution;
import org.jbpm.api.ProcessInstance;
import org.jbpm.test.AbstractTransactionalSpringJbpmTestCase;
 
/**
 * @author Tomasz Bujok (tom[dot]bujok[at]gmail[dot]com)
 *
 */
public class JbpmTestCase extends AbstractTransactionalSpringJbpmTestCase {
 
	private String processName;
 
	private String processDefinition;
 
	@Override
	protected String[] getConfigLocations() {
		return new String[] { "applicationContext.xml" };
	}
 
	@Override
	protected void onSetUpInTransaction() throws Exception {
		super.onSetUpInTransaction();
 
		processName = "StateSequence";
		processDefinition = 
			"<process name='StateSequence'>"+
			"  <start>"+
			"    <transition to='a'/>"+
			"  </start>"+
			"  <state name='a'>"+
			"    <transition to='b'/>"+
			"  </state>"+
			"  <state name='b'>"+
			"    <transition to='c'/>"+
			"  </state>"+
			"  <state name='c'/>"+
			"</process>";
 
		deployJpdlXmlString(processDefinition);
	}
 
	public void testWaitStatesSequence() {
		ProcessInstance processInstance = null;
		processInstance = executionService.startProcessInstanceByKey(processName);
 
		Execution execInA = processInstance.findActiveExecutionIn("a");
		assertNotNull(execInA);
 
		processInstance = executionService.signalExecutionById(execInA.getId());
		Execution execInB = processInstance.findActiveExecutionIn("b");
		assertNotNull(execInB);
 
		processInstance = executionService.signalExecutionById(execInB.getId());
		Execution execInC = processInstance.findActiveExecutionIn("c");
		assertNotNull(execInC);
	}
 
}

Result

Finally, the project structure looks like this:

└───jbpm-spring-integration
    │   pom.xml
    │
    └───src
        └───test
            ├───java
            │   └───info
            │       └───bujok
            │           └───tomek
            │               └───jbpm
            │                   └───spring
            │                           JbpmTestCase.java
            │
            └───resources
                    applicationContext.properties
                    applicationContext.xml
                    jbpm.cfg.xml
                    log4j.xml

If you run:

mvn test

in the main project folder you will see that everything is fine:

[...]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.547 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7 seconds
[INFO] Finished at: Tue Dec 15 11:18:32 CET 2009
[INFO] Final Memory: 8M/254M
[INFO] ------------------------------------------------------------------------

Download

Comming soon... I will publish the code on github and googlecode.

Summary

Few simple steps described above were enough to setup a complete jBPM + Spring application stack. Isn't that beautiful :) Hope this helps!

Personal tools