text
|
no
|
The content of the e-mail, in case one needs to send plain none-rich e-mails.
Can be used in combination with
html
, for e-mail clients
that don't support rich content. The client will then fall back to this text-only alternative.
Execution listeners allow you to execute external Java code or evaluate an expression when certain events occur
during process exevcution. The events that can be captured are:
The following process definition contains 3 execution listenerss:
<process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionlistener.ExampleExecutionListenerOne" event="start" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleExecutionListenerTwo" />
</extensionElements>
</sequenceFlow>
<userTask id="secondTask" >
<extensionElements>
<activiti:executionListener expression="${myPojo.myMethod(execution.event)}" event="end" />
</extensionElements>
</userTask>
<sequenceFlow sourceRef="secondTask" targetRef="thirdTask" />
<userTask id="thirdTask" />
<sequenceFlow sourceRef="thirdTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
The first execution listener is notified when the process starts. The listener is an external Java-class (like
ExampleExecutionListenerOne
)
and should implement
org.activiti.engine.impl.pvm.delegate.ExecutionListener
interface. When the event occurs (in this case
end
event)
the method
notify(ExecutionListenerExecution execution)
is called.
public class ExampleExecutionListenerOne implements
ExecutionListener
{
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("variableSetInExecutionListener", "firstValue");
execution.setVariable("eventReceived", execution.getevent());
It is also possible to use a delegation class that implements the
org.activiti.engine.delegate.JavaDelegate
interface. These delegation classes can then be reused in other constructs, such as a delegation for a serviceTask.
The second execution listener is called when the transition is taken. Note that the
listener
element doesn't define an
event
, since only
take
events are fired on transitions.
Values in the
event
attribute are ignored when a listener is defined on a transition.
The last execution listener is called when activity
secondTask
ends. Instead of using the
class
on the listener declaration,
a
expression
is defined instead which is evaluated/invoked when the event is fired.
<activiti:executionListener expression="
${myPojo.myMethod(execution.eventName)}
" event="end" />
As with other expressions, execution variables are resolved and can be used. Because the execution implementation object has a property that exposes the event name, it's
possible to pass the event-name to your methods using
execution.eventName
.
Execution listeners also support using a
delegateExpression
,
similar to a service task
.
<activiti:executionListener event="start" delegateExpression="${myExecutionListenerBean}" />
When using an execution listener that is configured with the
class
attribute, field injection can be applied. This is exactly the same
mechanism as used
Service task field injection
, which contains an overview of the possibilities provided by field injection.
The fragment below shows a simple example process with an execution listener with fields injected.
<process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener" event="start">
<activiti:field name="fixedValue" stringValue="Yes, I am " />
<activiti:field name="dynamicValue" expression="${myVar}" />
</activiti:executionListener>
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
public class ExampleFieldInjectedExecutionListener implements ExecutionListener {
private Expression fixedValue;
private Expression dynamicValue;
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("var",
fixedValue.getValue(execution).toString()
+
dynamicValue.getValue(execution).toString()
);
The class
ExampleFieldInjectedExecutionListener
concatenates the 2 injected fields (one fixed an the other dynamic) and stores this in the process variable '
var
'.
@Deployment(resources = {"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("myVar", "listening!");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess", variables);
Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
assertNotNull(varSetByListener);
assertTrue(varSetByListener instanceof String);
// Result is a concatenation of fixed injected field and injected expression
assertEquals("Yes, I am listening!", varSetByListener);
A
task listener
is used to execute custom Java logic or an expression
upon the occurrence of a certain task-related event.
A task listener can only be added in the process definition as a child element of a
user task
.
Note that this also must happen as a child of the
BPMN 2.0 extensionElements
and in the
activiti
namespace, since a task listener is an Activiti-specific construct.
<userTask id="myTask" name="My Task" >
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyTaskCreateListener" />
</extensionElements>
</userTask>
A
task listener
supports following attributes:
event
(required): the type of task event on which the task listener will
be invoked. Possible values are
'create'
(occurs when the task
has been created an all task properties are set),
'assignment'
(occurs when the task is assigned to somebody) or
'complete'
(occurs when the task is completed and just before the task is deleted from the runtime data).
class
: the delegation class that must be called.
This class must implement the
org.activiti.engine.impl.pvm.delegate.TaskListener
interface.
public class MyTaskCreateListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Custom logic goes here
It is also possible to use
field injection
to pass
process variables or the execution to the delegation class.
Note that an instance of the delegation class is created upon process deployment
(as is the case with any class delegation in Activiti), which means that the
instance is shared between all process instance executions.
expression
: (cannot be used together with the
class
attribute):
specifies an expression that will be executed when the event happens.
It is possible to pass the
DelegateTask
object and the name
of the event (using
task.eventName
) as parameter to the called object.
<activiti:taskListener event="create" expression="${myObject.callMethod(task, task.eventName)}" />
delegateExpression
allows to specify an expression
that resolves to an object implementing ther
TaskListener
interface,
similar to a service task
.
<activiti:taskListener event="create" delegateExpression="${myTaskListenerBean}" />
To make an activity multi-instance, the activity xml element must have a
multiInstanceLoopCharacteristics
child element.
<multiInstanceLoopCharacteristics isSequential="false|true">
</multiInstanceLoopCharacteristics>
The
isSequential
attribute indicates if the
instances of that activity are executed sequentially or parallel.
The number of instances are
calculated once, when entering the activity
.
There are a few ways of configuring this. On way is directly specifying a number, by using
the
loopCardinality
child element.
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>
Expressions that resolve to a postive number are also possible:
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>
Another way to define the number of instances, is to specify the name of a process variable which is a collection using the
loopDataInputRef
child element. For each item in the collection, an instance will be created.
Optionally, it is possible to set that specific item of the collection for the instance
using the
inputDataItem
child element. This is shown in the following
XML example:
<userTask id="miTasks" name="My Task ${loopCounter}" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assigneeList</loopDataInputRef>
<inputDataItem name="assignee" />
</multiInstanceLoopCharacteristics>
</userTask>
Suppose the variable
assigneeList
contains the values
[kermit, gonzo, foziee]
.
In the snippet above, three user tasks will be created in parallel. Each of the executions
will have a process variable named
assignee
containing one value of the
collection, which is used to assign the user task in this example.
The downside of the
loopDataInputRef
and
inputDataItem
is that 1) the names are pretty hard to remember and 2) due to the BPMN 2.0 schema restrictions
they can't contain expressions. Activiti solves this by offering the
collection
and
elementVariable
attributes on the
multiInstanceCharacteristics
:
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
activiti:collection="${myService.resolveUsersForTask()}" activiti:elementVariable="assignee"
>
</multiInstanceLoopCharacteristics>
</userTask>
A multi-instance activity ends when all instances are finished. However, it is possible
to specify an expression that is evaluated every time one instance ends. When this
expression evaluates to true, all remaining instances are destroyed and the multi-instance
activity ends, continuing the process. Such an expression must be defined in
the
completionCondition
child element.
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="assigneeList" activiti:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
In this example, there will be parallel instances created for each element of the
assigneeList
collection. However, when 60% of the tasks are completed, the other tasks are deleted
and the process continues.
Since a multi-instance is a regular activity, it is possible to define a
boundary event
on its boundary.
In case of an interrupting boundary event, when the event is catched,
all instances
that are still active will be destroyed. Take for example following multi-instance subprocess:
Here, all instances of the subprocess will be destroyed when the timer fires, regardless
of how many instances there are or which inner activities are currently not yet completed.
A timer boundary event is defined as a
regular boundary event
.
The specific type sub-element is in this case a
timerEventDefinition
element.
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>PT4H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
To specify how long the timer should run before it is fired, a
timeDuration
can be specified as sub-element of
timerEventDefinition
. The format
used is the
ISO 8601
format (as required by the BPMN 2.0 specification).
You can use expressions for the timer duration, by doing so you can influence the timer duration
based on process variables. The process variables must contain the ISO 8601 string for the duration.
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>${duration}</timeDuration>
</timerEventDefinition>
</boundaryEvent>
In the grafical representation, the line of the circle is dotted as you can see in this example above:
A typical use case is sending an escalation email additionally but not interrupt the normal process flow.
Note:
boundary timer events are only fired when
the job executor is enabled (i.e.
jobExecutorActivate
needs to be set to
true
in the
activiti.cfg.xml
, since the job
executor is disabled by default).
A boundary error event is defined as a typical
boundary event
:
<boundaryEvent id="catchError" attachedToRef="mySubProcess">
<errorEventDefinition errorRef="myError"/>
</boundaryEvent>
As with the
error end event
, the
errorRef
references an error defined outside the process element:
<error id="myError" errorCode="123" />
<process id="myProcess">
The
errorCode
is used to match the errors that are catched:
If
errorRef
is omitted, the boundary error event will catch
any error event
, regardless of the errorCode of the
error
.
In case an
errorRef
is provided and it references an existing
error
,
the boundary event will
only catch errors with the same error code
.
In case an
errorRef
is provided, but no
error
is defined in the BPMN 2.0 file, then the
errorRef is used
as errorCode
(similar for with error end events).
BPMN 2.0 makes a distinction between a regular
subprocess
,
often also called
embedded subprocess
, and the call activity, which
looks very similar. From a conceptual point of view, both will call a subprocess when
process execution arrives at the activity.
The difference is that the call activity references a process that is external to
the process definition, whereas the
subprocess
is embedded within the original process definition. The main use case for the call
activity is to have a reusable process definition that can be called from multiple
other process definitions.
When process execution arrives in the
call activity
, a new
execution is created that is a sub-execution of the execution that arrives in the
call activity. This sub-execution is then used to execute the subprocess, potentially
creating parallel child execution as within a regular process. The super-execution
waits until the subprocess is completely ended, and continues the original
process afterwards.
Activiti provides a convenient and flexible way to add forms for the manual steps of
your business processes. We support two strategies to work with forms:
Build-in form rendering and external form rendering
Build-in form rendering is the simplest to get started with. We'll explain it
with an example.
The demo setup script installs the
vacationRequest
business process
as an example of using task forms through Activiti Explorer. Please check the example
for the complete source code. The business process diagram looks like this:
To use the build-in rendering, the form files have to be included in the deployment.
That can be done programmatically like this:
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("org/activiti/examples/taskforms/VacationRequest.bpmn20.xml")
.addClasspathResource("org/activiti/examples/taskforms/approve.form")
.addClasspathResource("org/activiti/examples/taskforms/request.form")
.addClasspathResource("org/activiti/examples/taskforms/adjustRequest.form")
.deploy();
Other ways of deploying your processes/forms, can be found in chapter Deployment.
The BPMN 2.0 specification does not specify how tasks or task forms should be rendered,
as such forms are defined using Activiti specific constructs.
There is the attribute activiti:formKey that can be specified on
startEvent s and userTask s.
<startEvent id="request"
activiti:formKey="org/activiti/examples/taskforms/request.form" />
<sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest" />
<userTask id="handleRequest" name="Handle vacation request"
activiti:formKey="org/activiti/examples/taskforms/approve.form" >
<documentation>
Vacation request by ${employeeName}
</documentation>
</userTask>
The activiti:formKey attribute can contain any text value which you use to identify your form in case
you do your own form rendering. But the build-in form rendering
expects the activiti:formKey to be a reference to a resource in the
same business archive (= deployment). The resource is identify by it's full path within the deployment. In our case,
the form request.form was deployed within a Business-acrhive, in folder org/activiti/examples/taskforms/ .
Activiti Explorer uses the build-in form rendering engines. Currently, there is
only one form rendering engine configured, which is Juel. So it resolves resource files as
a Juel expression and
the resulting HTML String is sent to the client. In the future, we
hope to add a FreeMarker form engine, but that will require more library dependencies
so we opt for Juel as the default form engine.
Here is the rendered form defined in resource org/activiti/examples/taskforms/request.form .
Its a form to capture the data necessary to start a new process instance.
And here is the contents of that form file:
<h1>Vacation Request</h1>
<table>
<label>
Employee name:<br/>
<input type="text" name="employeeName" value="" />
<input type="hidden" name="employeeName_required" value="true" />
<input type="hidden" name="employeeName_type" value="User" />
</label><br/>
<label>
Number of days:<br/>
<input type="number" name="numberOfDays" value="1" min="1" />
<input type="hidden" name="numberOfDays_type" value="Integer" />
</label>
<label>
First day of vacation: (YYYY-MM-DD)<br/>
<input type="date" name="startDate"/>
<input type="hidden" name="startDate_type" value="Date"/>
</label>
<label>
Date of return to work: (YYYY-MM-DD)<br/>
<input type="date" name="returnDate"/>
<input type="hidden" name="returnDate_type" value="Date"/>
</label>
<label>
<input type="checkbox" name="vacationPay"/> Vacation pay requested
<input type="hidden" name="vacationPay_boolean" value="true"/>
</label>
<label>
Motivation:<br/>
<textarea name="vacationMotivation" value=""></textarea>
</label>
</table>
<EXPERIMENTAL>
The mechanism of using hidden fields to provide extra information such as type and required
will be changed in one of the subsequent releases. In the future this type of metadata will
be based on FormService.getStartFormData and FormService.getTaskFormData
The hidden
fields provide extra information to the Activiti Explorer client application.
So the Javascript in the browser will use the hidden fields and enhance the corresponding
input fields. For example, it's possible to specify that a certain text field is a date
and Activiti Explorer will add a date picker.
Variable names must be camel-cased.
The default type of the process variable that is stored is string.
Use a hidden field with the input variable name followed by '_type' to define the type (and hence also the conversion from the HTML
input to the variable): <input type="hidden" name="numberOfDays_type" value="Integer" />
Currently supported are String, Integer, Boolean, Date.
Input variables can be made required by adding a hidden field with the input variable name followed
by '_required':
<input type="hidden" name="employeeName_required" value="true" />
In Activiti-Explorer, the Date type validates to ISO 8601 (YYYY-MM-DD). This field will also use any native date picker
tools in the browser (using the HTML5 input type="date") and fall back on a pop-up date picker using the YUI calendar widget. It is,
of course still possible to manually enter your own date, which is validated as you type.
The Integer form field in the demo has also been enhanced to make use of HTML5 input type=number, which provides native validation and custom input
fields in the supported browser(s), although Activiti-Explorer provides client side validation as well.
It is expected that Activiti Explorer in the future will use FormService.getStartFormData
instead of these hidden field values to enhance the form input fields.
That's why the
hidden fields part is marked as experimental. </EXPERIMENTAL>
Use FormService.getRenderedStartForm to get the rendered form string with the API, using the default form engine (JUEL): String FormService.getRenderedStartForm(String processDefinitionId) Use FormService.submitStartFormData to start a new process instance with
the properties that the user provided as input for the form: ProcessDefinition FormService.submitStartFormData(String processDefinitionId, Map<String, String> properties) To learn about the difference between starting a new process instance with this FormService method
in comparison with the ProcessInstance RuntimeService.startProcessInstanceById(String processDefinitionId) ,
read the section called “Form properties”
After submitting the form, a process instance is started and now someone of the
management team needs to handle the request.
The corresponding user task has a task form attached to it, which uses the
variables which were given as input by the employee in the start form. These variables
are referenced as expressions and are resolved at runtime to their text representation.
<h1>Vacation Approval</h1>
${employeeName} would like to take ${numberOfDays} day(s) of vacation.
Motivation: ${vacationMotivation}
Do you approve this?
<select name="vacationApproved">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
<input type="hidden" name="vacationApproved_type" value="Boolean" />
<label>
Motivation:<br/>
<textarea name="managerMotivation" value=""></textarea>
</label>
The manager will now indicate in the form whether the vacation request is approved or not,
by selecting the appropriate input in the form.
The result is stored in as a process variable,
which is then used in the exclusive gateway after the form is submitted.
<sequenceFlow id="flow5" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
<conditionExpression xsi:type="tFormalExpression">${!vacationApproved}</conditionExpression>
</sequenceFlow>
Depending on the input of the manager in the user task, the employee will now
get a new task in his personal task list, stating that the vacation request was
disapproved and it needs can be refilled if wanted.
The employee can now choose to resend the vacation request, which brings process
execution again in the user task for the manager. Or the employee can throw away
the request, ending the process.
<h1>Adjust vacation Request</h1>
Your manager has disapproved your vacation request for ${numberOfDays} days. <br/>
Reason: ${managerMotivation}
Number of days:<br/>
<input type="text" name="numberOfDays" value="${numberOfDays}" />
<input type="hidden" name="numberOfDays_type" value="Integer" />
Above we showed the build-in task form rendering. But the API also allows for you to perform your
own task form rendering outside of the Activiti Engine. These steps explain the hooks that you can use to
render your task forms yourself.
Essentially, all the data that's needed to render a form is assembled in one of these two service methods:
StartFormData FormService.getStartFormData(String processDefinitionId)
and
TaskFormdata FormService.getTaskFormData(String taskId)
.
Submitting form properties can be done with
ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)
and
void FormService.submitStartFormData(String taskId, Map<String,String> properties)
To learn about how form properties map to process variables, see
the section called “Form properties”
You can place any form template resource inside the business archives that you deploy (in case
you want to store them versioned with the process). It will be available as a resource in the deployment, which you can retrieve using:
String ProcessDefinition.getDeploymentId()
and
InputStream RepositoryService.getResourceAsStream(String deploymentId, String resourceName);
This could be your template definition file, which you can use to render/show the form in your
own application.
You can use this capability of accessing the deployment resources beyond task forms for any other
purposes as well.
The attribute
<userTask activiti:formKey="..."
is exposed by the API through
String FormService.getStartFormData(String processDefinitionId).getFormKey()
and
String FormService.getTaskFormData(String taskId).getFormKey()
. You could use this to
store the full name of the template within your deployment (eg.
org/activiti/example/form/my-custom-form.xml
), but this is not required at all.
For instance, you could also store a generic key in the form
attribute and apply an algorithm or transformation to get to the actual template that needs to be used.
This might be handy when you want to render different forms for different UI technologies like e.g.
one form for usage in a web app of normal screen size, one form for mobile phone's small screens and
maybe even a template for a IM form or an email form.
All information relevant to a business process is either included in the process
variables themselves or referenced through the process variables. Activiti supports
complex Java objects to be stored as process variables like
Serializable
objects, JPA entities or whole XML documents as
String
s.
Starting a process and completing tasks is where people are involved into a process.
Communicating with people requires forms to be rendered in some UI technology.
In order to facilitate multiple UI technologies easy, the process definition
can includes the logic of transforming of the complex Java typed objects in the process variables
to a
Map<String,String>
of properties.
Any UI technology can then build a form on top of those properties.
The properties can provide a dedicated (and more limited) view on the process variables.
The properties needed to display a form are available in the FormData returnvalues of
StartFormData FormService.getStartFormData(String processDefinitionId)
and
TaskFormdata FormService.getTaskFormData(String taskId)
.
Those properties are obtained from the process variables.
By default, the build-in form engines, will 'see' the properties as well
as the process variables. So there is no need to declare task form properties
if they match 1-1 with the process variables. For example, with the following
declaration:
<startEvent id="start" />
A form will see all the process variables but the
formService.getStartFormData(String processDefinitionId).getFormProperties()
will be empty.
In the above case, all the submitted properties will be stored as process variables.
This means that by simply adding a new inputfield in the form, a new variable can be stored.
Properties are derived from process variables, but they don't have to be stored
as process variables. For example, a process variable could be a JPA entity of
class Address. And a form property
StreetName
used by the UI
technology could be linked with an expression
#{address.street}
Analogue, the properties that a user is supposed to submit in a form can be
stored as a process variable or as a nested property in one of the process variables
with a UEL value expression like e.g.
#{address.street}
.
Analogue the default behavior of properties that are submitted is that
they will be stored as process variables unless a
formProperty
declaration specifies otherwise.
Also type conversions can be applied as part of the processing between
form properties and process variables.
For example:
<userTask id="task">
<extensionElements>
<activiti:formProperty id="room" />
<activiti:formProperty id="duration" type="long"/>
<activiti:formProperty id="speaker" variable="SpeakerName" writable="false" />
<activiti:formProperty id="street" expression="#{address.street}" required="true" />
</extensionElements>
</userTask>
It's also possible to provide type meta data as part of the FormData that is
returned from methods
StartFormData FormService.getStartFormData(String processDefinitionId)
and
TaskFormdata FormService.getTaskFormData(String taskId)
We support following form property types:
For each form property declared, the following
FormProperty
information will be made available through
List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties()
and
List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()
public interface FormProperty {
/** the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}
* or {@link FormService#submitTaskFormData(String, java.util.Map)} */
String getId();
/** the display label */
String getName();
/** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */
FormType getType();
/** optional value that should be used to display in this property */
String getValue();
/** is this property read to be displayed in the form and made accessible with the methods
* {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */
boolean isReadable();
/** is this property expected when a user submits the form? */
boolean isWritable();
/** is this property a required input field */
boolean isRequired();
}
For example:
<startEvent id="start">
<extensionElements>
<activiti:formProperty id="speaker"
name="Speaker"
variable="SpeakerName"
type="string" />
<activiti:formProperty id="start"
type="date"
datePattern="dd-MMM-yyyy" />
<activiti:formProperty id="direction" type="enum">
<activiti:value id="left" name="Go Left" />
<activiti:value id="right" name="Go Right" />
<activiti:value id="up" name="Go Up" />
<activiti:value id="down" name="Go Down" />
</activiti:formProperty>
</extensionElements>
</startEvent>
All that information is accessible through the API. The type names can be
obtained with
formProperty.getType().getName()
. And even the
date pattern is available with
formProperty.getType().getInformation("datePattern")
and the enumeration values are accessible with
formProperty.getType().getInformation("values")
You can use JPA-Entities as process variables, allowing you to:
-
Updating existing JPA-entities based on process variables, that can be filled in on a form in a userTask or generated in a serviceTask.
-
Reusing existing domain model without having to write explicit services to fetch the entities and update the values
-
Make decisions (gateways) based on properties of existing entities.
-
...
Examples for using JPA variables can be found in
JPAVariableTest
. We'll explain
JPAVariableTest.testUpdateJPAEntityValues
step by step.
First of all, we create a
EntityManagerFactory
for our persistence-unit, which is based on
META-INF/persistence.xml
. This contains classes which should be included in
the persistence unit and some vendor-specific configuration.
We are using a simple entity in the test, having an id and
String
value property, which is also persisted. Before running the test, we create an entity and save this.
@Entity(name = "JPA_ENTITY_FIELD")
public class FieldAccessJPAEntity {
@Column(name = "ID_")
private Long id;
private String value;
public FieldAccessJPAEntity() {
// Empty constructor needed for JPA
public Long getId() {
return id;
public void setId(Long id) {
this.id = id;
public String getValue() {
return value;
public void setValue(String value) {
this.value = value;
We start a new process instance, adding the entity as a variable. As with other variables, they are stored in the persistent storage of the engine. When the variable is requested the next time, it will be loaded from the
EntityManager
based on the class and Id stored.
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("entityToUpdate", entityToUpdate);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("UpdateJPAValuesProcess", variables);
The first node in our process definition contains a
serviceTask
that will invoke the method
setValue
on
entityToUpdate
, which resolves to
the JPA variable we set earlier when starting the process instance and will be loaded from the
EntityManager
associated with the current engine's context'.
<serviceTask id='theTask' name='updateJPAEntityTask' activiti:expression="${entityToUpdate.setValue('updatedValue')}" />
When the service-task is finished, the process instance waits in a userTask defined in the process definition, which allows us to inspect the process instance. At this point, the
EntityManager
has been flushed
and the changes to the entity have been pushed to the database. When we get the value of the variable
entityToUpdate
, it's loaded again and we get
the entity with it's
value
property set to
updatedValue
.
// Servicetask in process 'UpdateJPAValuesProcess' should have set value on entityToUpdate.
Object updatedEntity = runtimeService.getVariable(processInstance.getId(), "entityToUpdate");
assertTrue(updatedEntity instanceof FieldAccessJPAEntity);
assertEquals("updatedValue", ((FieldAccessJPAEntity)updatedEntity).getValue());
A more advanced example,
JPASpringTest
, can be found in
activiti-spring-examples
. It describes the following simple use case:
-
An existing Spring-bean which uses JPA entities already exists which allows for Loan Requests to be stored.
-
Using Activiti, we can use the existing entities, obtained through the existing bean, and use them as variable in our process.
Process is defined in the following steps:
-
Service task that creates a new LoanRequest, using the existing
LoanRequestBean
using variables received
when starting the process (e.g. could come from a start form). The created entity is stored as a variable, using
activiti:resultVariable
which stores the expression result as a variable.
-
UserTask that allows a manager to review the request and approve/disapprove, which is stored as a boolean variable
approvedByManager
-
ServiceTask that updates the loan request entity so the entity is in sync with the process.
-
Depending on the value of the entity property
approved
, an exclusive gateway is used to make a decision
about what path to take next: When the request is approved, process ends, otherwise, an extra task will become available (Send rejection letter), so the
customer can be notified manually by a rejection letter.
Please note that the process doesn't contain any forms, since it is only used in a unit test.
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="taskAssigneeExample"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="org.activiti.examples">
<process id="LoanRequestProcess" name="Process creating and handling loan request">
<startEvent id='theStart' />
<sequenceFlow id='flow1' sourceRef='theStart' targetRef='createLoanRequest' />
<serviceTask id='createLoanRequest' name='Create loan request'
activiti:expression="${loanRequestBean.newLoanRequest(customerName, amount)}"
activiti:resultVariable="loanRequest"/>
<sequenceFlow id='flow2' sourceRef='createLoanRequest' targetRef='approveTask' />
<userTask id="approveTask" name="Approve request" />
<sequenceFlow id='flow3' sourceRef='approveTask' targetRef='approveOrDissaprove' />
<serviceTask id='approveOrDissaprove' name='Store decision'
activiti:expression="${loanRequest.setApproved(approvedByManager)}" />
<sequenceFlow id='flow4' sourceRef='approveOrDissaprove' targetRef='exclusiveGw' />
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway approval" />
<sequenceFlow id="endFlow1" sourceRef="exclusiveGw" targetRef="theEnd">
<conditionExpression xsi:type="tFormalExpression">${loanRequest.approved}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="endFlow2" sourceRef="exclusiveGw" targetRef="sendRejectionLetter">
<conditionExpression xsi:type="tFormalExpression">${!loanRequest.approved}</conditionExpression>
</sequenceFlow>
<userTask id="sendRejectionLetter" name="Send rejection letter" />
<sequenceFlow id='flow5' sourceRef='sendRejectionLetter' targetRef='theOtherEnd' />
<endEvent id='theEnd' />
<endEvent id='theOtherEnd' />
</process>
</definitions>
Although the example above is quite simple, it shows the power of using JPA combined with Spring and parametrized method-expressions. The process requires
no custom java-code at all (except for the Spring-bean off course) and speeds up development drastically.
History is the component that captures what happened
during process execution and stores it permanently. In contrast to the runtime data,
the history data will remain present in the DB also after process instances have completed.
There are 4 history entities:
-
HistoricProcessInstance
s containing information about current and past process instances.
-
HistoricActivityInstance
s containing information about a single execution of an activity.
-
HistoricTaskInstance
s containing information about current and past (completed and deleted) task instances.
-
HistoricDetail
s containing various kinds of information related to either a historic process instances, an activity instance or a task instance.
Since the DB contains historic entities for past as well as ongoing instances, you might want to consider
querying these tables in order to minimize access to the runtime process instance data
and that way keeping the runtime execution performant.
Later on, this information will be exposed in Activiti Explorer and Activiti Probe.
Also, it will be the information from which the reports will be generated.
When
configuring
at least
audit
level
for configuration. Then all properties submitted through methods
FormService.submitStartFormData(String processDefinitionId, Map<String, String> properties)
and
FormService.submitTaskFormData(String taskId, Map<String, String> properties)
are recorded.
[KNOWN LIMITATION]
Currently the forms as worked out in Activiti Explorer
do not yet use the
submitStartFormData
and
submitTaskFormData
.
So the form properties are not yet archived when using the forms in Activity Explorer. A workaround is to set the historyLevel to full and use the variableUpdates to see what values were set in userTasks.
@see
ACT-294
Form properties can be retrieved with the query API like this:
historyService
.createHistoricDetailQuery()
.onlyFormProperties()
.list();
In that case only historic details of type
HistoricFormProperty
are returned.
If you've set the authenticated user before calling the submit methods with
IdentityService.setAuthenticatedUserId(String)
then that authenticated user
who submitted the form will be accessible in the history as well with
HistoricProcessInstance.getStartUserId()
for start forms and
HistoricActivityInstance.getAssignee()
for task forms.
Activiti comes with an Eclipse plugin, the Activiti Eclipse Designer, that can be used to
graphically model, test and deploy BPMN 2.0 processes. The Activiti tool stack offers two modeling / design tools
with the Activiti Modeler and the Activiti Designer. You can of course use these tools in your own way, but a common way
of working is to use the Activiti Modeler for high level modeling. There should be no technical knowledge necessary to
model a process definition with the Activiti Modeler. The Activiti Designer can then be used to add the necessary
technical details, like Java service tasks, execution listeners etc. With the import functionality of the Activiti Designer
this workflow is well supported.
Please note: Activiti Designer is still in a "beta" state.
This means that we don't yet
support version compatibility. Although, with the BPMN 2.0 XML support of the Activiti Designer you should be able to
upgrade your process definitions to new versions of the Activiti Designer quite easily. Also note that not all BPMN 2.0
constructs that are supported by the Activiti Engine are supported in the Activiti Designer.
Support for start event, end event, sequence flow, parallel gateway, exclusive
gateway, embedded subprocess, call activity, script task, user task, service task, mail task, manual
task and timer boundary events.
Java class, expression or delegate expression configuration is supported for the Java service task. In
addition field extensions can be configured.
Support for expanded embedded sub processes. For this version no hierarchical support for embedded sub processes
is provided. This means you can't add an embedded sub process in another embedded sub process.
Support for timer boundary events on tasks and embedded sub processes. Although, the timer boundary event makes
the most sense when using it on a user task or an embedded sub process in the Activiti Designer.
Support for additional Activiti extensions like the Mail task, the candidate
configuration of User tasks and Script task configuration.
Support for the Activiti execution and task listeners. You can also add field extensions for execution listeners.
Support for conditions on sequence flows.
[EXPERIMENTAL]
You can extend the default functionality
offered by Activiti Designer. This section documents which extensions are available, how they
can be used and provides some usage examples. Extending Activiti Designer is useful in cases
where the default functionality doesn't suit your needs, you require additional capabilities
or have domain specific requirements when modelling business processes. Extension of Activiti
Designer falls into two distinct categories, extending the palette and extending output
formats. Each of these extension ways requires a specific approach and different technical
expertise.
Extending Activiti Designer requires technical knowledge and more specifically,
knowledge of programming in Java. Depending on the type of extension you want to create,
you might also need to be familiar with Maven, Eclipse, OSGi, Eclipse extensions and
You can customize the palette that is offered to users when modelling processes. The
palette is the collection of shapes that can be dragged onto the canvas in a process diagram
and is displayed to the right hand side of the canvas. As you can see in the default
palette, the default shapes are grouped into compartments (these are called "drawers") for
Events, Gateways and so on. There are two options built-in to Activiti Designer to customize
the drawers and shapes in the palette:
In order to customize the palette, you create a JAR file that is added to a specific
installation of Activiti Designer (more on
how
to do that
later). Such a JAR file is called an
extension
. By
writing classes that are included in your extension, Activiti Designer understands which
customizations you wish to make. In order for this to work, your classes should implement
certain interfaces. There is an integration library available with those interfaces and base
classes to extend which you should add to your project's classpath.
You can find the code examples listed below in source control with Activiti Designer.
Take a look in the
examples/money-tasks
directory in the
projects/designer
directory of Activiti's source code.
Note
You can setup your project in whichever tool you prefer and build the JAR with your
build tool of choice. For the instructions below, a setup is assumed with Eclipse
Helios, using Maven (3.x) as build tool, but any setup should enable you to create the
same results.
Download and extract
Eclipse
(Galileo or Helios should both work) and a recent version (3.x) of
Apache Maven
. If you use a 2.x
vesion of Maven, you will run into problems when building your project, so make sure your
version is up to date. We assume you are familiar with using basic features and the Java
editor in Eclipse. It's up to you whether your prefer to use Eclipse's features for Maven
or run Maven commands from a command prompt.
Create a new project in Eclipse. This can be a general project type. Create a
pom.xml
file at the root of the project to contain the Maven project
setup. Also create folders for the
src/main/java
and
src/main/resources
folders, which are Maven conventions for your Java
source files and resources respectively. Open the
pom.xml
file and add
the following lines:
<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.acme</groupId>
<artifactId>money-tasks</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Acme Corporation Money Tasks</name>
As you can see, this is just a basic pom.xml file that defines a
groupId , artifactId and version
for the project. We will create a customization that includes a single custom node for our
money business. Add the integration library to your project's dependencies by including this
dependency in your pom.xml file:
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-designer-integration</artifactId>
<version>0.7.0</version> <!-- Current Activiti Designer Version -->
<scope>compile</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>Activiti</id>
<url>http://maven.alfresco.com/nexus/content/repositories/activiti/</url>
</repository>
</repositories>
Finally, in the pom.xml file, add the configuration for the
maven-compiler-plugin so the Java source level is at least 1.5 (see
snippet below). You will need this in order to use annotations. You can also include
instructions for Maven to generate the JAR's MANIFEST.MF file. This is
not required, but you can use a specific property in the manifest to provide a name for
your extension (this name may be shown at certain places in the designer and is primarily
intended for future use if you have several extensions in the designer). If you wish to do
so, include the following snippet in pom.xml :
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<optimize>true</optimize>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<index>true</index>
<manifest>
<addClasspath>false</addClasspath>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<ActivitiDesigner-Extension-Name>Acme Money</ActivitiDesigner-Extension-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
The name for the extension is described by the
ActivitiDesigner-Extension-Name property. The only thing left to do
now is tell Eclipse to setup the project according to the instructions in
pom.xml . So open up a command shell and go to the root folder of your
project in the Eclipse workspace. Then execute the following Maven command:
mvn eclipse:eclipse
Wait until the build is successful. Refresh the project (use the project's context
menu (right-click) and select Refresh ). You should now have the
src/main/java and src/main/resources folders as
source folders in the Eclipse project. NoteYou can of course also use the m2eclipse plugin and simply enable Maven dependency management from the
context menu (right-click) of the project. Then choose Maven >
Update project configuration from the project's context menu. That
should setup the source folders as well. That's it for the setup. Now you're ready to start creating customizations to Activiti
Designer!
With your project set up, you can now easily add shapes to the palette. Each shape you
wish to add is represented by a class in your JAR. Take note that these classes are not
the classes that will be used by the Activiti engine during runtime. In your extension you
describe the properties that can be set in Activiti Designer for each shape. From these
shapes, your refer to the runtime class that should be used by the engine. This class
should implement JavaDelegate as for any ServiceTask in Activiti.
A shape's class is a simple Java class, to which a number of annotations are added.
The class should implement the
CustomServiceTask
interface, but you
shouldn't implement this interface yourself. Extend the
AbstractCustomServiceTask
base class instead (at the moment you MUST
extend this class directly, so no abstract classes in between). In the Javadoc for that
class you can find instructions on the defaults it provides and when you should override
any of the methods it already implements. Overrides allow you to do things such as
providing icons for the palette and in the shape on the canvas (these can be different)
and specifying the base shape you want the node to have (activity, event, gateway).
* @author John Doe
* @version 1
* @since 1.0.0
public class AcmeMoneyTask extends AbstractCustomServiceTask {
You will need to implement the
getName()
method to determine the
name the node will have in the palette. You can also put the nodes in their own drawer and
provide an icon. Override the appropriate methods from
AbstractCustomServiceTask
. If you want to provide an icon, make sure
it's in the
src/main/resources
package in your JAR and is about 16x16
pixels and a JPEG or PNG format. The path you supply is relative to that folder.
You can add properties to the shape by adding members to the class and annotating them
with the
@Property
annotation like this:
@Property(type = PropertyType.TEXT, displayName = "Account Number")
@Help(displayHelpShort = "Provide an account number", displayHelpLong = HELP_ACCOUNT_NUMBER_LONG)
private String accountNumber;
There are several
PropertyType
values you can use, which are
described in more detail in
this section
. You
can make a field required by setting the required attribute to true. A message and red
background will appear if the user doesn't fill out the field.
If you want to ensure the order of the various properties in your class as they appear
in the property screen, you should specify the order attribute of the
@Property
annotation.
As you can see, there's also an
@Help
annotation that's used to
provide the user some guidance when filling out the field. You can also use the
@Help
annotation on the class itself - this information is shown at
the top of the property sheet presented to the user.
Below is the listing for a further elaboration of the
MoneyTask
. A
comment field has been added and you can see an icon is included for the node.
* @author John Doe
* @version 1
* @since 1.0.0
@Runtime(delegationClass = "org.acme.runtime.AcmeMoneyJavaDelegation")
@Help(displayHelpShort = "Creates a new account", displayHelpLong = "Creates a new account using the account number specified")
public class AcmeMoneyTask extends AbstractCustomServiceTask {
private static final String HELP_ACCOUNT_NUMBER_LONG = "Provide a number that is suitable as an account number.";
@Property(type = PropertyType.TEXT, displayName = "Account Number", required = true)
@Help(displayHelpShort = "Provide an account number", displayHelpLong = HELP_ACCOUNT_NUMBER_LONG)
private String accountNumber;
@Property(type = PropertyType.MULTILINE_TEXT, displayName = "Comments")
@Help(displayHelpShort = "Provide comments", displayHelpLong = "You can add comments to the node to provide a brief description.")
private String comments;
* (non-Javadoc)
* @see org.activiti.designer.integration.servicetask.AbstractCustomServiceTask #contributeToPaletteDrawer()
@Override
public String contributeToPaletteDrawer() {
return "Acme Corporation";
@Override
public String getName() {
return "Money node";
* (non-Javadoc)
* @see org.activiti.designer.integration.servicetask.AbstractCustomServiceTask #getSmallIconPath()
@Override
public String getSmallIconPath() {
return "icons/coins.png";
If you extend Activiti Designer with this shape, The palette and corresponding node
will look like this:
The properties screen for the money task is shown below. Note the required message for
the
accountNumber
field.
The help for fields is offered by the buttons to the right of each property. Clicking
on the button shows a popup as displayed below.
The final step for your shape is to indicate the class that is instantiated by the
Activiti engine when it reaches your node when executing a process instance. To do this,
you use the
@Runtime
annotation. The
delegationClass
attribute you return should contain the canonical name of the runtime class. Note that the
runtime class shouldn't be in your extension JAR, as it's dependent on the Activiti
libraries.
@Runtime(delegationClass = "org.acme.runtime.AcmeMoneyJavaDelegation")
This section describes the property types you can use for a
CustomServiceTask
by setting its type to a
PropertyType
value.
Creates a group of radio buttons as shown below. Selection of any of the radio
buttons is mutually exclusive with selection of any of the others (i.e., only one
selection allowed). Can be a required field and shows validation messages as a tooltip.
Validation failures are displayed by changing the background of the group to a light red
color.
This property type expects the class member you have annotated to also have an
accompanying
@PropertyItems
annotation (for an example, see below).
Using this additional annotation, you can specify the list of items that should be
offered in an array of Strings. Specify the items by adding two array entries for each
item: first, the label to be shown; second, the value to be stored.
@Property(type = PropertyType.RADIO_CHOICE, displayName = "Withdrawl limit", required = true)
@Help(displayHelpShort = "The maximum daily withdrawl amount ", displayHelpLong = "Choose the maximum daily amount that can be withdrawn from the account.")
@PropertyItems({ LIMIT_LOW_LABEL, LIMIT_LOW_VALUE, LIMIT_MEDIUM_LABEL, LIMIT_MEDIUM_VALUE, LIMIT_HIGH_LABEL, LIMIT_HIGH_VALUE })
private String withdrawlLimit;
Creates a combobox with fixed options as shown below. Can be a required field and
shows validation messages as a tooltip. Validation failures are displayed by changing
the background of the combobox to a light red color.
This property type expects the class member you have annotated to also have an
accompanying
@PropertyItems
annotation (for an example, see below).
Using this additional annotation, you can specify the list of items that should be
offered in an array of Strings. Specify the items by adding two array entries for each
item: first, the label to be shown; second, the value to be stored.
@Property(type = PropertyType.COMBOBOX_CHOICE, displayName = "Account type", required = true)
@Help(displayHelpShort = "The type of account", displayHelpLong = "Choose a type of account from the list of options")
@PropertyItems({ ACCOUNT_TYPE_SAVINGS_LABEL, ACCOUNT_TYPE_SAVINGS_VALUE, ACCOUNT_TYPE_JUNIOR_LABEL, ACCOUNT_TYPE_JUNIOR_VALUE, ACCOUNT_TYPE_JOINT_LABEL,
ACCOUNT_TYPE_JOINT_VALUE, ACCOUNT_TYPE_TRANSACTIONAL_LABEL, ACCOUNT_TYPE_TRANSACTIONAL_VALUE, ACCOUNT_TYPE_STUDENT_LABEL, ACCOUNT_TYPE_STUDENT_VALUE,
ACCOUNT_TYPE_SENIOR_LABEL, ACCOUNT_TYPE_SENIOR_VALUE })
private String accountType;
Creates a date selection control as shown below. Can be a required field and shows
validation messages as a tooltip (note, that the control used will auto-set the
selection to the date on the system, so the value is seldom empty). Validation failures
are displayed by changing the background of the control to a light red color.
This property type expects the class member you have annotated to also have an
accompanying
@DatePickerProperty
annotation (for an example, see
below). Using this additional annotation, you can specify the date time pattern to be
used to store dates in the diagram and the type of datepicker you would like to be
shown. Both attributes are optional and have default values that will be used if you
don't specify them (these are static variables in the
DatePickerProperty
annotation). The
dateTimePattern
attribute should be used to supply a pattern to the
SimpleDateFormat
class. When using the
swtStyle
attribute, you should specify an integer value that is supported by
SWT
's
DateTime
control, because this is the
control that is used to render this type of property.
@Property(type = PropertyType.DATE_PICKER, displayName = "Expiry date", required = true)
@Help(displayHelpShort = "The date the account expires ", displayHelpLong = "Choose the date when the account will expire if no extended before the date.")
@DatePickerProperty(dateTimePattern = "MM-dd-yyyy", swtStyle = 32)
private String expiryDate;
Creates a data grid control as shown below. A data grid can be used to allow the
user to enter an arbitrary amount of rows of data and enter values for a fixed set of
columns in each of those rows (each individual combination of row and column is referred
to as a cell). Rows can be added and removed as the user sees fit.
This property type expects the class member you have annotated to also have an
accompanying
@DataGridProperty
annotation (for an example, see
below). Using this additional annotation, you can specify some specific attributes of
the data grid. You are required to reference a different class to determine which
columns go into the grid with the
itemClass
attribute. Activiti
Designer expects the member type to be a
List
. By convention, you can
use the class of the
itemClass
attribute as its generic type. If, for
example, you have a grocery list that you edit in the grid, you would define the columns
of the grid in the
GroceryListItem
class. From your
CustomServiceTask
, you would refer to it like this:
@Property(type = PropertyType.DATA_GRID, displayName = "Grocery List")
@DataGridProperty(itemClass = GroceryListItem.class)
private List<GroceryListItem> groceryList;
The "itemClass" class uses the same annotations you would otherwise use to specify
fields of a
CustomServiceTask
, with the exception of using a data
grid. Specifically,
TEXT
,
MULTILINE_TEXT
and
PERIOD
are currently supported. You'll notice the grid will create
single line text controls for each field, regardless of the
PropertyType
. This is done on purpose to keep the grid graphically
appealing and readable. If you consider the regular display mode for a
PERIOD
PropertyType
for instance, you can imagine it would never properly
fit in a grid cell without cluttering the screen. For
MULTILINE_TEXT
and
PERIOD
, a double-click mechanism is added to each field which
pops up a larger editor for the
PropertyType
. The value is stored to
the field after the user clicks OK and is therefore readable within the grid.
Required attributes are handled in a similar manner to regular fields of type
TEXT
and the entire grid is validated as soon as any field loses
focus. The background color of the text control in a specific cell of the data grid is
changed to light red if there are validation failures.
By default, the component allows the user to add rows, but not to determine the
order of those rows. If you wish to allow this, you should set the
orderable
attribute to true, which enables buttons at the end of
each row to move it up or down in the grid.
This customization requires you to include a class in your extension that implements
the
DefaultPaletteCustomizer
interface. You should not implement this
interface directly, but subclass the
AbstractDefaultPaletteCustomizer
base class. Currently, this class provides no functionality, but future versions of the
DefaultPaletteCustomizer
interface will offer more capabilities for
which this base class will provide some sensible defaults so it's best to subclass so your
extension will be compatible with future releases.
Extending the
AbstractDefaultPaletteCustomizer
class requires you
to implement one method,
disablePaletteEntries()
, from which you must
return a list of
PaletteEntry
values. For each of the default shapes,
you can disable it by adding its corresponding
PaletteEntry
value to
your list. Note that if you remove shapes from the default set and there are no remaining
shapes in a particular drawer, that drawer will be removed from the palette in its
entirety. If you wish to disable all of the default shapes, you only need to add
PaletteEntry.ALL
to your result. As an example, the code below
disables the Manual task and Script task shapes in the palette.
public class MyPaletteCustomizer extends AbstractDefaultPaletteCustomizer {
* (non-Javadoc)
* @see org.activiti.designer.integration.palette.DefaultPaletteCustomizer#disablePaletteEntries()
@Override
public List<PaletteEntry> disablePaletteEntries() {
List<PaletteEntry> result = new ArrayList<PaletteEntry>();
result.add(PaletteEntry.MANUAL_TASK);
result.add(PaletteEntry.SCRIPT_TASK);
return result;
The result of applying this extension is shown in the picture below. As you can see,
the manual task and script task shapes are no longer available in the
Tasks drawer.
To disable all of the default shapes, you could use something similar to the code
below.
public class MyPaletteCustomizer extends AbstractDefaultPaletteCustomizer {
* (non-Javadoc)
* @see org.activiti.designer.integration.palette.DefaultPaletteCustomizer#disablePaletteEntries()
@Override
public List<PaletteEntry> disablePaletteEntries() {
List<PaletteEntry> result = new ArrayList<PaletteEntry>();
result.add(PaletteEntry.ALL);
return result;
The result will look like this (notice that the drawers the default shapes were in are
no longer in the palette):
Besides customizing the palette, you can also create extensions to Activiti Designer
that can perform validations and save information from the diagram to custom resources in
the Eclipse workspace. There are built-in extension points for doing this and this section
explains how to use them.
Activiti Designer allows you to write extensions that validate diagrams. There are
already validations of BPMN constructs in the tool by default, but you can add your own if
you want to validate additional items such as modeling conventions or the values in
properties of
CustomServiceTask
s. These extensions are known as
Process Validators
.
You can also Activiti Designer to publish to additional formats when saving diagrams.
These extensions are called
Export Marshallers
and are invoked
automatically by Activiti Designer on each save action by the user. This behavior can be
enabled or disabled by setting a preference in Eclipse's preferences dialog for each format
to be saved.
You can compare these extensions to the BPMN 2.0 validation, BPMN 2.0 export and process
image saving that's performed during save actions by default in Activiti Designer. In fact,
these functions use exactly the same extension features you can use to save to your own
formats.
Often, you will want to combine a
ProcessValidator
and an
ExportMarshaller
. Let's say you have a number of
CustomServiceTask
s in use that have properties you would like to use in
the process that gets generated. However, before the process is generated, you want to
validate some of those values first. Combining a
ProcessValidator
and
ExportMarshaller
is the best way to accomplish this and Activiti
Designer enables you to plug your extensions into the tool seamlessly.
To create a
ProcessValidator
or an
ExportMarshaller
, you need to create a different kind of extension than
for extending the palette. The reason for this is simple: from your code you will need
access to more APIs than are offered by the integration library. In particular, you will
need classes that are available in Eclipse itself. So to get started, you should create an
Eclipse plugin (which you can do by using Eclipse's PDE support) and package it in a custom
Eclipse product or feature. It's beyond the scope of this user guide to explain all the
details involved in developing Eclipse plugins, so the instructions below are limited to the
functionality for extending Activiti Designer.
Your bundle should be dependent on the following libraries:
Both
ProcessValidator
s and
ExportMarshaller
s are
created by extending a base class. These base classes inherit some useful methods fromt heir
superclass, the
AbstractDiagramWorker
class. Using these methods you can
create information, warning and error markers that show up in Eclipse's problems view for
the user to figure out what's wrong or important. You can also access the diagram through
Resources
and
InputStreams
for the diagram's content
using these methods in the
AbstractDiagramWorker
class.
It's probably a good idea to invoke
clearMarkers()
as one of the
first things you do in either a
ProcessValidator
or an
ExportMarshaller
; this will clear any previous markers for your worker
(markers are automatically linked to the worker and clearing markers for one worker leaves
other markers untouched). For example:
// Clear markers for this diagram first
clearMarkers(getResource(diagram.eResource().getURI()));
You should also use the progress monitor provided to report your progress back to the
user because validations and/or marshalling actions can take up some time during which the
user is forced to wait. Reporting progress requires some knowledge of how you should use
Eclipse's features. Take a look at
this
article
for a thorough explanation of the concepts and usage.
Create an extension to the
org.activiti.designer.eclipse.extension.validation.ProcessValidator
extension point in your
plugin.xml
file. For this extension point, you
are required to subclass the
AbstractProcessValidator
class.
<?eclipse version="3.6"?>
<plugin>
<extension
point="org.activiti.designer.eclipse.extension.validation.ProcessValidator">
<ProcessValidator
class="org.acme.validation.AcmeProcessValidator">
</ProcessValidator>
</extension>
</plugin>
public class AcmeProcessValidator extends AbstractProcessValidator {
You have to implement a number of methods. Most importantly, implement
getValidatorId() so you return a globally unique ID for your
validator. This will enable you to invoke it from and ExportMarshaller ,
or event let someone else invoke your validator from their
ExportMarshaller . Implement getValidatorName() and
return a logical name for your validator. This name is shown to the user in dialogs. In
getFormatName() , you can return the type of diagram the validator
typically validates. The validation work itself is done in the validateDiagram() method.
From this point on, it's up to your specific functionality what you code here. Typically,
however, you will want to start by getting hold of the nodes in the diagram's process, so
you can iterate through them, collect, compare and validate data. This snippet shows you
how to do this:
final EList<EObject> contents = getResourceForDiagram(diagram).getContents();
for (final EObject object : contents) {
if (object instanceof StartEvent ) {
// Perform some validations for StartEvents
// Other node types and validations
Don't forget to invoke addProblemToDiagram() and/or
addWarningToDiagram() , etc as you go through your validations. Make
sure you return a correct boolean result at the end to indicate whether you consider the
validation as succeeded or failed. This can be used by and invoking
ExportMarshaller to determine the next course of action.
Create an extension to the
org.activiti.designer.eclipse.extension.export.ExportMarshaller
extension point in your
plugin.xml
file. For this extension point, you
are required to subclass the
AbstractExportMarshaller
class. This
abstract base class provides you with a number of useful methods when marshalling to your
own format, but most importantly it allows you to save resources to the workspace and to
invoke validators.
<?eclipse version="3.6"?>
<plugin>
<extension
point="org.activiti.designer.eclipse.extension.export.ExportMarshaller">
<ExportMarshaller
class="org.acme.export.AcmeExportMarshaller">
</ExportMarshaller>
</extension>
</plugin>
public class AcmeExportMarshaller extends AbstractExportMarshaller {
You are required to implement some methods, such as
getMarshallerName() and getFormatName() . These
methods are used to display options to the user and to show information in progress
dialogs, so make sure the descriptions you return reflect the functionality you are
implementing. The bulk of your work is performed in the marshallDiagram(Diagram diagram,
IProgressMonitor monitor) method. You are provided with the diagram object,
which contains all of the information about the objects in the diagram (BPMN constructs)
and the graphical representation. If you want to perform a certain validation first, you can invoke the validator
directly from your marshaller. You receive a boolean result from the validator, so you
know whether validation succeeded. In most cases you won't want to proceed with
marshalling the diagram if it's not valid, but you might choose to go ahead anyway or even
create a different resource if validation fails. For example:
final boolean validDiagram = invokeValidator(AcmeConstants.ACME_VALIDATOR_ID, diagram, monitor);
if (!validDiagram) {
addProblemToDiagram(diagram, "Marshalling to " + getFormatName() + " format was skipped because validation of the diagram failed.", null);
} else {
//proceed with marshalling
As you can see, here we have chosen to cancel the marshalling if the validator
(identified by a constant here) returns false as result. We have also added an additional
marker to the diagram so the user can see an explanation why the file wasn't created. This
is not required, but seems helpful to the user and shows how you can use these utilities
from both ProcessValidator s and ExportMarshaller s. Once you have all the data you need, you should invoke the
saveResource() method to create a file containing your data. You can
invoke saveResource() as many times as you wish from a single
ExportMarshaller; a marshaller can therefore be used to create more
than one output file. You can construct a filename for your output resource(s) by using some of the methods
in the AbstractDiagramWorker class. There are a couple of useful
variables you can have parsed, allowing you to create filenames such as
<original-filename>_<my-format-name>.xml. These variables are described in the
Javadocs, but here's an example how to use one of them:
private static final String FILENAME_PATTERN = ExportMarshaller.PLACEHOLDER_ORIGINAL_FILENAME + ".acme.axml";
saveResource(getRelativeURIForDiagram(diagram, FILENAME_PATTERN), bais, this.monitor);
What happens here is that a static member is used to describe the filename pattern
(this is just a best practice, you can specify the string any way you like of course) and
the pattern uses the ExportMarshaller.PLACEHOLDER_ORIGINAL_FILENAME
constant to insert a variable for the original filename. Later on in the
marshallDiagram() method,
getRelativeURIForDiagram() is invoked and it will parse the filename
for any variables and substitute them. You provide saveResource() with
an InputStream to your data and it will save the data to a resource
with a relative path to the original diagram. Again, you should also use the progress monitor provided to report your progress back
to the user. How to do this is described in this
article.
The Activiti Modeler is a web based process editor that can be used
to author BPMN 2.0 process graphically using nothing more then a browser.
The process files are stored by the server on a the file system, such
that they are easily accessible and can be imported without hassles into any Java IDE.
Activiti Modeler is a separate component developed by Signavio and hosted
in
a google code project
.
The license for the Activiti Modeler
is
MIT
Bugs and issues can be reported on the
Signavio core components issue tracker
.
The Activiti Modeler is automatically installed during the
demo setup
.
Activiti Cycle is a web application that provides a collaborative platform for different stakeholders of BPM projects (Business, developers, IT operations, ...).
It combines different repositories like the Activiti Modeler repository, Subversion or your local file system into a single view,
which makes it easy to browse all the artifacts that are involved in the process of creating an executable business process.
In addition to this, Activiti Cycle provides built in actions like moving artifacts between repositories or downloading different
formats of process models, and a plug-in infrastructure for custom actions. Below is a screen shot of Cycle in a default
configuration as pre-configured in the demo setup.
Please note: Activiti Cycle is still in a "beta" state.
With collaborative tools like Activiti Cycle we dont have years of experience yet, like we have with the core engine. Currently
most features are already stable, but with adding more use cases and features, we still have to do some tuning of the APIs or database tables.
This means, that for example the Plugin-API is still subject to change, which you have to know when you start writing own Plug-Ins.
Or the database might change without having out-of-the-box database migration scripts ready. But be sure: We work hard on removing the beta status,
meaning you will get a stable API and database schema.
The above image shows the Activiti Cycle repository tree.
By default it provides access to the repository of the Activiti Modeler
and a demo Eclipse workspace, which is located at
${activiti.home}/workspace/activiti-cycle-examples
in your local file system.
The repositories are plugged in as repository connectors.
You'll find the configuration in
the table
CYCLE_CONN_CONFIG
in the Activiti database,
which you can access as described in
the section called “Inspecting the database”
.
The database schema has 6 columns:
-
PLUGIN_ID_
: The plugin-id of the connector (e.g.
"org.activiti.cycle.impl.connector.fs.FileSystemConnector"
for the filesystem connector and
"org.activiti.cycle.impl.connector.signavio.SignavioConnector"
for the connector to the activiti modeler.)
-
INSTANCE_NAME_
: Name of the connector instance. This is the string
which is rendered as root folder for this connector in the cycle
repository tree. Note that it is possible to use the same connector
multiple times, to connect to different repositories
of the same type (e.g. to multiple eclipse workspaces using the
filesystem connector). In that case the
CYCLE_CONN_CONFIG
will contain multiple lines with the same
PULGIN_ID_
but different
INSTANCE_NAME_
s and different
INSTANCE_ID_
s (see below).
-
INSTANCE_ID_
: is used internally to address a connector instance.
-
USER_
: the user the current connector instance is configured for.
(NOTE: This
is not the login-name for the connector, but the Activiti Cycle
username, e.g. "kermit" in the demo-configuration.)
-
GROUP_
: a connector instance can be either configured for a single user, or for a group.
If the "GROUP_" field is set for a given connector instance, cycle will load this instance for every
user belonging to that group.
-
VALUES_
: an XML string containing the values for the current configuration.
It is a serialized map of the form
<map>(<entry><string>KEY</string><string>VALUE</string></entry>)*</map>
Executing the following SQL statement sets the default configuration for "kermit", including an instance of the
filesystem connector and an instance of the signavio connector used to access the activiti modeler repository:
insert into ACT_CY_CONN_CONFIG values ('1',
'org.activiti.cycle.impl.connector.fs.FileSystemConnector',
'Eclipse Workspace (File System)',
'Workspace',
'kermit', '',
'<map><entry><string>basePath</string><string>/path-to-activiti-home/activiti-5.0.beta2/workspace/activiti-cycle-examples</string></entry></map>');
insert into ACT_CY_CONN_CONFIG values ('2',
'org.activiti.cycle.impl.connector.signavio.SignavioConnector',
'Activiti Modeler',
'Activiti',
'kermit', '',
'<map>
<entry><string>signavioBaseUrl</string><string>http://localhost:8080/activiti-modeler/</string></entry>
<entry><string>loginRequired</string><boolean>false</boolean></entry>
</map>');
Now, if you want to add, e.g., a Signavio Process Modeler repository,
you can add a configuration for the
PLUGIN_ID_
'org.activiti.cycle.impl.connector.signavio.SignavioConnector'.
If you need a Signavio account, you can register for a free trial
at the
Signavio web site
.
Here is an example configuration:
<entry><string>signavioBaseUrl</string><string>https://editor.signavio.com/</string></entry>
<entry><string>loginRequired</string><boolean>true</boolean></entry>
<entry><string>username</string><string>your user name</string></entry>
<entry><string>password</string><string>secret</string></entry>
You may also choose not to save your username and password in the database
and leaving 'username' and 'password' empty. Cycle will then ask you for your credentials when it needs them.
Another thing you might consider
is to add a collection of BPMN examples from
the
Oryx Project
.
In order to do this, you need a connector configuration for the
PLUGIN_ID_
'org.activiti.cycle.impl.connector.signavio.SignavioConnector':
<entry><string>type</string><string>oryx</string></entry>
<entry><string>signavioBaseUrl</string><string>http://oryx-project.org/</string></entry>
<entry><string>loginRequired</string><boolean>false</boolean></entry>
<EXPERIMENTAL>
... the whole REST interface is still experimental
Activiti includes a REST API to the engine that will be deployed to your server when you run the setup script.
The REST API uses JSON format (http://www.json.org) and is built upon the Spring Webscripts (http://www.springsurf.org).
It is possible to browse the REST API if you point your browser to http://localhost:8080/activiti-rest/service/index
and login as one of the admin users (kermit). If you click the "Browse by Web Script Package" link you will get an overview
of the packages that are deployed on the server and you can easily navigate into a package to see the REST API calls
that are defined for that package. The "Alfresco JavaScript Debugger" can be ignored since Java is used instead of
JavaScript to implement the logic.
Each REST API call has its individual authorization level and you must be logged in as a user to invoke a REST API
call (except for the /login service). Authentication is done using Basic HTTP Authentication, so if you logged in as
an admin (i.e. kermit) to browse the REST API, as described above, you should be able to perform all calls as
described below.
The API follows normal REST API conventions using GET for read operations, POST for creating objects, PUT for updating and
performing operations on already created objects and finally DELETE for deleting objects. When performing a call
that affects multiple objects POST is used on all such operations for consistency and making sure that an unlimited
number of objects may be used. The reason for using POST is that the HTTP DELETE method doesn't implicitly allow
request bodies and therefore, a call using DELETE, in theory, could get it's request body stripped out by a proxy.
So to be certain this doesn't happen we use POST, even when PUT could have been used to update multiple objects,
for consistency.
All rest calls use a content type of "application/json" (except for upload requests which uses "multipart/form-data").
The base URL for invoking a REST call is http://localhost:8080/activiti-rest/service/.
So for example to list the process definitions in the engine point your browser to:
http://localhost:8080/activiti-rest/service/process-engine
Please look below to see what REST API calls that currently are available.
Please consider the "API" sections as a "one line hint" to what functionality of the core API that is used to implement the REST API call.
Returns details about the deployed process definitions that can be sorted by "id", "name", "version" or
"deploymentTime". The name of the BPMN2.0 XML process diagram is given in the "resourceName" attribute and can,
in combination with the "deploymentId" attribute, be retrieved from the GET Deployment Resource REST API call
above. If the process has a start form it is given in the "startFormResourceKey" attribute.
The start form for a process can be retrieved from the GET Start Process Form REST API call.
Paginated Request:
GET /process-definitions?start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}
ProcessEngines.getProcessEngine(configuredProcessEngineName).getProcessService().findProcessDefinitions()
Paginated Response:
"data": [
"id": "financialReport:1",
"key": "financialReport",
"version": 1,
"name": "Monthly financial report",
"resourceName": "org/activiti/examples/bpmn/usertask/FinancialReportProcess.bpmn20.xml",
"deploymentId": "10",
"startFormResourceKey": null
"total": 1,
"start": 0,
"sort": "id",
"order": "asc",
"size": 1
Creates a process instance based on a process definition and returns details about the newly created process instance.
Additional variables (from a form) may be passed using the body object. In other words placing attributes
next to the "processDefinitionId" attribute.
These additional variables may also be described using "meta data fields".
A variable named "numberOfDays" with value "2" may be described as an int using
an extra variable named "numberOfDays_type" set to "Integer" and to describe it as a required variable use
an extra variable named "numberOfDays_required" set to "true". If no type descriptor is used the value will
be treated as a String as long as its surrounded by '"'-characters. Its also possible to set the
type to "Boolean".
Note that if a value is submitted as true (instead of "true") it will be treated as a
Boolean even if no descriptor is used. The same is also valid for number, i.e., 123 will become an Integer
but "123" will become a String (unless a descriptor is defined). Note that no variables containing "_" in
the name will be saved, they are only treated as meta data fields.
The reason for using these meta data fields is to make it possible using a standard HTML form to
submit the values (since an HTML form submits everything as strings its not possible to distinguish the
type of a value as in JSON). HTML submission will be supported in the near future.
It is of course not an optimal solution to let the client send instructions to the server about which
variables that are required and what type they have, but this is a temporary solution to enable simple form
handling.
We are currently looking for more proper solutions for forms, containing real meta models that can be used on
the server to avoid using meta data fields like above. Please feel free to give suggestions or tips in the Activiti Forum.
Request:
POST /process-instance
"processDefinitionId":"financialReport:1:1700",
"businessKey":"order-4711"
ProcessEngines.getProcessEngine(configuredProcessEngineName).getProcessService().startProcessInstanceById(processDefinitionId[, businessKey][, variables])
Response:
"id": "217",
"processDefinitionId": "financialReport:1:1700",
"activityNames": ["writeReportTask"],
"ended": true
Request providing processDefinitionKey instead of Id:
POST /process-instance
"processDefinitionKey":"financialReport:1",
"businessKey":"order-4711"
Returns a paginated list of tasks that can be sorted by: "id", "name", "description", "priority", "assignee",
"executionId" or "processInstanceId". The list must be based on a user of a specific role: assignee
(lists the tasks assigned to the user) or candidate (lists the tasks that the user may claim) or a candidate
group (lists tasks that the members of the group may claim). If the task has a form it is given in the
"formResourceKey" attribute. The form for a task can be retrieved from the GET Task Form REST API call.
Paginated Request:
GET /tasks?[assignee={userId}|candidate={userId}|candidate-group={groupId}]&start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}
ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().createTaskQuery().xxx().listPage()
Paginated Response:
"data": [
"id": 127,
"name": "Handle vacation request",
"description": "Vacation request by Kermit",
"priority": 50,
"assignee": null,
"executionId": 118,
"formResourceKey": "org/activiti/examples/taskforms/approve.form"
"total": 1,
"start": 0,
"sort": "id",
"order": "asc",
"size": 1
Returns a paginated list of jobs that can be sorted by "id", "process-instance-id", "execution-id",
"due-date", "retries" or some custom arbitrary property id. The list can also be filtered by
process instance id, due date or if the jobs have retries, are executable or only have messages or timers.
Paginated Request:
GET /management/jobs?process-instance={processInstanceId?}&with-retries-left={withRetriesLeft=false}&executable={axecutable=false}&only-timers={onlyTimers=false}&only-messages={onlyMessage=false}&duedate-lt={iso8601Date}&duedate-ltoe={iso8601Date}&duedate-ht={iso8601Date}&duedate-htoe={iso8601Date}&start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}
ProcessEngines.getProcessEngine(configuredProcessEngineName).createJobQuery().xxx().listPage()
Paginated Response:
"data": [
"id": "212",
"executionId": "211",
"retries": -1,
"processInstanceId": "210",
"dueDate": null,
"assignee": null,
"exceptionMessage": "Can\'t find scripting engine for \'groovy\'"
"total": 1,
"start": 0,
"sort": "id",
"order": "asc",
"size": 1
The whole REST interface is still experimental ...
</EXPERIMENTAL>
Activiti KickStart is a webbased tool to quickly create 'adhoc' business processes using
a subset of constructs available to the Activiti engine.
KickStart provides a simple UI that doesn't require to learn BPMN or any modeling environment, as
it works with concepts that are familiar to every business user. However, the processes that are
created using KickStart, are fully BPMN 2.0 compliant and can be used as a starting point
for more complex BPM endeavours.
KickStart integrates perfectly with the Activiti engine. As such, processes created with KickStart
are immediataly usable in Activiti Explorer and are visible in Probe.
KickStart serves many business cases, but the following three are probably the most common:
Simple business processes:
some processes are just simple
by nature, and every company has them. Think about an expense process, a holiday leave process,
a hiring process, etc... These kind of processes are probably already being done
using paper or e-mail. KickStart allows to model these processes quickly and change
them whenever it is needed. As such, KickStart really lowers the threshold
to automate these business processes.
Prototyping:
before diving into complex BPMN 2.0 modeling
and thinking about the corner cases of the process, it is often wise to get all people involved aligned and work out
a prototype that shows the vision of what needs to be done. KickStart allows to do exatcly that:
create a business process prototype on the fly, to get your ideas visible for everyone.
Adhoc work:
in some cases, coordination is required
between different people or groups in a company. You know how it normally goes: sending
an email here, doing a telephone there ... which often ends up in a tarpit of nobody
knowing what or when something needs to be done. However, a business process management platform such as
Activiti is an excellent way of distributing and follow-up everything, as it is
intended to track exactly such things. KickStart allows you to create processes
for adhoc work in a matter of minutes, and distribute and coordinate tasks between
people easily.
Following screenshots show the capabilities of Activiti KickStart.
Take for example the next picture. It shows how an expense process is created
in a matter of a few minutes.
After clicking the save button, the process is immediately usable in Activiti Explorer:
KickStart also allows to define forms for every task:
Which are obviously directly usable in Activiti Explorer:
At any time during process creation, it is possible to view the corresponding BPMN 2.0 diagram
of the business process:
Whenever it is required, the processes defined with KickStart can be openend up and modified:
Processes created with KickStart are fully compliant with the BPMN 2.0 XML, which means that
the process can be imported into any BPMN 2.0 editor:
The jBPM migration is considered
[EXPERIMENTAL]
.
It is possible to migrate an existing installation of jBPM version 3 to Activiti. The
migration includes both process definition conversion (ie. from JPDL 3 to BPMN 2.0)
and data migration.
The migration tool is currently offered as a 'preview' only!
The migration coverage is at this point in time not sufficient to be usable on production
process definitions and databases. Also note that the migration is a
'best effort'
,
which means that you may need to
extend the migration logic
to cover all your use cases.
The migration tool is separately availbale as a zip file from
the activiti.org download site
.
Once you have unzipped this file, you will see following files and folders:
build.xml
: This ant buildfile contains the targets
to execute the process definition conversion and database migration.
activiti.db.properties and jbpm3.db.properties
:
these properties files contain the database connection parameters for the
jBPM 3 and Activiti database. These properties files must be changed when
executing a
database migration
.
processes
: when running a
process definition conversion
, the jBPM 3
process definition files must be placed in this folder.
src
: this folder contains the source code of the
migration tool. Read the
extension
section
if you want to tailor or extend the migration code to your needs.
lib
: this folder contains all jars
required to execute the migration logic.
It is possible to only convert the process definitions xml from JPDL to BPMN 2.0 that
can be executed on the Activiti engine. To do this, place the jBPM 3 processes
inside the
processes
folder. There can be any number of (nested) subfolders,
the tool will scan every (sub)folder inside
processes
to discover
processdefinition.xml
files (file name must be
processdefinition.xml
!).
The discovered processes will be parsed and deployed to an in-memory databse, such that the
logic of reverse engineering the process definition from the jBPM deployment tables contained
in
the database migration
is used for both cases.
Once the jBPM 3 processes are placed in the
processes
folder, execute the
convert.processes
target in the root of the unzipped migration tool folder:
ant convert.processes
During conversion, you will see plenty logging passing by describing where and how
the process conversion is being executed. At the end of the conversion, you will see following
logging appear:
As shown in the logging, the resulting BPMN 2.0 processes can be find in the
converted-process-xxxx
folder, where the
xxxx
is
the timestamp of the conversion.
In this release, only limited support for start, end, wait state and task-nodes is implemented.
In the future, this coverage will expand.
The migration logic is written in such a way, that it is easy to extend to suit your needs.
The source code is available as a Maven 2 project in the
src
subfolder of the downloaded
zip file. To build a new zip file, after having changed or extended the logic, simply execute a
mvn clean install
to produce a new zip file in the
target
folder.
Following picture gives a high-level overview of the classes in the migration logic.
Both the
ProcessConversion
and
ProcessDataMigration
classes have a
main
method that directly is called from the ant build script
in the root of the migration zip.
These classes construct a
ServiceFactory
based on
the two properties files, using a static factory method
ServiceFactory.configureFromProperties(jbpmDbProperties, activitiDbProperties);
The services are constructed by the
ServiceFactory
(eg.
getProcessConversionService()
)
and are used to execute the migration logic:
public void execute() throws IOException {
// convert processes
ServiceFactory serviceFactory = createServiceFactory();
ProcessConversionService processConversionService = serviceFactory.getProcessConversionService();
Map<String, Document> migratedProcesses = processConversionService.convertAllProcessDefinitions();
// write results to bpmn20.xml files
writeConvertedProcesses(migratedProcesses, workingDir);
// Deploy processes to Activiti
ActivitiService activitiService = serviceFactory.getActivitiService();
activitiService.deployConvertedProcesses(migratedProcesses);
// data migration
The
ProcessConversionService
is an interface
that contains process conversion and process definition data retrievel operations.
It uses an implementation of
Jbpm3Dao
. The default
implementation of this class uses a Hibernate
SessionFactory
to retrieve all the data from jBPM 3 tables.
The
ActivitiService
offers operation needed to get the
migrated data in the Activiti tables. For example, deploying the converted process
definitions is such an operation
All these dependencies,
ProcessConversionService, Jbpm3Dao, Sessionfactory,
ActivitiService and ProcessEngine
, are interfaces and can be implemented
by your own implementation. You can inject them into the
ServiceFactory
using regular JavaBean setters. When no such custom implementation is set, the
ServiceFactory
will fall back to creating the default implementation:
public ProcessConversionService getProcessConversionService() {
if (processConversionService == null) {
this.processConversionService = createDefaultProcessConversionService();
return processConversionService;
protected ProcessConversionService createDefaultProcessConversionService() {
|