jBPM is distributed under the terms of the GNU Lesser General Public License (LGPL) and the JBoss End User License Agreement (EULA). See the full LGPL license text and the full End User License Agreement.
The distribution packages can be downloaded from sourceforge
http://sourceforge.net/project/showfiles.php?group_id=70542&package_id=268068The source code for this component can be found in the jBPM SVN repository:
https://anonsvn.jboss.org/repos/jbpm/jbpm4/jBPM requires a JDK (standard java) version 5 or higher.
http://java.sun.com/javase/downloads/index.jspjBPM is an extensible and flexible process engine that can run as a standalone server or embedded in any Java application.
In this user guide, we'll describe the jPDL process language in persistent execution mode. Persistent execution mode means that process definitions, process executions and process history is stored in a relational DB. This is the common way of how jBPM is used.
This user guide explains the supported way on how to use jBPM. The developers guide explains more advanced customization options that are not part of the support offerings.
This chapter describes how to install jBPM in different application environments.
Just unzip the jBPM download to some location on your hard drive. You'll see following subdirectories:
db
: DB schema creation scriptsdoc
: User guide, javadocs and developers guideexamples
: Example processes that are used in the user guidegpd
: Graphical process designer plugin as an eclipse archived sitejboss
: JBoss installer to install jBPM into JBosslib
: Third party libs and some special jBPM librariessrc
: Sourcesjbpm.jar
: The jBPM main library archiveThe demo setup is the simplest way to get started. This section describes the steps to complete the demo setup.
If you have jboss-5.0.0.GA on locally on your machine, create a directory
called downloads
in your jBPM home directory and copy
the jboss distribution zip file in there. Same for an Eclipse Ganymede SR2 JEE distribution
(eclipse-jee-ganymede-SR2-win32.zip). If you have it already locally, copy it to
the downloads directory. If you don't have those files in that place, they will be downloaded
automatically by the demo setup ant script.
Open a command prompt and go do directory ${jbpm.home}/jboss
. Then
run
ant demo.setup
That will
${jbpm.home}/jboss-5.0.0.GA
directory${jbpm.home}/db/example.identities.sql
${jbpm.home}/eclipse
Once this is done and eclipse has started, you can continue to follow the instructions of Section 2.8, “Graphical Process Designer (GPD)”
And you can surf to the jBPM console You can login as one of the following users:
As you already saw one example in the demo setup, the distribution a couple of ant scripts:
${jbpm.home}/jboss/build.xml
${jbpm.home}/db/build.xml
${jbpm.home}/gpd/build.xml
${jbpm.home}/examples/build.xml
With ant -p
you can find out what each of these scripts can do.
The scripts are parametrized with default values. Following properties you might want to customize:
database
: Default value is hsqldb
. Alternative values
are mysql
, oracle
and postgresql
jboss.version
: Default value is 5.0.0.GA
. Alternative
value is 5.0.1.GA
To customize the values for these properties, just use -D
like this
ant -Ddatabase=postgresql demo.setup
Or specify the customized values in ${user.home}/.jbpm4/build.properties
To specify your jdbc properties that are used in the DB schema generation, direct DB access like
deployment and for the JBoss datasource creation, the easiest is to update the appropriate
properties file in directory ${jbpm.home}/db/jdbc
. The appropriate properties
file will be loaded by the scripts that are DB related.
The build file ${jbpm.home}/jboss/build.xml
contains scripts for
installing jBPM into JBoss 5. Navigate to that directory and run ant -p
for more details.
The build file ${jbpm.home}/db/build.xml
contains scripts for
DB operations like creating and dropping the schema. Navigate to that directory and run ant -p
for more details.
TODO: this will contain a description of how to use the installer to install jbpm on tomcat
If you're not deploying in JBoss, then the example configuration files
in the examples src
are the best starting point.
Eclipse is used as the platform to host the jPDL graphical process designer. This section will describe how to obtain and install Eclipse and how to install the GPD plugin in Eclipse.
You'll need Eclipse 3.4.2.
Use the demo setup or download eclipse manually: Eclipse IDE for Java EE Developers (163 MB).
The classic version of eclipse will not be sufficient as it does not have an XML editor. Eclipse for Java developers should also work.
The installation of the GPD uses the Eclipse Software Update
mechanism and is pretty straightforward. There is an archived update
site included in the runtime installation of jBPM when you unzip it
at gpd/jbpm-gpd-site.zip
To add the update site to eclipse:
Help
--> Software Updates
Available Software
Add Site...
Add Site
dialog, click Archive...
gpd/jbpm-gpd-site.zip
OK
will bring you back to the dialog 'Software Updates and Add-ons'jPDL 4 GPD Update Site
that has appearedInstall...
Install
, select Flow Common Feature
and jPDL 4 Feature
Next
Accept
and click Finish
This section shows how to define a user library for your workspace that is a placeholder for the jBPM library as well as its dependencies. If you create a new Java project, it will be sufficient to add this user library to the build path.
Window
--> Preferences
Java
--> Build Path
--> User Libraries
New...
jBPM Libraries
Add JARs...
Open
jBPM Libraries
entryAdd JARs...
againjbpm.jar
file in the root of your jBPM installationOpen
Source attachment
under jbpm.jar
Edit
Source Attachment Configuration
, click External Folder...
src
folder in your jBPM installationChoose
OK
twice to close all the open dialogsIn case you want to edit the process XML sources directly, it is best to specify your schema in the XML catalog. This will give you better code completion while editing the process sources.
Window
--> Preferences
XML
--> XML Catalog
jpdl-4.0.xsd
in the src directory of the jBPM installation root.In this section we will import the examples project in the installation in Eclipse
File
--> Import...
General
--> Existing Projects into Workspace
Next
Browse...
to select a root directoryOK
examples
project is automatically found and selectedFinish
You're all set to start playing with the coolest Java process technology!
You can leverage the eclipse ant integration to ease deployment of processes. We'll show you how it works with the examples. Then you can copy this practice in your own project. First, open up the Ant view.
Window
--> Show View
--> Other...
--> Ant
--> Ant
build.xml
in the examples project from the package explorer to the Ant viewThis chapter will explain how to work with the Graphical Process Designer. After installing the GPD and setting up the examples, you'll see that the jPDL process files will get a special icon. Double clicking such a file in the package view will open up the jPDL process in the GPD.
CRTL+N will open up the wizard selector.
Select jBPM --> jPDL 4 File. Click 'Next >'. Then the 'New jPDL 4 File' wizard opens.
Select the parent directory, enter a file name and click 'Finish'. Voila, you've created your first jPDL process file.
A process definition is description of the steps in a procedure.
For example, an insurance company could have a loan
process definition that describes the steps of how the company deals
with loan requests.
One process instance represents one particular run of a process definition. For example, the loan request of John Doe last Friday to finance his new boat is represented in one process instance of the loan process definition.
A process instance contains all the runtime state. The most prominent property is the pointer that keeps track of the current activity.
Suppose that wiring the money and archiving can be done in parallel. Then the main process instance will have two child executions to keep track of the state like this:
More general, a process instance is the root of a tree of executions. When a new process instance is started, the process instance is in fact the root execution scope. Only leaf executions can be active.
The motivation to work with a tree structure like this is that
this conceptually remains simple in the case where there is only one path
of execution. The services API doesn't need to make a functional difference
between process instances and executions. Therefore, the API has only
one Execution type to refer to both ProcessInstance
s and
Execution
s.
Interacting with jBPM occurs through services.
The service interfaces can be obtained from the ProcessEngine
which is build from a Configuration
.
A ProcessEngine
is thread safe and can be stored in a
static member field or even better in JNDI or some other central location.
One ProcessEngine
object can be used by all requests and
threads in an application. Here's how you can obtain a ProcessEngine
The code snippets in this section and the next section about process
deployments are taken from example
org.jbpm.examples.services.ServicesTest
ProcessEngine processEngine = new Configuration() .buildProcessEngine();
The previous code snippet shows how to build a ProcessEngine
from the default configuration file jbpm.cfg.xml
which is
expected in the root of the classpath. If you want to specify another
resource location, use the setResource
method like this:
ProcessEngine processEngine = new Configuration() .setResource("my-own-configuration-file.xml") .buildProcessEngine();
There are other setXxxx
methods that allow to specify
the configuration content as an InputStream
, an
xmlString
, InputSource
,
URL
or File
.
From a ProcessEngine
the following services
can be obtained:
RepositoryService repositoryService = processEngine.getRepositoryService(); ExecutionService executionService = processEngine.getExecutionService(); TaskService taskService = processEngine.getTaskService(); HistoryService historyService = processEngine.getHistoryService(); ManagementService managementService = processEngine.getManagementService();
Process engine objects defined in the configuration can also be retrieved by
type (processEngine.get(Class<T>)
)
or by name (processEngine.get(String)
)
The RepositoryService
groups all methods to manage
the repository of deployments. In this first example, we'll deploy one process
resource from the classpath with the RepositoryService
:
long deploymentDbid = repositoryService.createDeployment() .addResourceFromClasspath("org/jbpm/examples/services/Order.jpdl.xml") .deploy();
Analogue to the addResourceFromClasspath
method above,
the source of the processes definitions XML can be picked up from a file, url, string,
input stream or zip input stream.
Each deployment is composed of a set of named resources. The content
of each resource is a byte array. jPDL process files are recognized by their extension
.jpdl.xml
. Other resource types are task forms and java classes.
A deployment works with a set of named resources and can potentially contain
multiple process descriptions and multiple other artifact types. The jPDL deployer
will recognise process files based on the .jpdl.xml
extension automatically.
During deployment, an id
is assigned to the process
definitions. The id
will have format
{key}-{version}
with a dash between key and version
If key
is not provided, it is generated automatically
based on the name. All non alpha numeric characters in the name will be replaced
by underscores to generate the key.
The same name
can only be associated to one
key
and vice verca.
If version
is not provided, a version
will be automatically be assigned. For version
assignment, the versions of all deployed process definitions with the same name will
be taken into account. The assigned version
will be one higher
then the highest version
number of deployed process definitions
with the same key
. If no process definitions with a similar
key
have been deployed, version number 1 is assigned.
In this first example, we'll supply a name and nothing else.
<process name="Insurance claim"> ... </process>
Let's assume that this is the first time that this process gets deployed. Then it will get the following properties:
Table 4.1. Process properties without key
Property | Value | Source |
---|---|---|
name | Insurance claim | process xml |
key | Insurance_claim | generated |
version | 1 | generated |
id | Insurance_claim:1 | generated |
And as a second example, we'll show how you can get shorter ids by specifying a process key:
<process name="Insurance claim" key="ICL"> ... </process>
Then the process definition properties look like this:
Table 4.2. Process properties with key
Property | Value | Source |
---|---|---|
name | Insurance claim | process xml |
key | ICL | process xml |
version | 1 | generated |
id | ICL:1 | generated |
Deleting a deployment will remove it from the DB.
repositoryService.deleteDeployment(deploymentDbid);
That method will throw an exception when there are still active process executions for process definitions in that deployment.
If you want to cascade deletion of a deployment to all
the process instances of all the process definitions, use
deleteDeploymentCascade
.
Simplest and most common way to start a new process instance for a process definition is like this:
executionService.startProcessInstanceByKey("ICL");
In this case, the service method will first look up the latest version of
the processes with key ICL
. Then a new
process instance is started in that latest process definition.
When a new version of the insurance claim process
is deployed, all invocations of startProcessInstanceByKey
will automatically switch to the newly deployed version.
If instead you want to start a new process instance in a very specific version, you can use the id of the process definition like this:
executionService.startProcessInstanceById("ICL-1");
A new process instance can optionally be given a key. A key is a user defined reference to the execution. A key must be unique within the scope of all versions of a process definition. Typically it is easy to find such a key in the domain of the business process. For example, an order id or an insurance claim number.
executionService.startProcessInstanceByKey("ICL", "CL92837");
The key is used to create the id of the process instance.
The format used is {process-key}.{execution-id}
.
With a dot between process-key and execution-id.
So execution created in the previous code snippet will have id
ICL.CL92837
.
If no user defined key is provided, the DB primary key is taken as the key. In that case, the id can be retrieved like this:
Execution execution = executionService.startProcessInstanceByKey("ICL"); String executionId = execution.getId();
We recommend the use of a user defined keys. Typically in your application code, you'll have the key available. By providing a user defined key, you can then compose the id of the execution, rather then performing a query based on the process variables.
A map of named parameter objects can be provided when starting a new process instance. These parameters will be set as variables on the process instance between creation and start of the process instance.
Map<String,Object> variables = new HashMap<String,Object>(); variables.put("customer", "John Doe"); variables.put("type", "Accident"); variables.put("amount", new Float(763.74)); executionService.startProcessInstanceByKey("ICL", variables);
A process definition describes what must be done in terms of activities. Each activity in a process is either to be performed by the process system or by an external participant. When an activity is to be performed by an external participant, then the execution must wait until the external participant notifies the process system that the activity is completed. So an execution is either executing or waiting on an external participant. Typically, you'll see that the processes are mostly waiting for external participations. Especially humans tend to be slow :-) The time consumed by the process system between two wait states is typically very small.
A state
is the basic activity that represents
something has to be done by an external participant and the execution
must wait until a signal (aka external trigger) is given.
When an execution is in a wait state, it can be given an external trigger
with one of the signal methods. The recommended way to reference an execution
is by using the process definition and execution key. In the next code snippet,
ICL
refers to the process definition key and 82436
refers to the execution key.
executionService.signalExecutionByKey("ICL", "82436");
Alternatively, the execution that must be signaled can be referenced
by a unique execution id. In the next snippet, ICL.82436
refers to the executionId.
executionService.signalExecutionById("ICL.82436");
Optionally some data can be passed along with a signal: a signalName
and parameters
. How the signalName gets used depends on
the execution's current activity. The parameters get stored as process variables.
Map<String,Object> parameters = new HashMap<String,Object>(); parameters.put("quality", "a+"); parameters.put("target", "profit"); executionService.signalExecutionById("ICL/82436", "Accept", parameters);
The primary purpose of the TaskService is to provide access to
task lists. The code sample will show how to get the task list for
the user with id johndoe
.
List<Task> taskList = taskService.findAssignedTasks("johndoe");
Typically tasks are associated with a form and displayed in some user interface. The form needs to be able to read and write data related to the task.
long taskDbid = task.getDbid(); Set<String> variableNames = taskService.getVariableNames(taskDbid); variables = taskService.getVariables(taskDbid, variableNames); variables = new HashMap<String, Object>(); variables.put("category", "small"); variables.put("lires", 923874893); taskService.setVariables(taskDbid, variables);
and complete tasks
taskService.completeTask(taskDbid);
Tasks can also be offered to a set of candidates. Candidates can be users or groups. Users can take tasks for which they are a candidate. Taking a task means that this user will be set as the assignee. After that, other users will be blocked from taking the task.
People should not work on a task unless they are assigned to that task. The user interface should display forms and allow users to complete tasks if they are assigned to it. For unassigned tasks for which the user is a candidate, the only action that should be exposed is 'take'. Since taking a task boiles down to setting the assignee to the current user, we didn't introduce a separate method for that.
More on tasks in ???
This chapter will explain the jPDL file format for describing process definitions. The schemadocs can also serve as a quick reference for this information.
An example jPDL process file looks like this:
<?xml version="1.0" encoding="UTF-8"?> <process name="Purchase order" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="Verify supplier" /> </start> <state name="Verify supplier"> <transition name="Supplier ok" to="Check supplier data" /> <transition name="Supplier not ok" to="Error" /> </state> <decision name="Check supplier data"> <transition name="nok" to="Error" /> <transition name="ok" to="Completed" /> </decision> <end name="Completed" /> <end name="Error" /> </process>
The top level element representing one process definition.
Table 5.1. process
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | required | name or label of the process used to display to the process name in user interactions. | |
key | alpha numeric characters and underscores | if omitted, the key will be generated based on the name by replacing all non-alpha-numeric characters with underscores | optional | identification to distinct different process definitions. Multiple versions of a process with the same key can be deployed. The key:name combination must remain exactly the same for all deployed versions. |
version | integer | one higher then highest version number starting with 1 if no other process is deployed with the same name/key. | optional | version number of this process |
Table 5.2. process
elements:
Element | Multiplicity | Description |
---|---|---|
description | 0..1 | description text |
activities | 1..* | a list of any activity type can be placed here. At least
one start activity must be present.
|
Indicates where an execution for this process starts. Typically there is exactly one start activity in a process. A process has to have at least one start activity. A start activity must have exactly one outgoing transition and that transition is taken when a process execution starts.
Known limitation: for now, a process can not have more then
one start
.
Table 5.3. start
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | optional | name of the activity. Since a start activity cannot have incoming transitions, the name is optional. |
A wait state. Process execution will wait until an external trigger is
provided through the API. Apart from the
common activity content, state
doesn't have any extra
attributes or elements.
Let's look at an example which shows states connected with transitions as a sequence
<process name="StateSequence" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="a" /> </start> <state name="a"> <transition to="b" /> </state> <state name="b"> <transition to="c" /> </state> <state name="c" /> </process>
After you start an execution like this:
Execution execution = executionService.startProcessInstanceByKey("StateSequence");
the created process instance will be positioned in
state a
. Providing an external trigger can
be done with the signalExecution
methods.
String executionId = execution.getId(); execution = executionService.signalExecutionById(executionId);
In this second example with states, we'll show how you can use a
state
can be used to feed in an external choice of
the path to take.
<process name="StateChoice" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="wait for response" /> </start> <state name="wait for response"> <transition name="accept" to="submit document" /> <transition name="reject" to="try again" /> </state> <state name="submit document" /> <state name="try again" /> </process>
Let's start a new process instance for this process definition:
Execution execution = executionService.startProcessInstanceByKey("StateSequence");
Now, the execution is arrived in the wait for response
.
The execution will wait there until an external trigger is given. In case
a state
has multiple outgoing transitions, the signalName given
in the external trigger will be matched against the name of the outgoing transition
to take. So when we provide signalName accept
like this:
executionService.signalExecutionById(executionId, "accept");
Then the execution will continue over the outgoing transition named
accept
. Analogue, when signalName reject
is given in the signalExecutionXxx methods, the execution will continue over
the outgoing transition named reject.
Takes one path of many alternatives. Also known as a decision. A decision activity has multiple outgoing transitions and when an execution arrives in a decision activity, an automatic evaluation will decide which outgoing transition is taken.
A decision activity should be configured in one of the three following ways:
A decision with conditions on the transitions evaluates the condition in each transition. The first transition for which the nested condition expression resolves to true or which does not have a condition is taken.
Table 5.5. decision.transition.condition
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | expression | required | script that will be evaluated in the specified expression language. | |
lang | expression language | the default-expression-language taken from the script-manager configuration | optional | the language in which expr is
to be evaluated.
|
Example:
<process name="DecisionConditions" > <start> <transition to="evaluate document" /> </start> <decision name="evaluate document"> <transition to="submit document"> <condition expr="#{content=="good"}" /> </transition> <transition to="try again"> <condition expr="#{content=="not so good"}" /> </transition> <transition to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /> </process>
A decision expression evaluates to a String representing the name of an outgoing transition.
Table 5.6. decision
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | expression | required | script that will be evaluated in the specified expression language. | |
lang | expression language | the default-expression-language taken from the script-manager configuration | optional | the language in which expr is
to be evaluated.
|
Example:
<process name="Poolcar">
<start>
<transition to="How far?" />
</start>
<decision name="How far?" expr="#{distance}">
<transition name="far" to="Big car" />
<transition name="nearby" to="Small car" />
</decision>
<state name="Big car" />
<state name="Small car" />
</process>
When you start an new process instance like this
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("distance", "far"); Execution execution = executionService.startProcessInstanceByKey("Poolcar", variables);
then the new execution will go to activity Big car
.
A decision handler is a java class that implements the
DecisionHandler
interface. The decision handler
will be responsible for selecting the name of the outgoing transition.
public interface DecisionHandler { String select(OpenExecution execution); }
The handler is specified as a sub element of the decision
Table 5.7. decision.handler
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | required | fully qualified classname of the handler implementation class. |
Here's an example process of a decision using a DecisionHandler:
<process name="DecisionHandler"> <start> <transition to="evaluate document" /> </start> <decision name="evaluate document"> <handler class="org.jbpm.examples.decision.handler.ContentEvaluation" /> <transition name="good" to="submit document" /> <transition name="bad" to="try again" /> <transition name="ugly" to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /> </process>
The ContentEvaluation class looks like this
public class ContentEvaluation implements DecisionHandler { public String select(OpenExecution execution) { String content = (String) execution.getVariable("content"); if (content.equals("you're great")) { return "good"; } if (content.equals("you gotta improve")) { return "bad"; } return "ugly"; } }
Now, when we start a process instance and supply value
you're great
for variable content, then the
ContentEvaluation will return String good
and
the process instance will arrive in activity Submit document
.
With the fork
and join
activities,
concurrent paths of executions can be modeled.
For example:
<process name="ConcurrencyGraphBased" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="fork"/> </start> <fork name="fork"> <transition to="send invoice" /> <transition to="load truck"/> <transition to="print shipping documents" /> </fork> <state name="send invoice" > <transition to="final join" /> </state> <state name="load truck" > <transition to="shipping join" /> </state> <state name="print shipping documents"> <transition to="shipping join" /> </state> <join name="shipping join" > <transition to="drive truck to destination" /> </join> <state name="drive truck to destination" > <transition to="final join" /> </state> <join name="final join" > <transition to="end"/> </join> <end name="end" /> </process>
Ends the execution.
By default, an end activity will end the complete process instance. In case multiple concurrent executions are still active within the same process instance, all of them will be ended.
<process name="EndProcessInstance" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="end" /> </start> <end name="end" /> </process>
When a new process instance is created, it immediately ends.
Only the execution that arrives in the
end activity will be ended and other concurrent executions
should be left active. To get this behaviour, set
attribute ends="execution"
Table 5.8. end
execution attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
ends | {processinstance|execution} | processinstance | optional | specifies if the whole process instance should be ended or just the path of execution that arrives in the end activity. |
A process can have multiple end events. This can be handy to indicate different outcomes of a process instance. For example
<process name="EndMultiple" xmlns="http://;jbpm.org/4/jpdl"> <start> <transition to="get return code" /> <start> <state name="get return code"> <transition name="200" to="ok"/> <transition name="400" to="bad request"/> <transition name="500" to="internal server error"/> </state> <end name="ok"/> <end name="bad request"/> <end name="internal server error"/> </process>
Now if we would start an execution and signal it to move out of the get return code
wait state with the
following code, the execution would end with the bad request
end event.
Execution execution = executionService.startProcessInstanceByKey("EndMultiple"); String executionId = execution.getId(); execution = executionService.signalExecutionById(executionId, "400");
Likewise, using the value 200
or 500
would cause the execution
to end with the ok
or with the internal server error
end events
respectively.
An execution can also end with different states. It is another way to specify the outcome of a process.
It is indicated by the state
attribute of the end event or by the end-cancel
and end-error
shortcut notations.
Table 5.9. end
execution attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
state | String | optional | the state assigned to the execution. |
Take for example the following process.
<process name="EndState" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="get return code"/> </start> <state name="get return code"> <transition name="200" to="ok"/> <transition name="400" to="bad request" /> <transition name="500" to="internal server error"/> </state> <end name="ok" state="completed"/> <end-cancel name="bad request"/> <end-error name="internal server error"/> </process>
This time, if we would start an execution and signal it to move out of the get return code
wait state with the
following code, the execution would end with the cancel
state.
Execution execution = executionService.startProcessInstanceByKey("EndState"); String executionId = execution.getId(); execution = executionService.signalExecutionById(executionId, "400");
Similarly as above, using the value 200
or 500
would cause the execution
to end with the completed
or with the error
states
respectively.
Creates a task for a person in the task component.
A simple task that will be assigned to a specific user
Table 5.10. task
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
assignee | expression | optional | userId referring to the person that is responsible for completing this task. |
<process name="TaskAssignee">
<start>
<transition to="review" />
</start>
<task name="review"
assignee="#{order.owner}">
<transition to="wait" />
</task>
<state name="wait" />
</process>
This process shows 2 aspects of task assignment. First, that the
attribute assignee
is used to indicate the user that is
responsible for completing the task. The assignee is a String property
of a task and refers to a user.
Secondly, this attribute is by default evaluated as an expression.
In this case the task is assigned to #{order.owner}
.
Which means that first an object is searched for with name order. One of
the places where this object is looked up is the process variables
associated to the task. Then the getOwner()
getter
will be used to get the userId that references the user that is
responsible for completing this task.
Here's the Order class used in our example:
public class Order implements Serializable { String owner; public Order(String owner) { this.owner = owner; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } }
Next a new process instance is created with an order as a process variable.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("order", new Order("johndoe")); Execution execution = executionService .startProcessInstanceByKey("TaskAssignee", variables);
Then the task list for johndoe
can be obtained like this.
List<Task> taskList = taskService.findAssignedTasks("johndoe");
Note that it is also possible to put plain text like
assignee="johndoe"
. In that case
the task will be assigned to johndoe.
A task that will be offered to a group of users. One of the users should then take the task in order to complete it.
Table 5.11. task
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
candidate-groups | expression | optional | resolves to a comma separated list of groupIds. All the people in the groups will be candidates for this task. | |
candidate-users | expression | optional | resolves to a comma separated list of userIds. All the users will be candidates for this task. |
Here's an example process using task candidates:
<process name="TaskCandidates">
<start>
<transition to="review" />
</start>
<task name="review"
candidate-groups="sales-dept">
<transition to="wait" />
</task>
<state name="wait"/>
</process>
After starting, a task will be created. The task will not show up in anyone's personal task list. Following task lists will be empty.
taskService.getAssignedTasks("johndoe"); taskService.getAssignedTasks("joesmoe");
But the task will show up in the takable task list of all members of the sales-dept
group.
The in our example, the sales-dept
has two members: johndoe and joesmoe
identityService.createGroup("sales-dept"); identityService.createUser("johndoe", "johndoe", "John", "Doe"); identityService.createMembership("johndoe", "sales-dept"); identityService.createUser("joesmoe", "joesmoe", "Joe", "Smoe"); identityService.createMembership("joesmoe", "sales-dept");
So after the process is created, the task will appear in both the takable tasks for users johndoe and joesmoe
taskService.findTakableTasks("johndoe"); taskService.findTakableTasks("joesmoe");
Candidates must take a task before they can work on it. This will prevent that two candides start working on the same task. The user interface must only offer the action 'Take' for the tasks in the takable task list.
taskService.takeTask(task.getDbid(), "johndoe");
When a user takes a task, the assignee of that task will be set to the given user. The task will disappear from all the candidate's takable task list and it will appear in the user's assigned tasks.
Users are only allowed to work on tasks in their personal task list. This should be enforced by the user interface.
Similarly, the attribute candidate-users
can be used that
resolves to a comma separated list of userIds. The candidate-users
attribute can be used in combination with other assignment options.
An AssignmentHandler
can be used to calculate the
assignee and the candidates for a task programmatically.
public interface AssignmentHandler extends Serializable {
/** sets the actorId and candidates for the given assignable. */
void assign(Assignable assignable, OpenExecution execution) throws Exception;
}
Assignable
is a common interface for Tasks and
Swimlanes. So AssignmentHandlers can be used for tasks as well as swimlanes
(see later).
assignment-handler
is a sub element of the task element.
It specifies a user code object. So the attributes and elements of assignment-handler
are documented in Section 5.7, “User code”
Let's look at the task assignment example process.
<process name="TaskAssignmentHandler" xmlns="http://jbpm.org/4.0/jpdl">
<start g="20,20,48,48">
<transition to="review" />
</start>
<task name="review" g="96,16,127,52">
<assignment-handler class="org.jbpm.examples.task.assignmenthandler.AssignTask">
<field name="assignee">
<string value="johndoe" />
</field>
</assignment-handler>
<transition to="wait" />
</task>
<state name="wait" g="255,16,88,52" />
</process>
The referenced class AssignTask
looks like this:
public class AssignTask implements AssignmentHandler { String assignee; public void assign(Assignable assignable, OpenExecution execution) { assignable.setAssignee(assignee); } }
Please note that potentially, AssignmentHandler implementations can use the process variables and any other Java API to access resources like your application database to calculate the assignee and candidate users and groups.
Starting a new process instance of the TaskAssignmentHandler
process will immediately bring the new execution to the task activity. A new
review
task is created and at that point, the AssignTask
assignment handler is called. That will set johndoe
as
the assignee. So John Doe will find the task in his personal task list.
Multiple tasks in a process should be assigned to the same user or candidates. Multiple tasks in a process can be associated to a single swimlane. The process instance will remember the candidates and user that performed the first task in the swimlane. And subsequent tasks in the same swimlane will be assigned to those user and candidates.
A swimlane can also be considered as a process role. In some cases, this might boil down to authorization roles in the identity component. But bare in mind that it is not always the same thing.
Table 5.12. task
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
swimlane | swimlane (string) | optional | refers to a swimlane that is declared in the process |
Swimlanes can be declared inside a process element:
Table 5.13. swimlane
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | swimlane (string) | required | Name for this swimlane. This is the name that will be referenced by task swimlane attributes. | |
assignee | expression | optional | userId referring to the person that is responsible for completing this task. | |
candidate-groups | expression | optional | resolves to a comma separated list of groupIds. All the people in the groups will be candidates for this the tasks in this swimlane. | |
candidate-users | expression | optional | resolves to a comma separated list of userIds. All the users will be candidates for the tasks in this swimlane. |
The task swimlane example has the following process file :
<process name="TaskSwimlane" xmlns="http://jbpm.org/4.0/jpdl"> <swimlane name="sales representative" candidate-groups="sales-dept" /> <start> <transition to="enter order data" /> </start> <task name="enter order data" swimlane="sales representative"> <transition to="calculate quote"/> </task> <task name="calculate quote" swimlane="sales representative"> </task> </process>
In this example we create the following information in the identity component:
identityService.createGroup("sales-dept"); identityService.createUser("johndoe", "johndoe", "John", "Doe"); identityService.createMembership("johndoe", "sales-dept");
After starting a new process instance, user johndoe
will
be a candidate for task enter order data
. Again like in the
previous task candidates example, John Doe can now take this task like this:
taskService.takeTask(taskDbid, "johndoe");
Taking the task will make Litjohndoe
the assignee for
the task. And since this task is coupled to the swimlane
sales representative
, assignee johndoe
will
also be propagated as the assignee in the swimlane.
Next, John Doe can complete the task like this:
taskService.completeTask(taskDbid);
Completing the task will bring the process execution to the
next task, which is calculate quote
. Also
this task is linked to the swimlane. Therefore, the task will be
assigned to johndoe
. Also the candidate users
and candidate groups of the initial assignment will be copied from
the swimlane to the task. This is relevant in case user johndoe
would release the task and offer it back to the other candidates.
Tasks can see and update process variables. Later tasks will have the option to declare task-local process variables. Task variables are an important part of the task forms. Task forms typically show data that comes from the task and the process instance. Then input from the user is translated in setting task variables.
Getting task variables can be done like this:
List<Task> taskList = taskService.findAssignedTasks("johndoe"); Task task = taskList.get(0); long taskDbid = task.getDbid(); Set<String> variableNames = taskService.getVariableNames(taskDbid); Map<String, Object> variables = taskService.getVariables(taskDbid, variableNames);
And setting task variables can be done like this:
variables = new HashMap<String, Object>(); variables.put("category", "small"); variables.put("lires", 923874893); taskService.setVariables(taskDbid, variables);
Creates a sub process instance and waits till it is completed. When the sub process instance completes, then the execution in the sub-process will continue.
Table 5.14. sub-process
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
sub-process-id | string | either this or sub-process-key is required | Identifies the sub process by the id. This means that a specific version of a process definition is referenced. | |
sub-process-key | string | either this or sub-process-key is required | Identifies the sub process by the key. This means that the latest version of the process definition with the given key is referenced. The latest version of the process is looked up each time the activity executes. | |
outcome | expression | required when transitions have outcome-value 's specified | Expression that is evaluated when the sub process
instance ends. The value is then used for outcome transition mapping.
Add outcome-value elements to the outgoing transitions
of this sub-process activity.
|
Table 5.15. process
elements:
Element | Multiplicity | Description |
---|---|---|
variable | 0..* | Declares a variable that is passed to the sub process instance when it is created. |
out-variable | 0..* | Declares a variable that will be set in the parent execution when the sub process ends. |
Table 5.16. Extra transition
elements in case of outcome variable mappings:
Element | Multiplicity | Description |
---|---|---|
outcome-value | 0..1 | If the outcome matches the value, this
transition is taken after the sub-process ended. The value is specified with one child
element.
|
The SubProcessVariables example scenario will show the basic workings of the sub-process activity, how to feed information in the sub process when it starts and how to extract information out of the subprocess when it ends.
The parent process involves a document that needs to be reviewed.
<process name="SubProcessDocument" xmlns="http://jbpm.org/4.0/jpdl">
<start>
<transition to="review" />
</start>
<sub-process name="review"
sub-process-key="SubProcessReview">
<variable name="document" init="#{document}" />
<out-variable name="reviewResult" init="#{result}" />
<transition to="wait" />
</sub-process>
<state name="wait"/>
</process>
The review process is a reusable process for all kinds of reviews.
<process name="SubProcessReview" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="get approval"/> </start> <task name="get approval" assignee="johndoe"> <transition to="end"/> </task> <end name="end" /> </process>
The document process is started with a document variable:
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("document", "This document describes how we can make more money..."); ProcessInstance processInstance = executionService .startProcessInstanceByKey("SubProcessDocument", variables);
Then the parent process execution will arrive in the sub process
activity. A sub process instance is created and linked with the super
process execution. When the SubProcessReview
process
instance starts, it arrives in the task
. A task will be
created for johndoe
.
List<Task> taskList = taskService.findAssignedTasks("johndoe"); Task task = taskList.get(0);
We can see that the document has been passed from the super process instance to the sub process instance:
String document = (String) taskService.getVariable(task.getDbid(), "document"); assertEquals("This document describes how we can make more money...", document);
Then we set a variable on the task. This is typically done through a form. But here we'll show how it is done programmatically.
taskService.setVariable(task.getDbid(), "result", "accept");
Completing this task, will cause the sub process instance to end.
taskService.completeTask(task.getDbid());
When the sub process ends, the super process execution will get signalled(=notified).
First the result
variable from the sub process instance
will be copied into the reviewResult
variable in the
super process execution. Then the super process execution will continue
and leave the review activity.
In the SubProcessOutcomeValueTest
example, the value
of a sub process variable is used to select the outgoing transition
of the sub-process
activity.
<process name="SubProcessDocument"> <start> <transition to="review" /> </start> <sub-process name="review" sub-process-key="SubProcessReview" outcome="#{result}"> <transition name="ok" to="next step" /> <transition name="nok" to="update" /> <transition name="reject" to="close" /> </sub-process> <state name="next step" /> <state name="update" /> <state name="close" /> </process>
The SubProcessReview
is the same as above in the
subprocess variables example:
<process name="SubProcessReview" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="get approval"/> </start> <task name="get approval" assignee="johndoe"> <transition to="end"/> </task> <end name="end" /> </process>
A new document process instance is started like usual:
ProcessInstance processInstance = executionService .startProcessInstanceByKey("SubProcessDocument");
Then task is fetched from johndoe
's task list
List<Task> taskList = taskService.findAssignedTasks("johndoe"); Task task = taskList.get(0);
Then the result
variable is set and
the task is completed.
taskService.setVariable(task.getDbid(), "result", "ok"); taskService.completeTask(task.getDbid());
In this scenario, the ok
transition is taken in
the parent process out of the sub-process review activity. The example
test case also shows other scenarios.
A process can have many end activities. In the SubProcessOutcomeActivityTest
example, the resulting end activity is used to select the outgoing transition of the sub-process
activity.
<process name="SubProcessDocument"> <start> <transition to="review" /> </start> <sub-process name="review" sub-process-key="SubProcessReview"> <transition name="ok" to="next step" /> <transition name="nok" to="update" /> <transition name="reject" to="close" /> </sub-process> <state name="next step" /> <state name="update" /> <state name="close" /> </process>
The SubProcessReview
now has multiple end activities:
<process name="SubProcessReview" xmlns="http://jbpm.org/4.0/jpdl">
<start>
<transition to="get approval"/>
</start>
<task name="get approval"
assignee="johndoe">
<transition name="ok" to="ok"/>
<transition name="nok" to="nok"/>
<transition name="reject" to="reject"/>
</task>
<end name="ok" />
<end name="nok" />
<end name="reject" />
</process>
A new document process instance is started like usual:
ProcessInstance processInstance = executionService .startProcessInstanceByKey("SubProcessDocument");
Then task is fetched from johndoe
's task list
List<Task> taskList = taskService.findAssignedTasks("johndoe"); Task task = taskList.get(0);
Then the task is completed with outcome ok
.
taskService.completeTask(task.getDbid(), "ok");
This will cause the sub process to end in end activity ok
.
The super process execution will then take outgoing transition ok
to next step
.
The example test case also shows the other scenarios.
The Java task. A process execution will execute the method of the class that is configured in this activity.
Table 5.17. java
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | required | The fully qualified classname. | |
method | methodname | required | The name of the method to invoke | |
var | variablename | optional | The name of the variable in which the return value should be stored. |
Table 5.18. java
elements:
Element | Multiplicity | Description |
---|---|---|
field | 0..* | describes a configuration value to inject in a memberfield before the method is invoked. |
arg | 0..* | method parameters |
Consider the following example.
<process name="Java" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="invoke java method" /> </start> <java name="invoke java method" class="org.jbpm.examples.java.JohnDoe" method="hello" var="answer"> <field name="state"><string value="fine"/></field> <field name="session"><env type="org.hibernate.Session"/></field> <arg><string value="Hi, how are you?"/></arg> <transition to="wait" /> </java> <state name="wait"> </process>
The java task specifies that during its execution an instance of the class org.jbpm.examples.java.JohnDoe
will be instantiated and the method hello
of this class will be invoked on the resulting object. The variable named
answer
will contain the result of the invocation. Let's look at the class JohnDoe
below.
package org.jbpm.examples.java; import org.hibernate.Session; public class JohnDoe { String state; Session session; public String hello(String msg) { if ( (msg.indexOf("how are you?")!=-1) && (session.isOpen()) ) { return "I'm "+state+", thank you."; } return null; } }
The class above reveals that it contains two fields named state
and session
and that the method hello
accepts one argument. During the execution the values specified in the
field
and arg
configuration elements will be used. The expected result of creating
a process instance is that the process variable answer
contains the string
I'm fine, thank you.
.
A script activity evaluates a script. Scripts can be specified in any language for which there is a JSR-223 compliant scripting engine. Configuration of scripting engines is explained below.
There are 2 ways of specifying a script:
The script is provided with the expr
attribute.
This is for short expressions that are easier expressed in an attribute
then in a text element. If no lang
is specified,
the default-expression-language is used.
Table 5.19. script
expression attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | text | required | the expression text to evaluate. | |
lang | scripting language name as defined in Chapter 7, Scripting | the default expression language as defined in Chapter 7, Scripting | optional | the language in which the expression is specified. |
var | variablename | optional | name of the variable in which the return value should be stored. |
In the next example, we'll see how a script activity with an expression and how the result is stored in a variable.
<process name="ScriptExpression" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="invoke script" /> </start> <script name="invoke script" expr="Send packet to #{person.address}" var="text"> <transition to="wait" /> </script> <state name="wait"/> </process>
This example uses a Person
class that looks like this.
public class Person implements Serializable { String address; public Person(String address) { this.address = address; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
When starting a process instance for this process, we supply a person
with a given address property as variable person
.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("person", new Person("Honolulu")); executionService.startProcessInstanceByKey("ScriptText", variables);
After the execution of the script activity, variable text
will contain 'Send packet to Honolulu'.
The second way of specifying a script is with a text
element.
This is convenient when the script text spans multiple lines.
Table 5.20. script
text attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
lang | scripting language name as defined in Chapter 7, Scripting | the default scripting language as defined in Chapter 7, Scripting | optional | the language in which the script is specified. |
var | variablename | optional | name of the variable in which the return value should be stored. |
For example
<process name="ScriptText" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="invoke script" /> </start> <script name="invoke script" var="text"> <text> Send packet to #{person.address} </text> <transition to="wait" /> </script> <state name="wait"/> </process>
Execution of this process is exactly the same as with the script expression above.
An esb
activity sends a message to a service over the ESB.
The attributes category
and service
identify the
service in the esb repository. The message is composed with the
part
elements.
Table 5.22. esb
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
category | string | required | the esb category where the service is defined. | |
service | string | required | the esb name of the service |
For example
<process name="Esb" xmlns="http://jbpm.org/4.0/jpdl"> <start > <transition to="invoke esb service" /> </start> <esb name="invoke esb service" category="orderProcessing" service="bookSold"> <part name="bookTitle" expr="#{title}" /> <part name="goal"> <string value="deliver asap" /> </part> <transition to="wait" /> </esb> <state name="wait" /> </process>
When a new process is started, a message will be sent to the esb
service bookSold
in category orderProcessing
.
The message will have 2 parts: one named bookTitle
containing
the title process variable. And one named goal
which
contains the text 'deliver asap'.
With thehql
activity, a HQL query can be performed
on the database and the result is stored in a process variable.
Table 5.24. hql
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
var | variablename | required | the name of the variable in which the result is stored. | |
unique | {true, false} | false | optional | a value of true means that the result from the hibernate
query should be obtained with method uniqueResult() .
The default is false and in that case the list()
method will be used to get the result.
|
Table 5.25. hql
elements:
Element | Multiplicity | Description |
---|---|---|
query | 1 | The HQL query. |
parameter | 0..* | The query parameters |
For example:
<process name="Hql" xmlns="http://jbpm.org/4.0/jpdl"> <start> <transition to="get process names" /> </start> <hql name="get process names" var="activities with o"> <query> select activity.name from org.jbpm.pvm.internal.model.ActivityImpl as activity where activity.name like :activityName </query> <parameters> <string name="activityName" value="%o%" /> </parameters> <transition to="count activities" /> </hql> <hql name="count activities" var="activities" unique="true"> <query> select count(*) from org.jbpm.pvm.internal.model.ActivityImpl </query> <transition to="wait" /> </hql> <state name="wait"/> </process>
The sql
activity is exactly the same as the
hql activity, with the only difference that
session.createSQLQuery(...)
is used.
Unless specified otherwise above, all activities also include this content model:
Table 5.26. Common activity attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | required | name of the activity |
Table 5.27. Common activity elements:
Element | Multiplicity | Description |
---|---|---|
transition | 0..* | the outgoing transitions |
Events specify points in a process on which a list of event listeners can be registered. When an execution passes that point in the process, the event listeners are notified. The events and listeners are not shown in the graphical view of the process. An event is fired by an element in the process definition like e.g. the process definition, an activity or a transition.
The EventListener interface looks like this:
public interface EventListener extends Serializable {
void notify(EventListenerExecution execution) throws Exception;
}
All automatic activities can be used as event listeners as well.
To associate a list of event listeners with a process or an activity, use
the on
element to group the event listeners and specifiy the
event. on
can be nested as a subelement of process
or any activity.
To associate a list of event listeners with a transition take
event, just include the event listeners directly in the transition
element.
Table 5.28. on
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
event | {start | end} | required | name name of the event |
Table 5.29. on
elements:
Element | Multiplicity | Description |
---|---|---|
event-listener | 0..* | An event listener implementation object. |
any automatic activity | 0..* |
Let's look at an example process with event listeners:
<process name="EventListener" xmlns="http://jbpm.org/4.0/jpdl"> <on event="start"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"/> </on> <on event="end"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"/> </on> <start> <transition to="wait" name=""/> </start> <state name="wait"> <on event="start"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"/> </on> <on event="end"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"/> </on> <transition to="end" name=""> <event-listener class="org.jbpm.examples.eventlistener.LogListener"/> </transition> </state> <end name="end"/> </process>
LogListener
will maintain a list of logs in a static member
field:
public class LogListener implements EventListener {
public static List<String> logs; // initialization done in test method
public void notify(EventListenerExecution execution) {
logs.add(execution.getEvent()+" on "+execution.getEventSource());
}
}
Next, we start a new process instance.
Execution execution = executionService.startProcessInstanceByKey("EventListener");
Then the process instance executes up to the wait activity. So we provide a signal and that will cause it to execute till the end.
executionService.signalExecutionById(execution.getId());
The list of log messages will now look like this:
[event(start) on process(EventListener), event(start) on activity(wait), event(end) on activity(wait), event(take) on (wait)-->(end), event(end) on process(EventListener)]
A timer can be specified in the transition
element in wait state activities such as state
s,
task
s, sub-process
es and
super-state
s.
When such a timer fires, that transition is taken.
A timer can also be specified in custom events in wait state
activities such as state
s,
task
s, sub-process
es and
super-state
s. The timer
element
should then be the first element in the on
element representing
the event. In that case the event fires upon the duedate of the timer.
Timers are created when the activity is entered. The timer can fire
when the execution remains in the activity until the duedate
.
When the execution leaves the activity, the timer is cancelled.
Table 5.30. timer
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
duedate | duedate expression | required | Specifies when the timer needs to fire. For
example: 20 minutes or
3 business days
| |
repeat | duedate expression | optional | When a timer fires, this attribute specifies when the timer
needs to fire again. For example: 20 minutes or
3 business days
|
A duedate expression has the following syntax:
quantity [business] {second | seconds | minute | minutes | hour | hours | day | days | week | weeks | month | months | year | years}
where quantity
is a positive integer.
And adding the optional indication business
means
that only business hours should be taken into account for this duration. Without
the indication business, the duration will be interpreted as an absolute time period.
How to configure business hours is explained in Section 5.6.2, “Business calendar”
The default configuration will contain a reference to the file
jbpm.business.calendar.xml
. That contains a
configuration of business hours in the following format:
<?xml version="1.0" encoding="UTF-8"?> <jbpm-configuration xmlns="http://jbpm.org/xsd/cfg"> <process-engine-context> <business-calendar> <monday hours="9:00-12:00 and 12:30-17:00"/> <tuesday hours="9:00-12:00 and 12:30-17:00"/> <wednesday hours="9:00-12:00 and 12:30-17:00"/> <thursday hours="9:00-12:00 and 12:30-17:00"/> <friday hours="9:00-12:00 and 12:30-17:00"/> <holiday period="01/07/2008 - 31/08/2008"/> </business-calendar> </process-engine-context> </jbpm-configuration>
For an example of where the business calendar is used, see ???
Example TimerTransitionTest shows how to put a timer on a transition.
<process name="TimerTransition" xmlns="http://jbpm.org/4.0/jpdl">
<start>
<transition to="guardedWait" />
</start>
<state name="guardedWait">
<transition name="go on" to="next step" />
<transition name="timeout" to="escalation">
<timer duedate="10 minutes" />
</transition>
</state>
<state name="next step" />
<state name="escalation" />
</process>
When an process instance for this process is started, it arrives immediately
in the guardedWait
state. At that time, a timer is created that will fire
after 10 minutes.
Execution processInstance = executionService .startProcessInstanceByKey("TimerTransition");
With the following query, we can query for the timers related to the newly created processInstance. We know that there should be exactly one such timer.
Job job = managementService.createJobQuery() .timers() .processInstanceId(processInstance.getId()) .uniqueResult();
In a unit test, we won't use the JobExecutor to execute the timer. Instead, we execute timers directly in the thread of the unit test. That way it is easy to simulate one scenario though an execution.
So as the next step, we assume that the timer will fire. We simulate this by executing the timer programmatically:
managementService.executeJob(job.getDbid());
After that the process instance will have taken the
timeout
transition and moved to the escalation state.
processInstance = executionService.findExecutionById(processInstance.getId()); assertEquals("escalation", processInstance.getActivityName());
The second scenario in TimerTransitionTest shows that the
timer is cancelled in case the signal go on
is given
before the timer fires. In that case the execution ends up in the
next step
.
Example TimerEventTest shows how to put a timer on a custom event.
<process name="TimerEvent" xmlns="http://jbpm.org/4.0/jpdl">
<start>
<transition to="guardedWait" />
</start>
<state name="guardedWait" >
<on event="timeout">
<timer duedate="10 minutes"/>
<event-listener class="org.jbpm.examples.timer.event.Escalate" />
</on>
<transition name="go on" to="next step" />
</state>
<state name="next step" />
</process>
In this case, if the execution is not signalled within 10 minutes after the
activity is started, the event timeout
is fired
and the event listener org.jbpm.examples.timer.event.Escalate
will be notified.
Again, if the guardedWait
activity is ended within
10 minutes, then the timer is cancelled and the Escalate
event listener will not be notified.
Example TimerBusinessTimeTest shows how business time works.
<process name="TimerBusinessTime" xmlns="http://jbpm.org/4.0/jpdl">
<start>
<transition to="guardedWait" />
</start>
<state name="guardedWait" >
<transition name="go on" to="next step" />
<transition name="timeout" to="escalation" >
<timer duedate="9 business hours" />
</transition>
</state>
<state name="next step" />
<state name="escalation" />
</process>
Suppose that a new TimerBusinessTime
process instance is started
at 11:30am on a tuesday. The default configured business calendar specifies working hours
between 9:00-12:00 and 12:30-17:00. So 9 business hours later results in an actual duedate
for the timer of wednesday 13:00 (1pm).
Since we do not know when the TimerBusinessTimeTest will be ran, we only assert in the test that the actual duedate of the scheduled timer at least 24 hours ahead.
Example TimerRepeatTest shows how to put a timer with a repeat. The attribute
repeat
on a timer will cause the timer to be rescheduled automatically
after it is executed.
<process name="TimerRepeat" xmlns="http://jbpm.org/4.0/jpdl">
<start>
<transition to="guardedWait" />
</start>
<state name="guardedWait">
<on event="timeout">
<timer duedate="20 minutes" repeat="10 seconds" />
<event-listener class="org.jbpm.examples.timer.repeat.Escalate" />
</on>
<transition name="go on" to="next step"/>
</state>
<state name="next step"/>
</process>
When a new process is started, a timer is created and the duedate will be 20 minutes ahead. When the timer fires, a new timer will be created with a duedate of 10 seconds ahead. When that timer fires, a new timer will be created again 10 seconds ahead. And so on.
New timers will be created each time the timer fires until the
guardedWait
state activity is ended with a signal.
When the guardedWait
state activity is ended, the
existing timer will be cancelled.
Various elements in the jPDL process language refer to a an
object on which an interface method will be invoked. See for example
Section 5.2.6.3, “task
assignment handler”. This section describes the
attributes and sub elements of those type of user code objects.
Table 5.31. attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | required | The fully qualified classname. |
Table 5.32. sub elements:
Element | Multiplicity | Description |
---|---|---|
field | 0..* | describes a configuration value to be injected directly in a memberfield before this user class is used. |
property | 0..* | describes a configuration value to injected through a setter method before this user object is used. |
Table 5.33. field
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | string | required | the name of the field |
Scripting in jBPM is based on JSR 223: Scripting for the JavaTM Platform. Scripting engines can be configured like this:
<script-manager default-expression-language="juel" default-script-language="juel" read-contexts="execution, environment, process-engine" write-context=""> <script-language name="juel" factory="com.sun.script.juel.JuelScriptEngineFactory" /> </script-manager>
A jPDL process definition can contain scripts and expressions. All of the configured scripting engines can be used in each situation. But scripts and expressions each have their own default.
The default jBPM identity component is based on JBoss IDM. Configuration is like this:
<jbpm-configuration xmlns="http://jbpm.org/xsd/cfg"> <process-engine-context> ... <identity-service /> ... </process-engine-context> <transaction-context> ... <identity-session realm="realm://jbpm-identity" /> </transaction-context> </jbpm-configuration>
To replace the identity component, keep the identity-service declaration, implement org.jbpm.session.IdentitySession and configure your identity session in the transaction context like this:
<jbpm-configuration xmlns="http://jbpm.org/xsd/cfg"> ... <transaction-context> ... <object class="your.package.YourIdentitySession" /> </transaction-context> </jbpm-configuration>
jBPM provides integration with JBoss 4.2.x and JBoss 5.0.0.GA. As part of the installation, the ProcessEngine and a deployer for jBPM archives will be installed as a JBoss service.
After a successful installation you should see that the ProcessEngine has been started and bound to JNDI:
[...] 14:12:09,301 INFO [JBPMService] jBPM 4 - Integration JBoss 4 14:12:09,301 INFO [JBPMService] 4.0.0.Beta1 14:12:09,301 INFO [JBPMService] ProcessEngine bound to: java:/ProcessEngine
When jBPM is deployed on a JBoss instance, process deployments are treated like any other deployment artifact (i.e. *.war, *.ear) and processed by the JBPMDeployer. In order to deploy a process archive simply create a *.jpdl archive (zip file) that contains the process definition (*.jpdl.xml) and all required resources to execute the process (i.e. classes, property files):
Bonanova:Desktop hbraun$ jar -tf OrderProcess.jpdl META-INF/MANIFEST.MF OrderProcess.jpdl.xml org/mycompany/order/*.class
In order to deploy a process archive simply copy it to $JBOSS_HOME/server/<config>/deploy:
(1) cp OrderProcess.jpdl $JBOSS_HOME/server/default/deploy (2) less $JBOSS_HOME/server/default/log [...] 2009-04-08 14:12:21,947 INFO [org.jbpm.integration.jboss4.JBPMDeployer] Deploy file:/Users/hbraun/dev/prj/jboss/tags/JBoss_4_2_2_GA /build/output/jboss-4.2.2.GA/server/default/deploy/OrderProcess.jpdl
In order to remove a process simply remove the process archive from the deploy directory.
TBD: A prelimenary explanation cn be found here
As described above the ProcessEngine will be installed as JBoss service and bound to JNDI. This means that any EE component (i.e. servlet, ejb) can access it doing a JNDI lookup:
private ProcessEngine processEngine; [...] try { InitialContext ctx = new InitialContext(); this.processEngine = (ProcessEngine)ctx.lookup("java:/ProcessEngine"); } catch (Exception e) { throw new RuntimeException("Failed to lookup process engine"); }
Once you obtained an instance of the ProcessEngine you can invoke on it as described in chapter services
UserTransaction tx = (UserTransaction)ctx.lookup("UserTransaction"); (1) Environment env = ((EnvironmentFactory)processEngine).openEnvironment(); try { ExecutionService execService = (ExecutionService) this.processEngine.get(ExecutionService.class); // begin transaction tx.begin(); // invoke on process engine executionService.signalExecutionById("ICL.82436"); // commit transaction tx.commit(); } catch (Exception e) { if(tx!=null) { try { tx.rollback(); } catch (SystemException e1) {} } throw new RuntimeException("...", e); } finally { env.close(); }
(1) Wrapping the call in a UserTransaction is not necessary if the invocation comes a CMT component, i.e. an EJB.
jBPM 4 takes advantage of the JavaMail API to make high-level email services available to business process authors.
Producers are responsible for creating email messages within jBPM. All mail producers
implement the org.jbpm.pvm.internal.email.spi.MailProducer
interface.
A default mail producer is available out of the box to address typical email needs.
The default mail producer is capable of creating email messages with text, HTML and attachments from a template. Templates can be provided inline or in the process-engine-context section of the jBPM configuration. Templates may contain expressions which are evaluated through the script manager. Refer to Scripting for details.
The following listing presents a mail activity with an inline template.
<mail name="rectify" language="juel"> (1) <from addresses='winston@minitrue' /> (2) <to addresses='julia@minitrue, obrien@miniluv'/> (3) <cc users='bigbrother'/> <bcc groups='thinkpol, innerparty'/> <subject>Part ${part} Chapter ${chapter}</subject> (4) <text>times ${date} reporting bb dayorder doubleplusungood (5) refs ${unpersons} rewrite fullwise upsub antefiling</text> <html><table><tr><td>times</td><td>${date}</td> (6) <td>reporting bb dayorder doubleplusungood refs ${unpersons} rewrite fullwise upsub antefiling</td> </tr></table></html> <attachments> (7) <attachment url='http://www.george-orwell.org/1984/3.html'/> <attachment resource='org/example/pic.jpg'/> <attachment file='${user.home}/.face'/> </attachments> </mail>
Expressions within the template are written in the scripting language indicated here. If not specified, the default expression language will be assumed.
List of message senders. Senders are either identified directly by their email addresses or appointed by means of the identity model.
Lists of message recipients, categorized as follows: To (primary), CC (carbon copy) and BCC (blind carbon copy). Like senders, recipients are directly identified by their email addresses or appointed by means of the identity model.
Character data contained in element subject
are used as the message subject.
Character data contained in element text
are used as the plain text content of the message.
Nodes contained in element html
are used as the HTML content of the message.
Attachments can be specified as absolute URLs, classpath resources or local files.
Note that every section of the template is amenable to expression evaluation.
For complex emails or custom generation of attachments, see: Extension Points: Custom Emails.
Mail templates are available to externalize commonly used messages from process definitions. Templates are placed in the process-engine-context section of your configuration file. All elements available to inline templates, as described in the previous section are available to external templates. Consider the fragment below.
<jbpm-configuration> <process-engine-context> <mail-template name="rectify-template"> <!-- same elements as inline template --> </mail-template> </process-engine-context> </jbpm-configuration>
Each template must have an unique name. Mail activities may reference the template
through the template
attribute, as follows.
<mail name="rectify" template="rectify-template />
Mail servers are declared in the configuration file. The mail-server
element describes an SMTP mail server capable of sending email messages.
Because jBPM uses JavaMail to send mail, all properties supported by JavaMail are also
exposed to jBPM. Within the session-properties
child element,
the SMTP properties must be provided as shown in the example below.
See the Sun JavaMail API for more information on supported properties: Sun SMTP Properties.
<jbpm-configuration> <transaction-context> <mail-session> <mail-server> <session-properties> <property name="mail.smtp.host" value="localhost" /> <property name="mail.smtp.port" value="2525" /> <property name="mail.from" value="noreply@jbpm.org" /> </session-properties> </mail-server> </mail-session> </transaction-context> </jbpm-configuration>
If the "From" attribute is not present in an outgoing message, the value of the
mail.from
property will be used instead.
Multiple SMTP server support has been added to jBPM 4 to accommodate a wider variety of organizational server structures. For example, this is useful for companies that have both internal and external SMTP servers.
To setup multiple SMTP mail servers, declare multiple mail servers within the
configuration file, as described below. The tag address-filter
exists
to define which domains are serviced by each mail server. The address filter consists
of regular expressions that determine whether an address will be processed by a given
server.
See the Sun Pattern API for more information on supported regular expressions: Sun Regex Patterns.
<jbpm-configuration> <transaction-context> <mail-session> <mail-server> <address-filter> <include>.+@jbpm.org</include> </address-filter> <session-properties> <property name="mail.smtp.host" value="int.smtp.jbpm.org" /> <property name="mail.from" value="noreply@jbpm.org" /> </session-properties> </mail-server> <mail-server> <address-filter> <exclude>.+@jbpm.org</exclude> </address-filter> <session-properties> <property name="mail.smtp.host" value="ext.smtp.jbpm.org" /> <property name="mail.from" value="noreply@jbpm.org" /> </session-properties> </mail-server> </mail-session> </transaction-context> </jbpm-configuration>
Address filters follow the logic below to accept an address.
Address is accepted if it is included and not excluded.
Absence of includes implies the address is included.
Absence of excludes implies the address is not excluded.
jBPM 4 allows the creation of your own Mail Producers to address an organization's
specific email needs. To do so, users must implement the
org.jbpm.pvm.internal.email.spi.MailProducer
interface. The method
produce
will return one or more Message
objects,
which will be sent through the MailSession
.
Generation of custom attachments at runtime can be easily implemented in jBPM 4.
By extending the default mail producer, or implementing your own with the
MailProducer
interface, attachments can be generated and
added to email messages at runtime.
The following is an example of how to extend MailProducerImpl
to add an extra attachment to every outgoing mail.
public class CustomMailProducer extends MailProducerImpl { protected void addAttachments(Execution execution, Multipart multipart) { // have default mail producer create attachments from template super.addAttachments(execution, multipart); // create a body part to carry the content BodyPart attachmentPart = new MimeBodyPart(); // set content provided by an arbitrary data handler attachmentPart.setDataHandler(...); // attach content multipart.addBodyPart(attachmentPart); } }