This documentation is best viewed in firefox. There are some known issues with internet explorer.
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/projects/jbpm/files/The source code for this component can be found in the jBPM SVN repository:
https://anonsvn.jboss.org/repos/jbpm/jbpm4/jBPM 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 in practice.
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.
Migration from jBPM 3 to jBPM 4 is not supported. Check out the developers guide for hints on how to perform the migration.
When reporting a problem in the user forum or in our support portal, please use the following snippet as a template:
=== Environment ============================== - jBPM Version : which version of jBPM are you using? - Database : which database and which version of that database - JDK : which Java version are you using? use 'java -version' to find out - Container : which container are you using? (JBoss, Tomcat, etc.) - Configuration : is your jbpm.cfg.xml only using imports from the jbpm.jar lib itself? or did you create a custom config file? - Libraries : are you using the exact versions of the libs from inside the jbpm distribution of the version that you're using? or did you change some of the libs? === Process ================================== paste jPDL process here === API =================================== paste the relevant code snippet that you use to invoke jBPM === Stacktrace ============================== paste full stack trace here === Debug logs ============================== paste debug logs here === Problem description ========================= Keep this part short and to the point. E.g. API doesn't work as expected. or e.g. method ExecutionService.signalExecutionById throws exception.
Clever readers will have noticed that some of these questions point to probably causes :-) Especially tweaking the libs and configuration can easily lead to trouble. That's why we have spend a great deal of effort to include default installations and a simplified configuration mechanism with imports. Think twice before you start to customize configurations beyond what is indicated in this userguide. Also think twice before replacing libs with other versions.
Unzip the jBPM download (jbpm-4.X.zip
) to some location on your hard drive.
You'll see following subdirectories:
doc
: User guide, javadocs and developers guideexamples
: Example processes that are used in the user guideinstall
: Installation scripts for several environmentslib
: Third party libs and some special jBPM librariessrc
: Source filesmigration
: see developers guidejbpm.jar
: The jBPM main library archivejBPM requires a JDK (standard java) version 5 or higher.
http://java.sun.com/javase/downloads/index.jspTo execute the ant scripts, you'll need apache ant version 1.7.0 or higher:
http://ant.apache.org/bindownload.cgiThe demo setup is the simplest way to get started. This section describes the steps to complete the demo setup.
If you have previously downloaded jboss-5.0.0.GA.zip
, you can drop it
in the ${jbpm.home}/install/downloads
directory.
Otherwise the script will download it for you, but it will take some time (depending on your connection).
The same is holds for eclipse-jee-galileo-win32.zip (or eclipse-jee-galileo-linux-gtk(-x86_64).tar.gz on Linux
and eclipse-jee-galileo-macosx-carbon.tar.gz on Mac OSX)
Open a command prompt and go do directory ${jbpm.home}/install
. Then
run
ant demo.setup.jboss
or
ant demo.setup.tomcat
That will
${jbpm.home}/jboss-5.0.0.GA
directory${jbpm.home}/install/src/demo/example.identities.sql
${jbpm.home}/eclipse
After this is done, JBoss (or Tomcat, depending on which demo.setup script you chose) will be running in the background. Once eclipse has started, you can continue to follow the instructions of Section 2.11, “Graphical Process Designer (GPD)” to start coding your jBPM business processes.
Or you can start modeling processes through the Signavio web editor.
Or surf to the jBPM console. You can login as one of the following users:
Known console limitation: Currently, the timeout of the console is too tight for the reporting to initialize on slower machines. So the first time when you access the reporting the request will timeout and the console crashes. Logging out and login again works around this problem. It's being addressed as issue JBPM-2508
The jBPM download contains an install
directory
with an ant build.xml file in it. You can use that to
install jBPM into your application environment.
It's best to stick to the installations and jBPM configuration files as done with these installation scripts. Customizing jBPM configuration files directly, is possible, but not supported.
To invoke the install script, open a command line and go to the
${jbpm.home}/install
directory. With ant -p
you can
find out what each of these scripts can do. The scripts are parametrized with default values
to get going quickly. The following list gives an overview of the available scripts:
demo.setup.jboss
: installs jboss, installs jbpm into jboss,
starts jboss, creates the jBPM DB schema, deploys examples, loads example identities,
installs and starts eclipse
demo.setup.tomcat
: installs tomcat, installs jbpm into tomcat,
starts tomcat, creates the jBPM DB schema, deploys examples, loads example identities,
installs and starts eclipse
clean.cfg.dir
: Deletes the ${jbpm.home}/install/generated/cfg
folder.
create.cfg
: Creates a configuration in
${jbpm.home}/install/generated/cfg
based on the current parameters.
create.jbpm.schema
: creates the jbpm tables in the database
create.user.webapp
: Generates a basic webapp in
${jbpm.home}/install/generated/user-webapp
delete.jboss
: Deletes the JBoss installation
delete.tomcat
: Deletes the Tomcat installation
demo.teardown.jboss
: Drops the jbpm db schema and stops jboss
demo.teardown.tomcat
: Stops tomcat and then the hsqldb server (if needed)
drop.jbpm.schema
: Drops the jbpm tables from the database
get.eclipse
: Downloads eclipse if it is not available
get.jboss
: Downloads a JBoss AS which was tested against the current jBPM version if it is not available
get.tomcat
: Downloads tomcat which was tested against the current jBPM version if it is not available
hsqldb.databasemanager
: Starts the hsqldb database manager
install.eclipse
: Unzips eclipse, downloads eclipse if it is not available
install.jboss
: Downloads JBoss if its not available and then unzips it
install.jbpm.into.jboss
: Installs jBPM into JBoss
install.tomcat
: Downloads tomcat to ${tomcat.distro.dir} if its not available and then unzips tomcat
install.jbpm.into.tomcat
: Installs jBPM into tomcat
install.examples.into.tomcat
: Deploys all the example processes
install.signavio.into.jboss
: Installs signavio into jboss
install.signavio.into.tomcat
: Installs signavio into tomcat
load.example.identities
: Loads the example users and groups into the database
reinstall.jboss
: Deletes the previous jboss installation and re-installs jboss
reinstall.jboss.and.jbpm
: Deletes the previous jboss installation and re-installs jboss and installs jbpm in it
reinstall.tomcat
: Deletes the previous tomcat installation and re-installs tomcat
reinstall.tomcat.and.jbpm
: Deletes the previous tomcat installation and re-installs tomcat and installs jbpm in it
start.eclipse
: Starts eclipse
start.jboss
: Starts jboss and waits till jboss is booted, then lets jboss run in the background
start.tomcat
: Starts Tomcat and waits till it is booted, then lets Tomcat run in the background
stop.jboss
: signals jboss to stop, but doesn't wait till its finished
stop.tomcat
: Signals Tomcat to stop, but doesn't wait till its finished
upgrade.jbpm.schema
: Upgrades the jBPM tables in the database to the current version
To specify your jdbc properties that are used in the scripts above (eg.DB schema generation),
the easiest is to update the appropriate properties file in directory ${jbpm.home}/install/jdbc
.
The appropriate properties file will be loaded by the scripts that are DB related.
Also following properties are customizeable
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.1.0.GA
To customize the values for these properties, just use -D
like this
ant -Ddatabase=postgresql demo.setup.jboss
Alternatively you can specify the customized values in
${user.home}/.jbpm4/build.properties
We provide support for installations of jBPM through our automatic ant scripts. Those scripts will put the right libs and the right configuration files in the right location for you. If you want to create your own installation of jBPM in your application, see the developers guide for more information.
The target install.jbpm.into.jboss
will install jBPM into
your JBoss 5 installation. Navigate to the install directory and run ant -p
for more details. This install script will install jBPM as a JBoss-wide service so
that all applications can use the same jBPM ProcessEngine.
Specify property -Djboss.home=PathToYourJBossInstallation
to customize the path to your JBoss installation.
In JBoss, the ProcessEngine
can be obtained from JNDI
with new InitialContext().lookup("java:/ProcessEngine")
.
The same ProcessEngine can be obtained with Configuration.getProcessEngine()
The targets install.signavio.into.jboss
and
install.signavio.into.tomcat
will install the Signavio web based
process editor into JBoss or Tomcat respectively.
If you want to deploy jBPM as a part of your web application, use the
install target create.user.webapp
. That will
create a web application with jBPM in it, in the location ${jbpm.home}/install/generated/user-webapp
.
In case you deploy your app on JBoss or another appserver that has the
jta.jar classes, then you need to delete the ${jbpm.home}/install/generated/user-webapp/WEB-INF/lib/jta.jar
The install script is also capable of performing database operations such as creating the schema, if you are installing jBPM for the first time, or upgrading the database used with a previous version to the current schema. Dropping the schema is an option as well.
The prerrequisite for any database operation is to specify your
database connection parameters in ${jbpm.home}/install/jdbc
.
To create the schema, run target create.jbpm.schema
in the ${jbpm.home}/install
directory. Apart from
creating tables and constraints, the mentioned target will initialize
table JBPM4_PROPERTY
with the current engine version
(key db.version
) and the ID generator base value
(key next.dbid
).
To drop the schema, simply run target drop.jbpm.schema
.
Be aware that this operation will destroy any data present in the jBPM
tables.
To upgrade, run target upgrade.jbpm.schema
in the ${jbpm.home}/install
directory.
Upgrading is a two-fold operation. The foremost step is to add any extra tables, columns or constraints that were introduced in newer versions. Afterwards, seed data is inserted.
Between 4.0 and 4.1, table JBPM4_VARIABLE
got
a new column CLASSNAME_
used to support setting
process variables to values of custom types mapped with Hibernate.
This column is nullable and left uninitialized since the feature was
not operational in 4.0.
From 4.1 to 4.2 the upgrade procedure got more interesting.
JBPM4_PROPERTY
was introduced for storing engine-wide values.JBPM4_PROPERTY
under key db.version
to allow for precise
identification in future releases.next.dbid
in the JBPM4_PROPERTY
table.jpdl-4.0
for all existing process definitions under key langid
in table JBPM4_DEPLOYPROP
. The jPDL parser employs the
langid
property to read process documents in a
backwards-compatible manner.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.5.0.
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 install/src/gpd/jbpm-gpd-site.zip
To add the update site to eclipse:
Help
--> Install New Software...
Add...
Add Site
dialog, click Archive...
install/src/gpd/jbpm-gpd-site.zip
and click 'Open'OK
in the Add Site
dialog will bring you back to the dialog 'Install'jPDL 4 GPD Update Site
that has appearedNext...
and then Finish
Window
--> Preferences
JBoss jBPM
--> jBPM 4
--> Runtime Locations
Add...
Add Location
dialog, enter a name like e.g. jbpm-4.0
and then click Search...
Browse For Folder
dialog, select your jbpm home directory and click OK
OK
in the Add Location
dialogThis 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
After setting the jBPM User Libraries and importing the examples, all the examples can be run as JUnit tests. Right click on a test and select 'Run As' --> 'JUnit Test'.
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 business archive is a collection of files assembled in a jar formatted file. The files in a business archive can be jPDL process files, forms, classes, process image and other process resources.
Process files and process resources have to be deployed in the process repository which is stored in the database.
There is a jBPM ant task to deploy business archives
(org.jbpm.pvm.internal.ant.JbpmDeployTask
).
The JbpmDeployTask
can deploy individual
process files and business archives. They are deployed
directly to the database over a JDBC connection. So it is a
requirement that the database is up and running before you can
deploy processes.
An example of creating and deploying a business archive can be found in the ant build script (build.xml) in the examples directory of the distribution. Let's look at the relevant parts. First a path is declared that includes the jbpm.jar and all its dependencies.
<path id="jbpm.libs.incl.dependencies"> <pathelement location="${jbpm.home}/examples/target/classes" /> <fileset dir="${jbpm.home}"> <include name="jbpm.jar" /> </fileset> <fileset dir="${jbpm.home}/lib" /> </path>
The JDBC driver jar(s) for your database should also be included in the path. MySQL, PostgreSQL and HSQLDB are in the distribution. But the Oracle driver you have to download separately from the oracle site since we're not allowed to redistribute that file.
When a business archive is deployed, jBPM scans for all the files
with the .jpdl.xml
extension in the business archive.
All those files will be parsed as jPDL processes and made available to the
runtime engine. All other resources in the business archive will also
be stored as resources in that deployment and made accessible through
InputStream getResourceAsStream(long deploymentDbid, String resourceName);
in class RepositoryService
For creating a business archives, the jar
task
can be used.
<jar destfile="${jbpm.home}/examples/target/examples.bar"> <fileset dir="${jbpm.home}/examples/src"> <include name="**/*.jpdl.xml" /> ... </fileset> </jar>
Before the jbpm-deploy task can be used it need to be declared like this:
<taskdef name="jbpm-deploy" classname="org.jbpm.pvm.internal.ant.JbpmDeployTask" classpathref="jbpm.libs.incl.dependencies" />
Then the ant task can be used like this
<jbpm-deploy file="${jbpm.home}/examples/target/examples.bar" />
Table 4.1. jbpm-deploy
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
file | file | optional | A file to be deployed. Files ending with .xml will be deployed
as process files. Files ending with ar like .bar or .jar
will be deployed as business archives.
| |
cfg | file | jbpm.cfg.xml | optional | Points to the jbpm configuration file that has to be on the classpath in which
the jbpm-deploy task was defined.
|
Table 4.2. jbpm-deploy
elements:
Element | Multiplicity | Description |
---|---|---|
fileset | 0..* | files to be deployed expressed as a plain ant fileset.
Files ending with .xml will be deployed
as process files. Files ending with ar like .bar or .jar
will be deployed as business archives. |
Since version 4.2 jBPM has a process classloading mechanism as in jBPM 3.
Classes that are referenced from processes must be made available in one of 3 ways:
com.superdeluxsandwiches.Order
is referenced in the process file, it will be found when it is in the same business
archive with entry name com/superdeluxsandwiches/Order.class
Classes are cached (key is a combination of deployment and context classloader), so
it should perform better then in jBPM 3.
In case of the examples, an examples.jar file is created with
all the classes and it is put in the lib
directory of the JBoss
server configuration. Similarly for tomcat. See target install.examples.into.tomcat
and install.examples.into.jboss
. In one of the future releases we might
switch to include the classes in the business archive itself.
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 the 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
:
String deploymentId = 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 versa.
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
than 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 5.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 5.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(deploymentId);
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:
ProcessInstance processInstance = 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:
ProcessInstance processInstance = executionService.startProcessInstanceById("ICL-1");
A new process instance can optionally be given a key. This key is a user defined reference to the execution and is sometimes referred to as the 'business key'. A business 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.
ProcessInstance processInstance = 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:
ProcessInstance processInstance = executionService.startProcessInstanceByKey("ICL"); String pid = processInstance.getId();
It is a best practice to use a user defined business key. Typically in your application domain, finding such a key is not difficult. 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 - which is also more costly performance-wise.
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)); ProcessInstance processInstance = executionService.startProcessInstanceByKey("ICL", variables);
When using a state
activity, the execution (or process instance)
will halt when it arrives in a state, waiting for a signal (aka external trigger).
The method signalExecution
and alike can be used for that. Executions are
referenced by an execution id (String).
In some cases, the execution that arrives in a state will be the process instance itself. But that is not always the case. In case of timers or concurrency, a process is the root execution of a tree of executions. So you have to make sure that you signal the right path of execution.
The preferred way to capture the right execution is by associating an event listener to the state activity like this:
<state name="wait"> <on event="start"> <event-listener class="org.jbpm.examples.StartExternalWork" /> </on> ... </state>
In event listener StartExternalWork
you can kick off what needs to
be done externally. In that event listener you can also obtain the exact execution id
with execution.getId()
. It's that executionId that you'll need to provide
with the signal later on when the external work is done:
executionService.signalExecutionById(executionId);
There is an alternatively (less preferrable) way to obtain the executionId
when the execution arrives in the state
activity.
It's only possible to obtain the execution id this way if you know after which jBPM
API call the execution will have entered the state
activity:
// assume that we know that after the next call
// the process instance will arrive in state
external work
ProcessInstance processInstance =
executionService.startProcessInstanceById(processDefinitionId);
// or ProcessInstance processInstance =
// executionService.signalProcessInstanceById(executionId);
Execution execution = processInstance.findActiveExecutionIn("external work");
String executionId = execution.getId();
Do note that the above solution couples the application logic (too) closely by using knowledge about the actual process structure.
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.findPersonalTasks("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.
// read task variables Set<String> variableNames = taskService.getVariableNames(taskId); variables = taskService.getVariables(taskId, variableNames);
// write task variables variables = new HashMap<String, Object>(); variables.put("category", "small"); variables.put("lires", 923874893); taskService.setVariables(taskId, variables);
The taskService is also used to complete tasks
taskService.completeTask(taskId); taskService.completeTask(taskId, variables); taskService.completeTask(taskId, outcome); taskService.completeTask(taskId, outcome, variables);
The API allows to provide a map of variables that will be added as process variables before the task is completed. It is also possible to provide an 'outcome', that will be used to determine which outgoing transition will be chosen. The logic is as follows:
If a task has one outgoing transition without a name then:
If a task has one outgoing transition with a name then:
If a task has multiple outgoing transitions. One transition has no a name and the other transition have a name:
If a task has multiple outgoing transitions and all of them are uniquely named, then:
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'.
More on tasks in Section 6.2.6, “task
”
During runtime execution of process instances, events are generated. And from
those events, history information on both running and completed process executions are
collected in the history tables. The HistoryService
provides
access to that information.
Querying for all process instances for a specific process definition can be done like this:
List<HistoryProcessInstance> historyProcessInstances = historyService .createHistoryProcessInstanceQuery() .processDefinitionId("ICL-1") .orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME) .list();
Also individual activity executions are stored in the history information as
HistoryActivityInstance
s.
List<HistoryActivityInstance> histActInsts = historyService .createHistoryActivityInstanceQuery() .processDefinitionId("ICL-1") .activityName("a") .list();
Convenience methods avgDurationPerActivity
and
choiceDistribution
are also available. See javadocs for more
information on those methods.
Sometimes there is a need to get complete list of nodes executed for a given process instance. Following query can be used to get list of all nodes executed:
List<HistoryActivityInstance> histActInsts = historyService .createHistoryActivityInstanceQuery() .processInstanceId("ICL.12345") .list();
Above query is a bit different then querying by execution id. Sometimes execution id is different than process instance id, for instance when an activity has a timer then execution id will get additional suffix, which makes that node excluded from a result list while querying by execution id.
The management service is mostly used to manage the jobs. See javadocs for more information. This functionality is also exposed through the jBPM web console.
Starting from jBPM 4.0, a new API has been introuced with a query system that covers most of the queries you can think of. Developers who need to write company-specific queries can of course still rely on Hibernate. But for most use cases, the query API will be more then suffice. Queries can be written in a unified way on all major jBPM concepts: Process Instances, Tasks, Deployments, Historical processes, etc.
For example:
List<ProcessInstance> results = executionService.createProcessInstanceQuery() .processDefinitionId("my_process_definition") .notSuspended() .page(0, 50) .list();
This example returns all the process instances of the given process definition which are not suspended. The result is also paged, and the first page of 50 results is given.
Querying tasks is done in completely the same way:
List<Task> myTasks = taskService.createTaskQuery() .processInstanceId(piId) .assignee("John") .page(100, 120) .orderDesc(TaskQuery.PROPERTY_DUEDATE) .list();
This query will give all the tasks for a given process instance assigned to John, paged of course, in a descending order based on the duedate.
Every service has operations of creating such unified queries (eg. querying
jobs through the ManagementService
, querying completed
process instances through the HistoryService
. Do check
the Javadoc of the services to learn everything about the query API.
This chapter will explain the jPDL file format for describing process definitions. jPDL is the prominent process language of jBPM. The goal of jPDL is to be as concise and developer-friendly as possible, while offering every feature you'd expect from a BPM process language.
The jPDL schema file contains more attributes and elements then this documentation. This part of the documentation explains the stable and supported part of jPDL. Experimental/not supported jPDL features can be found in the developers guide.
An example jPDL process file looks like this:
<?xml version="1.0" encoding="UTF-8"?> <process name="Purchase order" xmlns="http://jbpm.org/4.4/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 6.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 6.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 6.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.4/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:
ProcessInstance processInstance = executionService.startProcessInstanceByKey("StateSequence");
the created process instance will be positioned in
state a
. Providing an external trigger can
be done with the signalExecution
methods.
Execution executionInA = processInstance.findActiveExecutionIn("a"); assertNotNull(executionInA); processInstance = executionService.signalExecutionById(executionInA.getId()); Execution executionInB = processInstance.findActiveExecutionIn("b"); assertNotNull(executionInB); processInstance = executionService.signalExecutionById(executionInB.getId()); Execution executionInC = processInstance.findActiveExecutionIn("c"); assertNotNull(executionInC);
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.4/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:
ProcessInstance processInstance = executionService .startProcessInstanceByKey("StateChoice");
Now, the execution has 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:
String executionId = processInstance .findActiveExecutionIn("wait for response") .getId(); processInstance = executionService.signalExecutionById(executionId, "accept"); assertTrue(processInstance.isActive("submit document"));
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 6.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>
After starting a process instance with good content
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("content", "good"); ProcessInstance processInstance = executionService.startProcessInstanceByKey("DecisionConditions", variables);
The activity submit document
will be active
assertTrue(processInstance.isActive("submit document"));
See the example unit test for more scenarios.
A decision expression evaluates to a String representing the name of an outgoing transition.
Table 6.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="DecisionExpression" xmlns="http://jbpm.org/4.4/jpdl"> <start > <transition to="evaluate document"/> </start> <decision name="evaluate document" expr="#{content}" > <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>
When you start an new process instance with good content like this
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("content", "good"); ProcessInstance processInstance = executionService.startProcessInstanceByKey("DecisionExpression", variables);
then the new execution will go to activity submit document
.
See the example unit test for the other scenarios.
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 decide(OpenExecution execution); }
The handler is specified as a sub element of the decision.
The configuration attributes and content of a decision handler
element can be found in Section 6.7, “User code”.
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 decide(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
.
Concurrent paths of executions can be modeled with the fork
and
join
activities. The next table describes the join
attributes; fork
has no specific attributes.
Table 6.7. join
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
multiplicity | integer or expression | nbr of incoming transitions | optional | The number of executions that should arrive in this join before the join activates and push an execution out the single outgoing transition of the join. |
lockmode | {none, read, upgrade, upgrade_nowait, write} | upgrade | optional | the hibernate lock mode applied on the parent execution to prevent that 2 concurrent transactions see each other as not yet arrived at the join, causing a process deadlock. |
The fork
activity allows a single path of execution to be
split into two or more branches which can execute activities concurrently.
<process name="ConcurrencyGraphBased" xmlns="http://jbpm.org/4.4/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.4/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 6.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.
ProcessInstance processInstance = executionService.startProcessInstanceByKey("EndMultiple"); String pid = processInstance.getId(); processInstance = executionService.signalExecutionById(pid, "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 6.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.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" 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.
Similarly, 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 6.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")); ProcessInstance processInstance = executionService .startProcessInstanceByKey("TaskAssignee", variables);
Then the task list for johndoe
can be obtained like this.
List<Task> taskList = taskService.findPersonalTasks("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 6.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.getPersonalTasks("johndoe"); taskService.getPersonalTasks("joesmoe");
But the task will show up in the group 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 group tasks for users johndoe and joesmoe
taskService.findGroupTasks("johndoe"); taskService.findGroupTasks("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 group 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 group 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 6.7, “User code”
Let's look at the task assignment example process.
<process name="TaskAssignmentHandler" xmlns="http://jbpm.org/4.4/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 6.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 6.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.4/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 read 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.findPersonalTasks("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);
It is possible to provide assignees with notifications when a task
is added to their list, as well as reminders at specific intervals.
Every email message is produced from a template. Templates may be specified
inline or in the process-engine-context
section of the
configuration file.
Table 6.14. task
elements
Element | Multiplicity | Description |
---|---|---|
notification | 0..1 | Sends a notification message when a task is assigned. If no template is referenced or supplied inline, mail support falls back on the template named task-notification. |
reminder | 0..1 | Sends a reminder message at specific intervals. If no template is referenced or supplied inline, mail support falls back on the template named task-reminder. |
Table 6.15. notification
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
continue | {sync | async | exclusive} | sync | optional | Specifies if an asynchronous continuation should be introduced right before sending this notification email. |
Table 6.16. reminder
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
duedate | duration (plain string or containing expression) | required | Delay before a reminder email should be send. | |
repeat | duration (plain string or containing expression) | optional | Delay after a subsequent reminder email should be send | |
continue | {sync | async | exclusive} | sync | optional | Specifies if an asynchronous continuation should be introduced right before sending this notification email. |
Here is a basic example that accepts the default templates.
<task name="review" assignee="#{order.owner}" <notification/> <reminder duedate="2 days" repeat="1 day"/> </task>
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 6.17. sub-process
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
sub-process-id | string or expression | 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 id can be specified as simple text or EL expression. | |
sub-process-key | string or expression | 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. Sub process key can be specified as simple text or EL expression. | |
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 6.18. sub-process
elements:
Element | Multiplicity | Description |
---|---|---|
parameter-in | 0..* | Declares a variable that is passed to the sub process instance when it is created. |
parameter-out | 0..* | Declares a variable that will be set in the super process execution when the sub process ends. |
Table 6.19. parameter-in
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
subvar | string | required | The name of the sub process variable in which the value is set. | |
var | string | exactly one of {'var', 'expr'} is required to specify the value | The name of the variable in the super process execution context. | |
expr | string | exactly one of {'var', 'expr'} is required to specify the value | An expression that will be resolved in the super process execution context. The resulting value will be set in the sub process variable. | |
lang | string | juel | optional | The scripting language in which the expression should be resolved. |
Table 6.20. parameter-out
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
var | string | required | The name of the variable in the super process execution context in which the value will be set. | |
subvar | string | exactly one of {'subvar', 'expr'} is required to specify the value | The name of the sub process variable from which the value will be taken. | |
expr | string | exactly one of {'subvar', 'expr'} is required to specify the value | An expression that will be resolved in the sub process execution context. The resulting value will be set in the super process variable. | |
lang | string | juel | optional | The scripting language in which the expression should be resolved. |
Table 6.21. 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.4/jpdl">
<start>
<transition to="review" />
</start>
<sub-process name="review"
sub-process-key="SubProcessReview">
<parameter-in var="document" subvar="document" />
<parameter-out var="reviewResult" subvar="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.4/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.findPersonalTasks("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.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("result", "accept"); taskService.setVariables(task.getDbid(), variables);
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.4/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.findPersonalTasks("johndoe"); Task task = taskList.get(0);
Then the result
variable is set and
the task is completed.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("result", "ok"); taskService.setVariables(task.getId(), variables); 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.4/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.findPersonalTasks("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.
Invokes user code that implements custom behaviour of an activity.
A custom activity refers to user code. See Section 6.7, “User code” for more details on the specific attributes and elements. Let's look at the example:
<process name="Custom" xmlns="http://jbpm.org/4.4/jpdl"> <start > <transition to="print dots" /> </start> <custom name="print dots" class="org.jbpm.examples.custom.PrintDots"> <transition to="end" /> </custom> <end name="end" /> </process>
The custom activity behaviour class PrintDots
shows that it's possible to control the flow when implementing
custom activity behaviours. In this case the PrintDots
acitivity implementation will after printing dots wait in the activity until
a signal is given.
public class PrintDots implements ExternalActivityBehaviour { private static final long serialVersionUID = 1L; public void execute(ActivityExecution execution) { String executionId = execution.getId(); String dots = ...; System.out.println(dots); execution.waitForSignal(); } public void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters) { execution.take(signalName); } }
The Java task. A process execution will execute the method of the class that is configured in this activity.
Table 6.22. java
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | either 'class' or 'expr' has to be specified | The fully qualified classname. See Section 6.7.2, “User code classloading” for classloading information. The user code object will be lazy initialized and cached as part of the process definition. | |
expr | expression | either 'expr' or 'class' has to be specified | An expression that returns the target object on which the method should be invoked. | |
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 6.23. 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.4/jpdl"> <start > <transition to="greet" /> </start> <java name="greet" class="org.jbpm.examples.java.JohnDoe" method="hello" var="answer" > <field name="state"><string value="fine"/></field> <arg><string value="Hi, how are you?"/></arg> <transition to="shake hand" /> </java> <java name="shake hand" expr="#{hand}" method="shake" var="hand" > <arg><object expr="#{joesmoe.handshakes.force}"/></arg> <arg><object expr="#{joesmoe.handshakes.duration}"/></arg> <transition to="wait" /> </java> <state name="wait" /> </process>
Classes involved:
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; } }
public class JoeSmoe implements Serializable { static Map<String, Integer> handshakes = new HashMap<String, Integer>(); { handshakes.put("force", 5); handshakes.put("duration", 12); } public Map<String, Integer> getHandshakes() { return handshakes; } }
public class Hand implements Serializable { private boolean isShaken; public Hand shake(Integer force, Integer duration) { if (force>3 && duration>7) { isShaken = true; } return this; } public boolean isShaken() { return isShaken; } }
The first java activity greet
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.
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.
.
The second java activity is named shake hand
. It will resolve
expression #{hand}
and capture the resulting object as the target object. On that object, the method
shake
will be invoked. The two arguments will be calculated by resolving
the respective expressions #{joesmoe.handshakes.force}
and
#{joesmoe.handshakes.duration}
. The resulting object is a mofied
version of the hand and var="hand"
will cause the modified
hand to overwrite the old hand
variable value.
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 6.24. script
expression attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | text | required | the expression text to evaluate. | |
lang | scripting language name as defined in Chapter 8, Scripting | the default expression language as defined in Chapter 8, 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.4/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 6.25. script
text attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
lang | scripting language name as defined in Chapter 8, Scripting | the default scripting language as defined in Chapter 8, 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.4/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.
With the hql
activity, a HQL query can be performed
on the database and the result is stored in a process variable.
Table 6.27. 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 6.28. 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.4/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.
Through the mail
activity, process authors are
able to specify the content of an email message to be sent to multiple
recipients at once. Every email message is produced from a template.
Templates may be specified inline or in the process-engine-context
section of the configuration file.
Table 6.29. mail
attributes
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
template | string | no | Reference to a mail-template element in the
configuration file. If absent, the template must be specified
inline using the child elements. |
Table 6.30. mail
elements
Element | Multiplicity | Description |
---|---|---|
from | 0..1 | list of sender(s) |
to | 1 | list of primary recipients |
cc | 0..1 | list of carbon copy recipients |
bcc | 0..1 | list of blind carbon copy recipients |
subject | 1 | text content of this element becomes the message subject |
text | 0..1 | text content of this element becomes the message text content |
html | 0..1 | text content of this element becomes the message HTML content |
attachments | 0..1 | each attachment is configured in a separate subelement |
Table 6.31. attachment
attributes
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | string | no, unless expression is present | File name associated with this attachment.
If absent, data sources that encapsulate files such as resource ,
file and url provide a reasonable
fallback value. | |
description | string | no | Descriptive information associated with this attachment. | |
expression | string | one of expression , file ,
url or resource must be present | Expression that evaluates to a representation of the attachment data in the form of a Java object. Useful to extract content from process variables. | |
file | string | Path to the attachment data in the file system. The denoted file must exist. | ||
url | string | Location of the attachment data in the worldwide web. The pointed resource must exist. | ||
resource | string | Name of the resource containing the attachment data in the class path. The denoted resource must exist. | ||
mime-type | string | no, unless expression is present | MIME type of the object returned by the expression. |
Example usage:
<process name="InlineMail" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="send birthday reminder note" /> </start> <mail name="send birthday reminder note"> <to addresses="johnDoe@some-company.com" /> <subject>Reminder: ${person} celebrates his birthday!</subject> <text>Do not forget: ${date} is the birthday of ${person} </text> <attachments> <attachment resource="org/example/birthday_card.png"/> <attachment name="picture.jpg" expression="${picture}" mime-type="image/jpeg"/> </attachments> <transition to="end" /> </mail> <state name="end"/> </process>
Unless specified otherwise above, all activities also include this content model:
Table 6.32. Common activity attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | required | name of the activity |
Table 6.33. 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, which makes them very interesting for implementing technical details. 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 6.34. on
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
event | {start | end} | required | name name of the event |
Table 6.35. on
elements:
Element | Multiplicity | Description |
---|---|---|
event-listener | 0..* | An event listener implementation object. |
any automatic activity | 0..* |
Table 6.36. event listener attributes:
event-listener
is user code so it can be configured
like described in Section 6.7, “User code”.
Any automatic activities (including event-listener) that are placed on events can specify following additional attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
propagation | {enabled | disabled | true | false | on | off} | disabled | optional | indicates if the event listener should also be invoked for propagating events. |
continue | {sync | async | exclusive} | sync | optional | indicates if the execution should be continued asynchronously right before the event listener is executed. @see also Section 6.6, “Asynchronous continuations” |
Let's look at an example process with event listeners:
<process name="EventListener" xmlns="http://jbpm.org/4.4/jpdl"> <on event="start"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="start on process definition"/></field> </event-listener> </on> <start> <transition to="wait"/> </start> <state name="wait"> <on event="start"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="start on activity wait"/></field> </event-listener> </on> <on event="end"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="end on activity wait"/></field> </event-listener> </on> <transition to="park"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="take transition"/></field> </event-listener> </transition> </state> <state name="park"/> </process>
LogListener
will maintain a list of logs as a process variable:
public class LogListener implements EventListener {
// value gets injected from process definition
String msg;
public void notify(EventListenerExecution execution) {
List<String> logs = (List<String>) execution.getVariable("logs");
if (logs==null) {
logs = new ArrayList<String>();
execution.setVariable("logs", logs);
}
logs.add(msg);
execution.setVariable("logs", logs);
}
}
Next, we start a new process instance.
ProcessInstance processInstance = 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.
Execution execution = processInstance.findActiveExecutionIn("wait"); executionService.signalExecutionById(execution.getId());
The list of log messages will now look like this:
[start on process definition, start on activity wait, end on activity wait, take transition]
Events are propagated from activities and transitions to outer activities and eventually to the process definition.
By default, event listeners are only invoked
for events that are fired on the elements on which the event listeners are subscribed.
But by specifying propagation="enabled"
, the event
listener will also be invoked for all events that are fired on contained elements.
Each invocation of ExecutionService.startProcessInstanceById(...)
or ExecutionService.signalProcessInstanceById(...)
will cause
the process to be executed in the thread it was called from (=client). In other words, those
methods will only return after the process execution has arrived in a wait state.
This default behaviour has a couple of advantages: user application transactions can be easily propagated to jBPM to that jBPM's DB updates are done in the user's transaction context. Secondly, it's possible for a client to get an exception in case something goes wrong during execution of the process. Usually, the automatic work that has to be done as part of the process inbetween two wait states is relatively small. Even if multiple automatic activities are executed inbetween 2 wait states. So in most situations, it's good to do all that work in a single transaction. This explains that the default behaviour of jPDL is to perform all work of the process synchronously in the thread of client.
For those cases where you don't want the call to jBPM to be blocking until all the automatic work is done, jPDL allows for very fine grained control over transaction boundaries. On various places in the process, asynchronous continuations can be introduced. Asynchronous continuations cause the transaction to commit and the jBPM method invocation will return. jBPM will then start a new transaction in a new thread and continue the rest of the automatic process work asynchronously. jBPM uses asynchronous messaging internally to accomplish this.
Upon an asynchronous continuation, an asynchronous message will be sent as
part of the currently ongoing transaction. And then the originally invoked method
like e.g. startProcessInstanceById(...)
or signalProcessInstanceById(...)
will return. When the
asynchronous message is committed and then processed, it will start a new transaction
and resume execution where it left off.
Table 6.37. Attribute of any activity, transition
or on
:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
continue | {sync | async | exclusive} | sync | optional | indicates if an asynchronous continuation should be performed before the element is executed. |
Let's look at a couple of examples.
<process name="AsyncActivity" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="generate pdf"/> </start> <java name="generate pdf" continue="async" class="org.jbpm.examples.async.activity.Application" method="generatePdf" > <transition to="calculate primes"/> </java> <java name="calculate primes" continue="async" class="org.jbpm.examples.async.activity.Application" method="calculatePrimes"> <transition to="end"/> </java> <end name="end"/> </process>
public class Application { public void generatePdf() { // assume long automatic calculations here } public void calculatePrimes() { // assume long automatic calculations here } }
ProcessInstance processInstance = executionService.startProcessInstanceByKey("AsyncActivity"); String processInstanceId = processInstance.getId();
Without the asynchronous continuations, this would be an all automatic
process and the process would execute all the way up to the end
in method startProcessInstanceByKey
But with continue="async"
the execution only
goes untill it is about to execute activity generate pdf
. Then
an asynchronous continuation message is send and the startProcessInstanceByKey
method returns.
In a normal configuration, the job executor will automatically pick up the message and execute it. But for testing scenarios and for these examples we want to control when messages are executed so the job executor is not configured. Therefore we have to execute the jobs manually like this:
Job job = managementService.createJobQuery() .processInstanceId(processInstanceId) .uniqueResult(); managementService.executeJob(job.getDbid());
That will bring the process until it's about to execute activity
calculate primes
and again an asynchronous message is
send.
Then the message can be looked up again and when that message is executed, that transaction will run the execution till the end.
<process name="AsyncFork" xmlns="http://jbpm.org/4.4/jpdl">
<start >
<transition to="fork"/>
</start>
<fork >
<on event="end" continue="exclusive" />
<transition />
<transition />
</fork>
<java class="org.jbpm.examples.async.fork.Application" >
<transition />
</java>
<java class="org.jbpm.examples.async.fork.Application" >
<transition />
</java>
<join >
<transition to="end"/>
</join>
<end />
</process>
public class Application { public void shipGoods() { // assume automatic calculations here } public void sendBill() { // assume automatic calculations here } }
By placing the asynchronous continuation on the end
event of the fork (<on event="end" continue="exclusive" />
),
each forked execution that takes a transition out of the
fork will be continued asynchronously.
Value exclusive
was selected to serialize the executions of
the 2 asynchonous continuation jobs resulting from the fork. The respective transactions
that will execute activities ship goods
and send bill
will both arrive at the join. At the join, both
transactions will synchronize on the same execution (read: update the same execution
row in the DB), resulting in a potential optimistic locking failure.
ProcessInstance processInstance = executionService.startProcessInstanceByKey("AsyncFork"); String processInstanceId = processInstance.getId(); List<Job> jobs = managementService.createJobQuery() .processInstanceId(processInstanceId) .list(); assertEquals(2, jobs.size()); Job job = jobs.get(0); // here we simulate execution of the job, // which is normally done by the job executor managementService.executeJob(job.getDbid()); job = jobs.get(1); // here we simulate execution of the job, // which is normally done by the job executor managementService.executeJob(job.getDbid()); Date endTime = historyService .createHistoryProcessInstanceQuery() .processInstanceId(processInstance.getId()) .uniqueResult() .getEndTime(); assertNotNull(endTime);
Various elements in the jPDL process language refer to a an object on which an interface method will be invoked. This section describes the common attributes and elements for the instantiation and configuration of such user code objects.
custom
event-listener
assignment-handler
in taskhandler
in decisioncondition
in transitionTable 6.38. attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | one of {class|expr} is required | The fully qualified classname. Instantiation is done only once and the user object is cached as part of the process definition. | |
expr | expression | one of {class|expr} is required | Expression for which the resulting value will be taken as the target object. Expressions will be evaluated for every usage. In other words, the resulting value of the evaluation will not be cached. |
Table 6.39. user code configuration 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 6.40. field
and property
attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | string | required | the name of the field or property. |
Table 6.41. field and property contained element:
field
and property
elements
have exactly one child element that represents the value that will be
injected.
Element | Multiplicity | Description |
---|---|---|
string | 0..1 | a java.lang.String |
int | 0..1 | a java.lang.Integer |
long | 0..1 | a java.lang.Long |
float | 0..1 | a java.lang.Float |
double | 0..1 | a java.lang.Double |
true | 0..1 | Boolean.TRUE |
false | 0..1 | Boolean.FALSE |
object | 0..1 | a object that will be instantiated with reflection |
Table 6.42. Attribute for basic type string
, int
, long
, float
and double
:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
value | text | required | text value that will be parsed to the respective type |
Process definitions are cached. By default, all user code objects are cached as part of those process definitions. For all objects that are referenced by a class name, will be instantiated during parsing time. Which implies that the objects aren't allowed to store non-stateless data (ie which can change). This is typically OK since those objects are in practice almost always immutable. If you do need to use 'dynamic' data in your user code, you can always fall back to process variables (or Environment.get(xxx) calls).
Objects that are referenced by an expression are calculated dynamically.
The devguide also explains an unsupported attribute to prevent that user objects are cached.
Process variables can be accessed from outside the process with methods from the ExecutionService
:
ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables);
ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables, String processInstanceKey);
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, ?> variables);
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, ?> variables, String processInstanceKey);
void setVariable(String executionId, String name, Object value);
void setVariables(String executionId, Map<String, ?> variables);
Object getVariable(String executionId, String variableName);
Set<String> getVariableNames(String executionId);
Map<String, Object> getVariables(String executionId, Set<String> variableNames);
And from inside the process with methods from Execution interfaces passed to user code like
ActivityExecution
and EventListenerExecution
:
Object getVariable(String key);
void setVariables(Map<String, ?> variables);
boolean hasVariable(String key);
boolean removeVariable(String key);
void removeVariables();
boolean hasVariables();
Set<String> getVariableKeys();
Map<String, Object> getVariables();
void createVariable(String key, Object value);
void createVariable(String key, Object value, String typeName);
jBPM doesn't have a mechanism for detecting changes automatically to variable values. So if you get e.g. a serializable collection from the process variables and add an element, then you need to set the changed variable value explicitely for the changes to be saved to the DB.
By default variables are created in the top level process instance scope. This means they are visible and accessible in all the paths of execution of the whole process instance. Process variables are created dynamically. Meaning that a variable is created the first time it is set through one of these methods.
Each execution is a variable scope. Variables declared in a nested execution level will 'see'
their own variables and variables declared in parent executions according to the normal scoping rules.
With the createVariable
methods in the execution interfaces ActivityExecution
and EventListenerExecution
, execution-local variables can be created.
In one of the future releases, we might add variable declaration in the jPDL process language.
jBPM supports following Java types as process variables:
For persistence of these variable, the type of the variable is checked in the order of this list. The first match will determine how the variable is stored.
(Since jBPM 4.3)
In custom
s, event-handler
s and other
user code, you can retrieve process variables. In case a process variable
is stored as a serialized object, you can just make updates to your deserialized
objects without the need for an explicit save. jBPM will manage deserialized process
variables and update them automatically if you change. For example (@see examples package
org.jbpm.examples.serializedobject), look at this piece of user code inside a custom
's activity behaviour:
public class UpdateSerializedVariables implements ActivityBehaviour { public void execute(ActivityExecution execution) { Set<String> messages = (Set<String>) execution.getVariable("messages"); messages.clear(); messages.add("i"); messages.add("was"); messages.add("updated"); } }
When the transaction commits in which this usercode was called, the updated messages set will be updated in the database automatically.
When reading process variables that are stored in serialized format from the DB jBPM will monitor that deserialized object. Right before the commit of the transaction, jBPM will serialize and update the variable automatically if that is necessary. jBPM will ignore updates to the deserialized object if another object was set as the value in that scope (which even can be of another type). jBPM will also skip updating of the variable if the deserialized object has not been changed. The check to see if the object has changed is based on comparing the byte arrays from serializing the object again and comparing that with the byte array that was originally loaded from the db.
(Since jBPM 4.4)
Variables can be declared directly in process definition (JPDL). These variables will be created at process instance startup. There can be more than one variable definition.
There are several possible ways for declaring variable:
String
variable initialized with static text
<variable name="declaredVar" type="string" init-expr="testing declared variable"/>
custom
variable initialized with EL
<variable name="declaredVar" type="long" init-expr="#{anotherVar}"/>
custom
variable initialized with serializable class
<variable name="declaredVar" type="serializable" > <object class="org.jbpm.examples.variable.declared.HistoryVariable" /> </variable>
As shown above variable values can be assigned in two ways: using attribute init-expr
or
by nesting init descriptor (element object
) within variable tags.
Note: Only one of value assignment can be used for a variable declaration.
Table 7.1. Attribute for variable
element:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | text | required | name of the variable | |
type | text | required | type of the variable, must refer to types defined in jbpm.variable.types.xml | |
init-expr | text (EL expression) | optional | value for the variable, this attribute or nested element must be given | |
init-expr-type | text | UEL | optional | defines language for expression evaluation |
history | boolean | false | optional | indicates wheater variable should be stored in history or not - default false, for more information about history see Section 7.5, “Variables history” |
Table 7.2. Nested element for variable
:
Element | Multiplicity | Description | ||
---|---|---|---|---|
object | 1 | Value for the variable as custom object, either this element or init-expr attribute must be specified |
(Since jBPM 4.4)
Variables can be marked to be persisted as history records. This means that once process instance is ended and its runtime information is removed, history details are preserved.
History can be enabled for variable in two ways:
ExecutionService
:
void createVariable(String executionId, String name, Object value, boolean historyEnabled);
void createVariables(String executionId, Map<String, ?> variables, boolean historyEnabled);
<variable name="declaredVar" type="string" init-expr="testing declared variable" history="true"/>
Currently all variables are persisted in history as String
values.
Variable (regardless of its type) will be converted to a string value using toString()
method. In case of custom objects they should override toString()
method to provide string representation
of the variable that will be available as history record. This will provide an easy way for enabling convienient search
capabilities based on variable values.
Access to history variables is given via HistoryService
methods:
Object getVariable(String processInstnceId, String name);
Map<String, Object> getVariables(String processInstnceId, Set<String> variableNames);
Set<String> getVariableNames(String processInstnceId);
Only jUEL is configured as the scripting language. jUEL is an implementation of the Unified Expression Language. For detailed description of how to use UEL, please refer to the JEE 5 Tutorial, section Unified Expression Language
To configure other scripting languages then jUEL, please refer to the developer's guide (non supported).
To customize the business calendar configuration, remove the default business calendar configuration import and replace it with the custom values.
<jbpm-configuration> <import resource="jbpm.businesscalendar.cfg.xml" /> ... <process-engine-context> <business-calendar> <monday hours="9:00-18:00"/> <tuesday hours="9:00-18:00"/> <wednesday hours="9:00-18:00"/> <thursday hours="9:00-18:00"/> <friday hours="9:00-18:00"/> <holiday period="01/02/2009 - 31/10/2009"/> </business-calendar> </process-engine-context> </jbpm-configuration>
By default the server host and port of the console
web app are respectively localhost
and 8080
. It is not hard to imagine
situations where it is needed to change those defaults.
Hence they are made configurable. To customize,
change the values of the default configuration
(e.g. in the file "jbpm.console.cfg.xml")
and replace them with the values you want.
<jbpm-configuration> <process-engine-context> <string name="jbpm.console.server.host" value="myNewHost"> <string name="jbpm.console.server.port" value="9191"> </process-engine-context> </jbpm-configuration>
The default configuration looks for a jbpm.mail.properties
classpath resource containing JavaMail properties.
To send mail through a server other than local host, set the
mail.smtp.host
property in the mail properties file.
mail.smtp.host=localhost mail.smtp.port=25 mail.from=noreply@jbpm.org
If the SMTP server requires authentication, the application can supply a custom authenticator in the configuration file.
<mail-session> <mail-server> <session-properties resource="jbpm.mail.properties" /> <authenticator class='BasicAuthenticator'> <field name='userName'><string value='aguizar'/></field> <field name='password'><string value='wontsay'/></field> </authenticator> </mail-server> </mail-session>
In Java EE environments it is often the case that a mail session is already configured and bound to JNDI. To employ such a session, specify its JNDI name in the configuration file.
<mail-session> <mail-server session-jndi='java:comp/env/mail/smtp' /> </mail-session>
If present, the session JNDI name has precedence over the session properties and the authenticator. The combined absence of session-properties and session-jndi constitutes an error.
Refer to the Developer Guide for advanced, yet unsupported, email settings.