CDI integrationThe activiti-cdi modules leverages both the configurability of activiti and the extensibility of cdi.
The most prominent features of activiti-cdi are:
Support for @BusinessProcessScoped beans (Cdi beans the lifecycle of which is bound to a process instance),A custom El-Resolver for resolving Cdi beans (including EJBs) from the process,Declarative control over a process instance using annotations,Activiti is hooked-up to the cdi event bus,Works with both Java EE and Java SE, works with Spring,Support for unit testing.
In order to include activiti-cdi in a maven project, we add the following maven dependency:
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-cdi</artifactId>
<version>5.x</version>
</dependency>
Replace 'x' with your activiti-version (>=5.6). This will pull-in activiti-engine and spring.
Setting up activiti-cdi
Activiti cdi can be setup in different environments. In this section we briefly walk through the configuration options.
Looking up a Process Engine
The cdi extension needs to get access to a ProcessEngine. To achieve this, an implementation of the interface
org.activiti.cdi.spi.ProcessEngineLookup is looked up at runtime. The cdi module ships with
a default implementation named org.activiti.cdi.impl.LocalProcessEngineLookup, which uses
the ProcessEngines-Utility class for looking up the ProcessEngine. In the default configuration
ProcessEngines#NAME_DEFAULT is used to lookup the ProcessEngine. This class might be subclassed to
set a custom name. NOTE: needs an activiti.cfg.xml configuration on the classpath.
Activiti cdi uses a java.util.ServiceLoader SPI for resolving an instance of org.activiti.cdi.spi.ProcessEngineLookup.
In order to provide a custom implementation of the interface, we need to add a plain text file named
META-INF/services/org.activiti.cdi.spi.ProcessEngineLookup to our deployment, in which we specify the fully
qualified classname of the implementation.
If you do not provide a custom org.activiti.cdi.spi.ProcessEngineLookup implementation, activiti
will use the default LocalProcessEngineLookup implementation. In that case, all you need to do is providing a
activiti.cfg.xml file on the classpath (see next section).
Configuring the Process EngineConfiguration depends on the selected ProcessEngineLookup-Strategy (cf. previous section).
Here, we focus on the configuration options available in combination with the LocalProcessEngineLookup, which requires us to provide
a Spring activiti.cfg.xml file on the classpath.Activiti offers different ProcessEngineConfiguration implementations mostly dependent on the underlying transaction management strategy. The
activiti-cdi module is not concerned with transactions, which means that potentially any transaction management strategy
can be used (even the Spring transaction abstraction). As a convenience, the cdi-module provides two custom ProcessEngineConfiguration
implementations:
org.activiti.cdi.CdiJtaProcessEngineConfiguration: a subclass of the activiti JtaProcessEngineConfiguration,
can be used if JTA-managed transactions should be used for activitiorg.activiti.cdi.CdiStandaloneProcessEngineConfiguration: a subclass of the activiti StandaloneProcessEngineConfiguration,
can be used if plain JDBC transactions should be used for activiti
The following is an example activiti.cfg.xml file for JBoss 7:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- lookup the JTA-Transaction manager -->
<bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:jboss/TransactionManager"></property>
<property name="resourceRef" value="true" />
</bean>
<!-- process engine configuration -->
<bean id="processEngineConfiguration"
class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
<!-- lookup the default Jboss datasource -->
<property name="dataSourceJndiName" value="java:jboss/datasources/ExampleDS" />
<property name="databaseType" value="h2" />
<property name="transactionManager" ref="transactionManager" />
<!-- using externally managed transactions -->
<property name="transactionsExternallyManaged" value="true" />
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>
And this is how it would look like for Glassfish 3.1.1 (assuming a datasource named jdbc/activiti is properly configured):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- lookup the JTA-Transaction manager -->
<bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:appserver/TransactionManager"></property>
<property name="resourceRef" value="true" />
</bean>
<!-- process engine configuration -->
<bean id="processEngineConfiguration"
class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
<property name="dataSourceJndiName" value="jdbc/activiti" />
<property name="transactionManager" ref="transactionManager" />
<!-- using externally managed transactions -->
<property name="transactionsExternallyManaged" value="true" />
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>
Note that the above configuration requires the "spring-context" module:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
The configuration in a Java SE environment looks exactly
like the examples provided in section Creating a ProcessEngine,
substitute "CdiStandaloneProcessEngineConfiguration" for "StandaloneProcessEngineConfiguration".
Deploying ProcessesProcesses can be deployed using standard activiti-api (RepositoryService). In addition, activiti-cdi offers the possibility to
auto-deploy processes listed in a file named processes.xml located top-level in the classpath. This is an example
processes.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<!-- list the processes to be deployed -->
<processes>
<process resource="diagrams/myProcess.bpmn20.xml" />
<process resource="diagrams/myOtherProcess.bpmn20.xml" />
</processes>
Contextual Process Execution with CDI
In this section we briefly look at the contextual process execution model used by the activiti cdi extension.
A BPMN business process is typically a long-running interaction, comprised of both user and system tasks. At runtime,
a process is split-up into a set of individual units of work, performed by users and/or
application logic. In activiti-cdi, a process instance can be associated with a cdi scope, the association representing a
unit of work. This is particularly useful, if a unit of work is complex, for instance if the implementation of
a UserTask is a complex sequence of different forms and "non-process-scoped" state needs to be kept during this interaction.
In the default configuration, process instances are associated with the "broadest" active scope, starting with
the conversation and falling back to the request if the conversation context is not active.
Associating a Conversation with a Process Instance
When resolving @BusinessProcessScoped beans, or injecting process variables, we rely on an existing association
between an active cdi scope and a process instance. Activiti-cdi provides the org.activiti.cdi.BusinessProcess bean
for controlling the association, most prominently:
the startProcessBy*(...)-methods, mirroring the respective methods exposed by the activiti RuntimeService
allowing to start and subsequently associating a business process,resumeProcessById(String processInstanceId), allowing to associate the process instance with the provided id,resumeTaskById(String taskId), allowing to associate the task with the provided id (and by extension, the corresponding
process instance),
Once a unit of work (for example a UserTask) is completed, the completeTask() method can be called to disassociate the
conversation/request from the process instance. This signals activiti that the current task is completed and makes
the process instance proceed. Note that the BusinessProcess-bean is a @Named bean, which means that the exposed methods can
be invoked using expression language, for example from a JSF page. The following JSF2 snippet begins a new conversation and associates it
with a user task instance, the id of which is passed as a request parameter (e.g. pageName.jsf?taskId=XX):
<f:metadata>
<f:viewParam name="taskId" />
<f:event type="preRenderView" listener="#{businessProcess.startTask(taskId, true)}" />
</f:metadata>
Declaratively controlling the Process
Activiti-cdi allows declaratively starting process instances and completing tasks using annotations. The
@org.activiti.cdi.annotation.StartProcess annotation allows to start a process instance
either by "key" or by "name".
Note that the process instance is started after the annotated method returns. Example:
@StartProcess("authorizeBusinessTripRequest")
public String submitRequest(BusinessTripRequest request) {
// do some work
return "success";
}
Depending on the configuration of activiti, the code of the annotated method and the starting of the
process instance will be combined in the same transaction.
The @org.activiti.cdi.annotation.CompleteTask-annotation works in the same way:
@CompleteTask(endConversation=false)
public String authorizeBusinessTrip() {
// do some work
return "success";
}
The @CompleteTask annotation offers the possibility to end the current conversation. The
default behavior is to end the conversation after the call to activiti returns. Ending the conversation can be disabled,
as shown in the example above.
Referencing Beans from the Process
Activiti-cdi exposes CDI beans to activiti El, using a custom resolver. This makes it possible to reference beans from the process:
<userTask id="authorizeBusinessTrip" name="Authorize Business Trip"
activiti:assignee="#{authorizingManager.account.username}" />
Where "authorizingManager" could be a bean provided by a producer method:
@Inject @ProcessVariable Object businessTripRequesterUsername;
@Produces
@Named
public Employee authorizingManager() {
TypedQuery<Employee> query = entityManager.createQuery("SELECT e FROM Employee e WHERE e.account.username='"
+ businessTripRequesterUsername + "'", Employee.class);
Employee employee = query.getSingleResult();
return employee.getManager();
}
We can use the same feature to call a business method of an EJB in a service task, using the activiti:expression="myEjb.method()"-extension.
Note that this requires a @Named-annotation on the MyEjb-class.
Working with @BusinessProcessScoped beans
Using activiti-cdi, the lifecycle of a bean can be bound to a process instance. To this extend, a custom context implementation is provided, namely the
BusinessProcessContext. Instances of BusinessProcessScoped beans are stored as process variables in the current process instance.
BusinessProcessScoped beans need to be PassivationCapable (for example Serializable).
The following is an example of a process scoped bean:
@Named
@BusinessProcessScoped
public class BusinessTripRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String startDate;
private String endDate;
// ...
}
Sometimes, we want to work with process scoped beans, in the absence of an association with a process instance, for example before starting a process.
If no process instance is currently active, instances of BusinessProcessScoped beans are temporarily stored in a local scope (I.e. the Conversation or
the Request, depending on the context. If this scope is later associated with a business process instance, the bean instances are flushed to
the process instance.
Injecting Process Variables
Process variables are available for injection. Activiti-CDI supports
type-safe injection of @BusinessProcessScoped beans using @Inject [additional qualifiers] Type fieldNameunsafe injection of other process variables using the @ProcessVariable(name?) qualifier:
@Inject @ProcessVariable Object accountNumber;
@Inject @ProcessVariable("accountNumber") Object account
In order to reference process variables using EL, we have similar options:
@Named @BusinessProcessScoped beans can be referenced directly,other process variables can be referenced using the ProcessVariables-bean:
#{processVariables['accountNumber']}
Receiving Process Events[EXPERIMENTAL]
Activiti can be hooked-up to the CDI event-bus. This allows us to be notified of process events using standard CDI event mechanisms.
In order to enable CDI event support for activiti, enable the corresponding parse listener in the configuration:
<property name="postBpmnParseHandlers">
<list>
<bean class="org.activiti.cdi.impl.event.CdiEventSupportBpmnParseHandler" />
</list>
</property>
Now activiti is configured for publishing events using the CDI event bus. The following gives an overview of how process events can be received in CDI beans.
In CDI, we can declaratively specify event observers using the @Observes-annotation. Event notification is type-safe. The type of
process events is org.activiti.cdi.BusinessProcessEvent.
The following is an example of a simple event observer method:
public void onProcessEvent(@Observes BusinessProcessEvent businessProcessEvent) {
// handle event
}
This observer would be notified of all events. If we want to restrict the set of events the observer receives, we can add qualifier annotations:
@BusinessProcess: restricts the set of events to a certain
process definition. Example: @Observes @BusinessProcess("billingProcess") BusinessProcessEvent evt@StartActivity: restricts the set of events by a certain activity. For example:
@Observes @StartActivity("shipGoods") BusinessProcessEvent evt is invoke whenever an activity with the
id "shipGoods" is entered.
@EndActivity: restricts the set of events by a certain activity. For example:
@Observes @EndActivity("shipGoods") BusinessProcessEvent evt is invoke whenever an activity with the
id "shipGoods" is left.
@TakeTransition: restricts the set of events by a certain transition.
The qualifiers named above can be combined freely. For example, in order to receive all events generated when
leaving the "shipGoods" activity in the "shipmentProcess", we could write the following observer method:
public void beforeShippingGoods(@Observes @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// handle event
}
In the default configuration, event listeners are invoked synchronously and in the context of the same transaction.
CDI transactional observers (only available in combination with JavaEE / EJB), allow to control
when the event is handed to the observer method. Using transactional observers, we can for example assure that an observer is only
notified if the transaction in which the event is fired succeeds:
public void onShipmentSuceeded(@Observes(during=TransactionPhase.AFTER_SUCCESS) @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// send email to customer.
}
Additional FeaturesThe ProcessEngine as well as the services are available for injection: @Inject ProcessEngine, RepositoryService, TaskService, ...The current process instance and task can be injected: @Inject ProcessInstance, Task,The current business key can be injected: @Inject @BusinessKey String businessKey,The current process instance id be injected: @Inject @ProcessInstanceId String pid,Known Limitations
Although activiti-cdi is implemented against the SPI and designed to be a "portable-extension" it is only tested using Weld.