JBoss.orgCommunity Documentation
Apache Maven is used by jBPM for two main purposes:
as deployment units that gets installed into runtime environment for execution
as dependency management tool for building systems based on jBPM - embedding jBPM into application
Since version 6, jBPM provides simplified and complete deployment mechanism that is based entirely on Apache Maven artifacts. These artifacts also known as kjars are simple JAR files that include a descriptor for KIE system to produce KieBase and KieSession. Descriptor of the kjar is represented as XML file named kmodule.xml and it can be:
empty to apply all defaults
custom configuration of KieBase and KieSession
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.drools.org/xsd/kmodule">
</kmodule>
Empty kmodule.xml that provides all defaults for the kjar:
single default KieBase that
contains all assets from all packages
event processing mode set to - cloud
equality behaviour set to - identity
declarative agenda is disabled
scope set to - ApplicationScope - valid for CDI integrations only
single default stateless KieSession that
is bound to above (single, default) KieBase
clock type is set to - real time
scope set to - ApplicationScope - valid for CDI integrations only
single default stateful KieSession that
is bound to above (single, default) KieBase
clock type is set to - real time
scope set to - ApplicationScope - valid for CDI integrations only
All these and more can be configured manually via kmodule.xml when defaults are not enough. The complete set of elements can be found in xsd schema of kmodule.xml.
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="defaultKieBase" default="true" eventProcessingMode="cloud" equalsBehavior="identity" declarativeAgenda="disabled" scope="javax.enterprise.context.ApplicationScoped" packages="*">
<ksession name="defaultKieSession" type="stateful" default="true" clockType="realtime" scope="javax.enterprise.context.ApplicationScoped">
<workItemHandlers>
<workItemHandler name="CustomTask" type="FQCN_OF_HANDLER" />
</workItemHandlers>
<listeners>
<listener type="FQCN_OF_EVENT_LISTENER" />
</listeners>
</ksession>
<ksession name="defaultStatelessKieSession" type="stateless" default="true" clockType="realtime" scope="javax.enterprise.context.ApplicationScoped"/>
</kbase>
</kmodule>
As illustrated in the listing above the kmodule.xml provides flexible way of instructing the runtime engine on what should be configured and how. The example above does not present all available options, but these are the most common when working with processes.
Important to note is that when using RuntimeManager, KieSession instances are created by the RuntimeManager instead of by KieContainer but kmodule.xml (or model in general) is aways used as a base of the construction process. KieBase although is always taken from KieContainer.
Kjars are represented same way as any other Maven artifact - by Group Artifact Version which is then represented as ReleaseId in KIE API. This the the only thing required to deploy kjar into runtime environment such as KIE Workbeanch.
When building systems that embed jBPM as workflow engine the simplest way is to configure all dependencies required by jBPM via Apache Maven. jBPM provides set of BOMs (Bill Of Material) to simplify what artifacts needs to be declared. Common way to start with integration of custom application and jBPM is to define dependency management:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.org.drools>6.0.0.Final</version.org.drools>
<version.org.jbpm>6.0.0.Final</version.org.jbpm>
<hibernate.version>4.2.0.Final</hibernate.version>
<hibernate.core.version>4.2.0.Final</hibernate.core.version>
<slf4j.version>1.6.4</slf4j.version>
<jboss.javaee.version>1.0.0.Final</jboss.javaee.version>
<logback.version>1.0.9</logback.version>
<h2.version>1.3.161</h2.version>
<btm.version>2.1.4</btm.version>
<junit.version>4.8.1</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- define drools BOM -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>${version.org.drools}</version>
<scope>import</scope>
</dependency>
<!-- define drools BOM -->
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-bom</artifactId>
<type>pom</type>
<version>${version.org.jbpm}</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Above should be declared in top level pom.xml so all modules that need to use KIE (drools and jBPM) API can access it.
Next, module(s) that would operate on KIE API should declare following dependencies:
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-flow</artifactId>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-flow-builder</artifactId>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-bpmn2</artifactId>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-persistence-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-human-task-core</artifactId>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-runtime-manager</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
Above are the main runtime dependencies, regardless of where the application is deployed (application server, servlet container, standalone app). A good practice is to test the workflow components to ensure they work properly before actual deployment and thus following test dependencies should be defined:
<!-- test dependencies -->
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-shared-services</artifactId>
<classifier>btm</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<version>${btm.version}</version>
<scope>test</scope>
</dependency>
Last but not least, define the JBoss Maven repository for artifacts resolution:
<repositories>
<repository>
<id>jboss-public-repository-group</id>
<name>JBoss Public Repository Group</name>
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<updatePolicy>daily</updatePolicy>
</snapshots>
</repository>
</repositories>
That should allow to configure jBPM in your application and provide access to KIE API to operate on processes, rules, events.
jBPM 6 comes with out of the box integration with CDI (Contexts and Dependency Injection). Although most of the API can be used in CDI world there are some dedicated modules that are designed especially for CDI containers. The most important one is jbpm-services-cdi that provides cdi wrappers on top of jbpm services, these shall be used in most of the cases were CDI is available for jBPM integration. It provides following set of services:
DeploymentService
ProcessService
UserTaskService
RuntimeDataService
DefinitionService
These services are first class citizens for CDI world so they are available for injection in any other CDI bean.
Service responsible for deploying DeploymentUnits into runtime environment. By deploying given deployment unit becomes ready for execution and has RuntimeManager created for it.DeploymentService can next be used to retrieve:
RuntimeManager instance for given deployment id
DeployedUnit that represents complete deployment process for given deployment id
list of all deployed units known to the deployment service
Deployment service stores the deployed units by default in memory and thus in case of a need to restore all previously deployed units, component that uses deployment service needs to store that information itself. Common places for such a store are database, file system, repository of some sort, etc. Deployment service will fire CDI events on deployment and undeployment to allow application components to react real time to these events to be able to store deployments or remove them from the store when they are undeployed.
DeploymentEvent with qualifier @Deploy will be fired on deployment
DeploymentEvent with qualifier @Undeploy will be fired on undeployment
use CDI observer mechanism to get notification on above events. First to save deployments in the store of your choice:
public void saveDeployment(@Observes @Deploy DeploymentEvent event) {
// store deployed unit info for further needs
DeployedUnit deployedUnit = event.getDeployedUnit();
}
next to remove it when it was undeployed
public void removeDeployment(@Observes @Undeploy DeploymentEvent event) {
// remove deployment with id event.getDeploymentId()
}
Deployment service comes with deployment synchronization mechanism that allows to persist deployed units into data base (since version 6.2) that is by default enabled. See jbpm services section for more details.
Due to the fact that there might be several implementation of DeploymentService use of qualifiers is needed to instruct CDI container which one shall be injected. jBPM comes with two out of the box:
@Kjar - KmoduleDeploymentService that is tailored to work with KmoduleDeploymentUnits that are small descriptor on top of a kjar - recommended to use in most of the cases
@Vfs - VFSDeploymentDService that allows to deploy assets directly from VFS (Virtual File System) that is provided by UberFire framework. Due to that fact VFSDeploymentService and VFSDeploymentUnit are not bundled with jbpm core modules but with jbpm-console-ng modules.
The general practice is that every implementation of DeploymentService should come with dedicated implementation of DeploymentUnit as these two provided out of the box.
FormProviderService provides access to form representations usually displayed on UI for both process forms and user task forms. It is built on concept of isolated FormProviders that can provide different capabilities and be backed by different technologies. FormProvider interface describes contract for the implementations
public interface FormProvider {
int getPriority();
String render(String name, ProcessDesc process, Map<String, Object> renderContext);
String render(String name, Task task, ProcessDesc process, Map<String, Object> renderContext);
}
Implementations of FormProvider interface should always define priority as this is the main driver for the FormProviderService to ask for the content of the form of a given provider. FormProviderService will collect all available providers and iterate over them asking for the form content (rendered) in their priority order. The lower the number the higher priority it gets during evaluation, e.g. provider with priority 5 will be evaluated before provider with priority 10. FormProviderService will iterate over available providers as long as one delivers the content. In the worse case scenario, simple text based forms will be returned.
jBPM comes with following FormProviders out of the box:
Fremarker based implementation to support jbpm version 5 process and task forms - priority 3
Default forms provider, considered last resort if none of the other providers deliver content this one will always provide simplest possible forms - lowest priority (1000)
when form modeler is used there is additional FormProvider available to deliver forms modeled in that tool - priority 2
RuntimeDataService provides access to actual data that is availabe on runtime such as
available processes to be executed - with various filters
active process instances - with various filters
process instance history
process instance variables
active and completed nodes of process instance
Default implementation of RuntimeDataService is observing deployment events and index all deployed processes to expose them to the calling components. So whatever gets deployed RuntimeDataService will be aware of that.
Service that provides access to process details stored as part of BPMN2 XML.
Before using any method that provides information, buildProcessDefinition must be invoked to populate repository with process information taken from BPMN2 content.
BPMN2DataService provides access to following data:
overall description of process for given process definition
collection of all user tasks found in the process definition
information about defined inputs for user task node
information about defined outputs for user task node
ids of reusable processes (call activity) defined within given process definition
information about process variables defined within given process definition
information about all organizational entities (users and groups) included in the process definition. Depending on the actual process definition the returned values for users and groups can contain
actual user or group name
process variable that will be used to get actual user or group name on runtime e.g. #{manager}
To make use of jbpm-services-cdi in your system you'll need to provide some beans for the out of the box services to satisfy all dependencies they have. There are several beans that depends on actual scenario
entity manager and entity manager factory
user group callback for human tasks
identity provider to pass authenticated user information to the services
When running in JEE environment like an JBoss Application Server following producer bean should satisfy all requirements of the jbpm-services-cdi
public class EnvironmentProducer {
@PersistenceUnit(unitName = "org.jbpm.domain")
private EntityManagerFactory emf;
@Inject
@Selectable
private UserGroupInfoProducer userGroupInfoProducer;
@Inject
@Kjar
private DeploymentService deploymentService;
@Produces
public EntityManagerFactory getEntityManagerFactory() {
return this.emf;
}
@Produces
public org.kie.api.task.UserGroupCallback produceSelectedUserGroupCalback() {
return userGroupInfoProducer.produceCallback();
}
@Produces
public UserInfo produceUserInfo() {
return userGroupInfoProducer.produceUserInfo();
}
@Produces
@Named("Logs")
public TaskLifeCycleEventListener produceTaskAuditListener() {
return new JPATaskLifeCycleEventListener(true);
}
@Produces
public DeploymentService getDeploymentService() {
return this.deploymentService;
}
@Produces
public IdentityProvider produceIdentityProvider {
return new IdentityProvider() {
// implement IdentityProvider
};
}
}
Then beans.xml for the application should enable proper alternative for user group callback (that will be taken based on @Selectable qualifier)
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://docs.jboss.org/cdi/beans_1_0.xsd">
<alternatives>
<class>org.jbpm.kie.services.cdi.producer.JAASUserGroupInfoProducer</class>
</alternatives>
</beans>
org.jbpm.kie.services.cdi.producer.JAASUserGroupInfoProducer is just an example here which usually is the good fit for JBoss Application Server to reuse security settings on application server regardless of what it actually is (LDAP, DB, etc). Check Human Task section for more alternatives for UserGroupCallback.
Optionally there can be several other producers provided to deliver:
WorkItemHandlers
Process, Agenda, WorkingMemory event listeners
These components can be provided by implementing following interfaces
/**
* Allows to provide custom implementations to deliver WorkItem name and WorkItemHandler instance pairs
* for the runtime.
* <br/>
* It will be invoked by RegisterableItemsFactory implementation (especially InjectableRegisterableItemsFactory
* in CDI world) for every KieSession. Recommendation is to always produce new instances to avoid unexpected
* results.
*
*/
public interface WorkItemHandlerProducer {
/**
* Returns map of (key = work item name, value work item handler instance) of work items
* to be registered on KieSession
* <br/>
* Parameters that might be given are as follows:
* <ul>
* <li>ksession</li>
* <li>taskService</li>
* <li>runtimeManager</li>
* </ul>
*
* @param identifier - identifier of the owner - usually RuntimeManager that allows the producer to filter out
* and provide valid instances for given owner
* @param params - owner might provide some parameters, usually KieSession, TaskService, RuntimeManager instances
* @return map of work item handler instances (recommendation is to always return new instances when this method is invoked)
*/
Map<String, WorkItemHandler> getWorkItemHandlers(String identifier, Map<String, Object> params);
}
and
/**
* Allows do define custom producers for know EventListeners. Intention of this is that there might be several
* implementations that might provide different listener instance based on the context they are executed in.
* <br/>
* It will be invoked by RegisterableItemsFactory implementation (especially InjectableRegisterableItemsFactory
* in CDI world) for every KieSession. Recommendation is to always produce new instances to avoid unexpected
* results.
*
* @param <T> type of the event listener - ProcessEventListener, AgendaEventListener, WorkingMemoryEventListener
*/
public interface EventListenerProducer<T> {
/**
* Returns list of instances for given (T) type of listeners
* <br/>
* Parameters that might be given are as follows:
* <ul>
* <li>ksession</li>
* <li>taskService</li>
* <li>runtimeManager</li>
* </ul>
* @param identifier - identifier of the owner - usually RuntimeManager that allows the producer to filter out
* and provide valid instances for given owner
* @param params - owner might provide some parameters, usually KieSession, TaskService, RuntimeManager instances
* @return list of listener instances (recommendation is to always return new instances when this method is invoked)
*/
List<T> getEventListeners(String identifier, Map<String, Object> params);
}
Beans implementing these two interfaces will be collected on runtime and consulted when building KieSession by RuntimeManager. See RuntimeManager section for more details on this.
A complete runnable example of application built with CDI can be found here.
Even though RuntimeManager can be directly injected, it's recommended to utilize jbpm services when frameworks like CDI, ejb or Spring is used. jBPM services bring in significant amount of features that encapsulate best practices when using RuntimeManager.
RuntimeManager itself can be injected as CDI bean into any other CDI bean within the application. It has then requirement to get RungimeEnvironment properly produces to allow RuntimeManager to be correctly initialized. RuntimeManager comes with three predefined strategies and each of them gets CDI qualifier so it can be referenced:
@Singleton
@PerRequest
@PerProcessInstance
Producer that was defined in Configuration section should be now enhanced with producer methods to provide RuntimeEnvironment
public class EnvironmentProducer {
//add same producers as for services
@Produces
@Singleton
@PerRequest
@PerProcessInstance
public RuntimeEnvironment produceEnvironment(EntityManagerFactory emf) {
RuntimeEnvironment environment = RuntimeEnvironmentBuilder.Factory.get()
.newDefaultBuilder()
.entityManagerFactory(emf)
.userGroupCallback(getUserGroupCallback())
.registerableItemsFactory(InjectableRegisterableItemsFactory.getFactory(beanManager, null))
.addAsset(ResourceFactory.newClassPathResource("BPMN2-ScriptTask.bpmn2"), ResourceType.BPMN2)
.addAsset(ResourceFactory.newClassPathResource("BPMN2-UserTask.bpmn2"), ResourceType.BPMN2)
.get();
return environment;
}
}
In this example single producer method is capable of providing RuntimeEnvironment for all strategies of RuntimeManager by specifying all qualifiers on the method level.
Once complete producer is available, RuntimeManager can be injected into application's CDi bean
public class ProcessEngine {
@Inject
@Singleton
private RuntimeManager singletonManager;
public void startProcess() {
RuntimeEngine runtime = singletonManager.getRuntimeEngine(EmptyContext.get());
KieSession ksession = runtime.getKieSession();
ProcessInstance processInstance = ksession.startProcess("UserTask");
singletonManager.disposeRuntimeEngine(runtime);
}
}
That's all what needs to be configured to make use of CDI power with jBPM.
An obvious limitation of injecting directly RuntimeManager via CDI is that there might be only one RuntimeManager in the application. That in some case can be desired and that's why there is such option. In general recommended approach is to make use of DeploymentService whenever there is a need to have many RuntimeManagers active within application.
As an alternative to DeploymentService, RuntimeManagerFactory can be injected and then RuntimeManager instance can be created manually by the application. In such case EnvironmentProducer stays same as for DeploymentService and following is an example of simple ProcessEngine bean
public class ProcessEngine {
@Inject
private RuntimeManagerFactory managerFactory;
@Inject
private EntityManagerFactory emf;
@Inject
private BeanManager beanManager;
public void startProcess() {
RuntimeEnvironment environment = RuntimeEnvironmentBuilder.Factory.get()
.newDefaultBuilder()
.entityManagerFactory(emf)
.addAsset(ResourceFactory.newClassPathResource("BPMN2-ScriptTask.bpmn2"), ResourceType.BPMN2)
.addAsset(ResourceFactory.newClassPathResource("BPMN2-UserTask.bpmn2"), ResourceType.BPMN2)
.registerableItemsFactory(InjectableRegisterableItemsFactory.getFactory(beanManager, null))
.get();
RuntimeManager manager = managerFactory.newSingletonRuntimeManager(environment);
RuntimeEngine runtime = manager.getRuntimeEngine(EmptyContext.get());
KieSession ksession = runtime.getKieSession();
ProcessInstance processInstance = ksession.startProcess("UserTask");
manager.disposeRuntimeEngine(runtime);
manager.close();
}
}
jBPM can be configured in many ways with Spring though the two most frequenlty used approaches are:
direct use of runtime manager API
use of jbpm services
While both approaches are tested and valid, which one to chose is a matter of the system functionaltiy. Before selecting one of the approache the most important question to ask is:
Will my system run multiple runtime managers at the same time?
If the asnwer to this question is no, then go ahead with direct Runtime Manager API as it will be the simplest way to use jBPM within your application. But when answer is yes, then go ahead with jbpm services as they encapsulate runtime manager API with best practices by providing dynamic runtime environment for your BPM logic - also known as execution server.
This is the standard (and the simplest) way to get up and running with jBPM in your application. You only configure it once and run as part of the application. With the RuntimeManager usage, both process engine and task service will be managed in complete synchronization, meaning there is no need from end user to deal with "plumbing" code to make these two work together.
To provide spring based way of setting up jBPM, few factory beans where added:
org.kie.spring.factorybeans.RuntimeEnvironmentFactoryBean
org.kie.spring.factorybeans.RuntimeManagerFactoryBean
org.kie.spring.factorybeans.TaskServiceFactoryBean
FactoryBeans provide standard way to configure Spring application spring xml though there are not custom spring xml tags equivalent for them.
Factory responsible for producing instances of RuntimeEnvironment that are consumed by RuntimeManager upon creation. It allows to create following types of RuntimeEnvironment (that mainly means what is configured by default):
DEFAULT - default (most common) configuration for RuntimeManager
EMPTY - completely empty environment to be manually populated
DEFAULT_IN_MEMORY - same as DEFAULT but without persistence of the runtime engine
DEFAULT_KJAR - same as DEFAULT but knowledge asset are taken from KJAR identified by releaseid or GAV
DEFAULT_KJAR_CL - build directly from classpath that consists kmodule.xml descriptor
Mandatory properties depends on the selected type but knowledge information must be given for all types. That means that one of the following must be provided:
knowledgeBase
assets
releaseId
groupId, artifactId, version
Next for DEFAULT, DEFAULT_KJAR, DEFAULT_KJAR_CL persistence needs to be configured:
entity manager factory
transaction manager
Transaction Manager must be Spring transaction manager as based on its presence entire persistence and transaction support is configured. Optionally EntityManager can be provided to be used instead of always creating new one from EntityManagerFactory - e.g. when using shared entity manager from Spring. All other properties are optional and are meant to override the default given by type of the environment selected.
FactoryBean responsible for creation of RuntimeManager instances of given type based on provided runtimeEnvironment. Supported types:
SINGLETON
PER_REQUEST
PER_PROCESS_INSTANCE
where default is SINGLETON when no type is specified. Every runtime manager must be uniquely identified thus identifier is a mandatory property. All instances created by this factory are cached to be able to properly dispose them using destroy method (close()).
Creates instance of TaskService based on given properties. Following are mandatory properties that must be provided:
entity manager factory
transaction manager
Transaction Manager must be Spring transaction manager as based on its presence entire persistence and transaction support is configured. Optionally EntityManager can be provided to be used instead of always creating new one from EntityManagerFactory - e.g. when using shared entity manager from Spring. In addition to above there are optional properties that can be set on task service instance:
userGroupCallback - implementation of UserGroupCallback to be used, defaults to MVELUserGroupCallbackImpl
userInfo - implementation of UserInfo to be used, defaults to DefaultUserInfo
listener - list of TaskLifeCycleEventListener that will be notified upon various operations on tasks
This factory creates single instance of task service only as it's intended to be shared across all other beans in the system.
Following section aims at giving complete spring configuration for single runtime manager wihtin spring application context.
Setup entity manager factory and transaction manager:
<bean id="jbpmEMF" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="org.jbpm.persistence.spring.jta"/>
</bean>
<bean id="btmConfig" factory-method="getConfiguration" class="bitronix.tm.TransactionManagerServices"></bean>
<bean id="BitronixTransactionManager" factory-method="getTransactionManager"
class="bitronix.tm.TransactionManagerServices" depends-on="btmConfig" destroy-method="shutdown" />
<bean id="jbpmTxManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="BitronixTransactionManager" />
<property name="userTransaction" ref="BitronixTransactionManager" />
</bean>
with this we have ready persistence configuration that gives us:
JTA transaction manager (backed by bitronix - for unit tests or servlet containers)
entity manager factory for persistence unit named org.jbpm.persistence.spring.jta
Configure resource that we are going to use - business process
<bean id="process" factory-method="newClassPathResource" class="org.kie.internal.io.ResourceFactory">
<constructor-arg>
<value>jbpm/processes/sample.bpmn</value>
</constructor-arg>
</bean>
this configures single process that will be available for execution - sample.bpmn that will be taken from class path. This is the simplest way to get your processes included when trying out jbpm.
Configure RuntimeEnvironment with our infrastructure (entity manager, transaction manager, resources)
<bean id="runtimeEnvironment" class="org.kie.spring.factorybeans.RuntimeEnvironmentFactoryBean">
<property name="type" value="DEFAULT"/>
<property name="entityManagerFactory" ref="jbpmEMF"/>
<property name="transactionManager" ref="jbpmTxManager"/>
<property name="assets">
<map>
<entry key-ref="process"><util:constant static-field="org.kie.api.io.ResourceType.BPMN2"/></entry>
</map>
</property>
</bean>
that gives us default runtime environment ready to be used to create instance of a RuntimeManager.
Create RuntimeManager with the environment we just setup
<bean id="runtimeManager" class="org.kie.spring.factorybeans.RuntimeManagerFactoryBean" destroy-method="close">
<property name="identifier" value="spring-rm"/>
<property name="runtimeEnvironment" ref="runtimeEnvironment"/>
</bean>
with just four steps you are ready to execute your processes with Spring and jBPM 6, utilizing EntityManagerFactory and JTA transaction manager.
Complete spring configuration file can be found here.
This is just one configuration setup that jBPM 6 supports - JTA transaction manager and EntityManagerFactory, others are:
JTA and SharedEntityManager
Local Persistence Unit and EntityManagerFactory
Local Persistence Unit and SharedEntityManager
For more details about difference configuration options look at the example configuration files and test cases.
In case more dynamic nature is required in your Spring application then more appropriate could be to build up so called execution server based on jbpm services. jBPM services has been designed in a way to make them framework agnostic and in case framework specific addons are required they will be brought by additional module. So the code logic of the services is embedded in jbpm-kie-services. These are pure java services and by that can be easily consumed by Spring application.
Dynamic nature means that processes (And other assets like data model, rules, forms, etc) can be added and removed without restarting application.
There is almost no code involved to completely configure jBPM services in spring besides single interface that needs to be implemented - IdentityProvider that depends on your security configuration. One built with Spring Security can be like following though it might not cover all features one can have for Spring application.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.kie.internal.identity.IdentityProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
public class SpringSecurityIdentityProvider implements IdentityProvider {
public String getName() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
return auth.getName();
}
return "system";
}
public List<String> getRoles() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
List<String> roles = new ArrayList<String>();
for (GrantedAuthority ga : auth.getAuthorities()) {
roles.add(ga.getAuthority());
}
return roles;
}
return Collections.emptyList();
}
public boolean hasRole(String role) {
return false;
}
}
As usual, first thing to start with is transaction configuration:
<context:annotation-config />
<tx:annotation-driven />
<tx:jta-transaction-manager />
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
Next configuration of JPA and persistence follows:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" depends-on="transactionManager">
<property name="persistenceXmlLocation" value="classpath:/META-INF/jbpm-persistence.xml" />
</bean>
Configure security and user/group information providers
<util:properties id="roleProperties" location="classpath:/roles.properties" />
<bean id="userGroupCallback" class="org.jbpm.services.task.identity.JBossUserGroupCallbackImpl">
<constructor-arg name="userGroups" ref="roleProperties"></constructor-arg>
</bean>
<bean id="identityProvider" class="org.jbpm.spring.SpringSecurityIdentityProvider"/>
Configure runtime manager factory that is Spring context aware and by that can interact with spring container in correct way and supporting services (transactional command service and task service)
<bean id="runtimeManagerFactory" class="org.kie.spring.manager.SpringRuntimeManagerFactoryImpl">
<property name="transactionManager" ref="transactionManager"/>
<property name="userGroupCallback" ref="userGroupCallback"/>
</bean>
<bean id="transactionCmdService" class="org.jbpm.shared.services.impl.TransactionalCommandService">
<constructor-arg name="emf" ref="entityManagerFactory"></constructor-arg>
</bean>
<bean id="taskService" class="org.kie.spring.factorybeans.TaskServiceFactoryBean" destroy-method="close">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="userGroupCallback" ref="userGroupCallback"/>
<property name="listeners">
<list>
<bean class="org.jbpm.services.task.audit.JPATaskLifeCycleEventListener">
<constructor-arg value="true"/>
</bean>
</list>
</property>
</bean>
Configure jBPM services as spring beans
<!-- definition service -->
<bean id="definitionService" class="org.jbpm.kie.services.impl.bpmn2.BPMN2DataServiceImpl"/>
<!-- runtime data service -->
<bean id="runtimeDataService" class="org.jbpm.kie.services.impl.RuntimeDataServiceImpl">
<property name="commandService" ref="transactionCmdService"/>
<property name="identityProvider" ref="identityProvider"/>
<property name="taskService" ref="taskService"/>
</bean>
<!-- -- deployment service -->
<bean id="deploymentService" class="org.jbpm.kie.services.impl.KModuleDeploymentService" depends-on="entityManagerFactory" init-method="onInit">
<property name="bpmn2Service" ref="definitionService"/>
<property name="emf" ref="entityManagerFactory"/>
<property name="managerFactory" ref="runtimeManagerFactory"/>
<property name="identityProvider" ref="identityProvider"/>
<property name="runtimeDataService" ref="runtimeDataService"/>
</bean>
<!-- process service -->
<bean id="processService" class="org.jbpm.kie.services.impl.ProcessServiceImpl" depends-on="deploymentService">
<property name="dataService" ref="runtimeDataService"/>
<property name="deploymentService" ref="deploymentService"/>
</bean>
<!-- user task service -->
<bean id="userTaskService" class="org.jbpm.kie.services.impl.UserTaskServiceImpl" depends-on="deploymentService">
<property name="dataService" ref="runtimeDataService"/>
<property name="deploymentService" ref="deploymentService"/>
</bean>
<!-- register runtime data service as listener on deployment service so it can receive notification about deployed and undeployed units -->
<bean id="data" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" depends-on="deploymentService">
<property name="targetObject" ref="deploymentService"></property>
<property name="targetMethod"><value>addListener</value></property>
<property name="arguments">
<list>
<ref bean="runtimeDataService"/>
</list>
</property>
</bean>
And this is all is needed to build fully featured execution server with Spring and jBPM services. A complete Spring web application with this setup can be found here.
jBPM since version 6.2 provides out of the box integration layer with Enterprise Java Beans (EJB) for both local and remote interaction.
Ejb services are brought by following modules:
jbpm-services-ejb-api
API module that extends jbpm-services-api with EJB specific interfaces and objects
jbpm-services-ejb-impl
EJB extension to core services
jbpm-services-ejb-timer
jBPM Scheduler Service implementation backed by EJB Timer Service
jbpm-services-ejb-client
EJB remote client implementation for remote interaction, provides JBoss AS support out of the box
EJB layer is based on jbpm services and thus provides almost same capabilities as the core module though there are some imiliations when it comes to remote interfaces. Main difference is for the DeploymentService that has been limited for remote ejb service to following methods:
deploy
undeploy
activate
deactivate
isDeployed
Main rationale behind is to avoid returning runtime objects such as RuntimeManager over EJB remote as it won't bring any value because it will be "disconnected" state.
All other services do provide exact same set of functionality as core module.
Ejb services as an extension of core services provide EJB based execution semantic and based on various EJB specific features.
DeploymentServiceEJBImpl
is implemented as ejb singleton with container managed concurrency and lock type set to write
DefinitionServiceEJBImpl
is implemented as ejb singleton with container managed concurrency with overall lock type set to read, except buildProcessDefinition method that has lock type set to write
ProcessServiceEJBImpl
is implemented as stateless session bean
RuntimeDataServiceEJBImpl
is implemented as ejb singleton with mojority of methods with lock type read, except following that are with lock type write:
onDeploy
onUnDeploy
onActivte
onDeactivate
UserTaskServiceEJBImpl
is implemented as stateless session bean
Transactions
Transaction is managed by EJB container thus there is no need to setup any sort of transaction manager or user transaction within application code.
Identity provider
Identity provider by default is backed by EJBContext and will rely on caller principal information for both name and roles. When inspecting IdentityProvider interface there are two methods related to roles:
getRoles
this method returns empty list due to the fact EJBContext does not provide options to fetch all roles for given user
hasRole
this method will delegate to context's isCallerInRole method
This means that ejb must be secured according to JEE security practices to authentiate and authorize users so valid information will be available. In case no authentication/authorization is configured for EJB services an anonymous user is always assumed.
In addition to that, EJB services acept CDI sytly injection for IdentityProvider in case another (non ejb) security model is used. Simply create valid CDI bean that implements org.kie.internal.identity.IdentityProvider and make it available for injection with application and such implementation will take precedence over EJBContext based identity provider.
Deployment synchronization
Deployment synchronization is enabled by default and will attempt to synchronize any deployments every 3 seconds. It is implemented as ejb singleton with container managed concurrency and lock type set to write. Under the covers it utilizes EJB TimerService to schedule the synchronization jobs.
EJB Scheduler Service
jBPM uses scheduler service to deal with time based activities such as timer events, deadlines, etc. When running in EJB environment and EJB Timer Service based scheduler will be used. It will be automatically registered for all instances of RuntimeManager. When it comes to cluster support application server specific configuration might be required.
UserGroupCallback and UserInfo selection
UserGroupCallback and UserInfo might differ for various applications and thus should be sort of pluggable. With EJB we could not make it directly available for injections as they could not be injected with common type so there is another mechanism that allows to select one of provided out of the box implementation or to give a custom one. This mechanism is based on ssytem properties:
org.jbpm.ht.callback
specify what implementation of user group callback will be selected
mvel - default mostly used for testing
ldap - ldap backed implementation - requires additional configuration via jbpm.usergroup.callback.properties file
db - data base backed implementation - requires additional configuration via jbpm.usergroup.callback.properties file
jaas - delegates to container to fetch information about user data
props - simple property based callback - requires additional file that will keep all information (users and groups)
custom - custom implementation that requires to have additional system property set (FQCN of the implementation) - org.jbpm.ht.custom.callback
org.jbpm.ht.userinfo
specify what implementation of UserInfo shall be used, one of:
ldap - backed by ldap - requires configuration via jbpm-user.info.properties file
db - backed by data base - requires configuration via jbpm-user.info.properties file
props - backed by simple property file
custom - custom implementation that requires to have additional system property set (FQCN of the implementation) - org.jbpm.ht.custom.userinfo
System properties can either be added to the startup configuration of the server (jvm) which is recommended or be set programmatically before services will be used - for example with custom @Startup bean that will configure it properly for selected callback and user info.
A example application that utilizes EJB services can be found here.
Local EJB services are brought via dedicated local interfaces that extends core services:
org.jbpm.services.ejb.api.DefinitionServiceEJBLocal
org.jbpm.services.ejb.api.DeploymentServiceEJBLocal
org.jbpm.services.ejb.api.ProcessServiceEJBLocal
org.jbpm.services.ejb.api.RuntimeDataServiceEJBLocal
org.jbpm.services.ejb.api.UserTaskServiceEJBLocal
These interfaces should be used as injection points and shall be annotated with @EJB:
@EJB
private DefinitionServiceEJBLocal bpmn2Service;
@EJB
private DeploymentServiceEJBLocal deploymentService;
@EJB
private ProcessServiceEJBLocal processService;
@EJB
private RuntimeDataServiceEJBLocal runtimeDataService;
Once injected operations can be invoked on them as with core modules, there are no restrictions to their usage.
Remote EJB services are defined as dedicated remote interfaces that extends core services:
org.jbpm.services.ejb.api.DefinitionServiceEJBRemote
org.jbpm.services.ejb.api.DeploymentServiceEJBRemote
org.jbpm.services.ejb.api.ProcessServiceEJBRemote
org.jbpm.services.ejb.api.RuntimeDataServiceEJBRemote
org.jbpm.services.ejb.api.UserTaskServiceEJBRemote
These can be used similar way as local interfaces except for handling custom types. Custom types can be defined:
globally
such types are available on application classpath - included in the enterprise application
locally to the deployment unit
such types are declared as project (kjar) dependency and are resolved on deployment time
Globally available types do not require any special handling as they will be available for EJB container when remote requests are handled - marshalling of incoming data. Though local custom types won't be visible by default to EJB container as they are not on application classpath. Thus special handling of such types is required.
EJB services provides easy yet rather powerful mechanism to resolve the issue - it comes with two additional types:
org.jbpm.services.ejb.remote.api.RemoteObject
Serializable wrapper class for single value parameters
org.jbpm.services.ejb.remote.api.RemoteMap
Dedicated java.util.Map implementation to simplify remote invocation of service methods that accept custom object input. This map is backed by an internal map that holds already serialized content to avoid additional serialization on sending time. That removes the burden of ensuring that container will know about all custom data model classes as part of global classpath.
This implementation does not support all methods that are usually not used when sending data. It shall be considered only as a wrapper only and not actual and complete implementation of a map.
These special objects will perform eager serialization to bytes using ObjectInputStream to remove the need of serialization from the EJB client/container. Though it might be worse in case of performance it does overcome much more complecated handling of class loaders on EJB container side to allow use of custom types defined in the project.
Here is an example code needed to work with local types and remote EJB:
// start a process with custom types via remote EJB
Map<String, Object> parameters = new RemoteMap();
Person person = new org.jbpm.test.Person("john", 25, true);
parameters.put("person", person);
Long processInstanceId = processService.startProcess(deploymentUnit.getIdentifier(), "custom-data-project.work-on-custom-data", parameters);
// fetch task data and complete task with custom types via remote EJB
Map<String, Object> data = userTaskService.getTaskInputContentByTaskId(taskId);
Person fromTaskPerson = data.get("_person");
fromTaskPerson.setName("John Doe");
RemoteMap outcome = new RemoteMap();
outcome.put("person_", fromTaskPerson);
userTaskService.complete(taskId, "john", outcome);
Similar way RemoteObject can be used for example to send evnet to process instance:
// send event with custom type via remote EJB
Person person = new org.jbpm.test.Person("john", 25, true);
RemoteObject myObject = new RemoteObject(person);
processService.signalProcessInstance(processInstanceId, "MySignal", myObject);
These illustrates how to wrap custom data when interacting with remote EJB services. Next section will introduce how to make a connection to a remote service vai client code.
Remote client support is provided by implemetation of ClientServiceFactory interface that is facede for application server specific code:
/**
* Generic service factory used for remote look ups that are usually container specific.
*
*/
public interface ClientServiceFactory {
/**
* Returns unique name of given factory implementation
* @return
*/
String getName();
/**
* Returns remote view of given service interface from selected application
* @param application application identifier on the container
* @param serviceInterface remote service interface to be found
* @return
* @throws NamingException
*/
<T> T getService(String application, Class<T> serviceInterface) throws NamingException;
}
Implementations can be dynamically registered using ServiceLoader mechanism and by default there is only one available for JBoss AS/EAP/Wildfly. Each ClientServiceFactory must provide name which will be used to register it within the client registry so it can be then easily looked up.
Here is a code used to get hold of default JBoss based remote client:
// get hold of valid client service factory
ClientServiceFactory factory = ServiceFactoryProvider.getProvider("JBoss");
// application is the name known to application server aka module name
String application = "sample-war-ejb-app";
// get given service out of the factory
DeploymentServiceEJBRemote deploymentService = factory.getService(application, DeploymentServiceEJBRemote.class);
With service available all know to its interface methods are ready to be used.
When working with JBoss AS and remote client you can add following maven dependency to bring in all EJB client libraries:
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-ejb-client-bom</artifactId>
<version>7.2.0.Final</version> <!-- use valid version for the server you run on -->
<optional>true</optional>
<type>pom</type>
</dependency>
All core jBPM JARs (and core dependencies) are OSGi-enabled. That means that they contain MANIFEST.MF files (in the META-INF directory) that describe their dependencies etc. These manifest files are automatically generated by the build. You can plug these JARs directly into an OSGi environment.
OSGi is a dynamic module system for declarative services. So what does that mean? Each JAR in OSGi is called a bundle and has its own Classloader. Each bundle specifies the packages it exports (makes publicly available) and which packages it imports (external dependencies). OSGi will use this information to wire the classloaders of different bundles together; the key distinction is you don't specify what bundle you depend on, or have a single monolithic classpath, instead you specify your package import and version and OSGi attempts to satisfy this from available bundles.
It also supports side by side versioning, so you can have multiple versions of a bundle installed and it'll wire up the correct one. Further to this Bundles can register services for other bundles to use. These services need initialisation, which can cause ordering problems - how do you make sure you don't consume a service before its registered? OSGi has a number of features to help with service composition and ordering. The two main ones are the programmatic ServiceTracker and the XML based Declarative Services. There are also other projects that help with this; Spring DM, iPOJO, Gravity.
The following jBPM JARs are OSGi-enabled: