JBoss.orgCommunity Documentation

jBPM User Guide


1. Introduction
1.1. License and EULA
1.2. Downloads
1.3. Sources
1.4. What is it
1.5. Contents of this userguide
1.6. Migration from jBPM 3
1.7. Reporting problems
2. Installation
2.1. The distribution
2.2. Required software
2.3. Getting started quickly
2.4. Install scripts
2.5. Library dependencies and configuration files
2.6. JBoss
2.7. Tomcat
2.8. Signavio web based process editor
2.9. User webapp
2.10. Database
2.11. Graphical Process Designer (GPD)
2.11.1. Get Eclipse
2.11.2. Install the GPD plugin into eclipse
2.11.3. Configuring the jBPM runtime
2.11.4. Define the jBPM User Library
2.11.5. Adding jPDL 4 schema to the catalog
2.11.6. Importing the Examples
2.11.7. Adding deployment with ant
3. Graphical Process Designer (GPD)
3.1. Creating a new process file
3.2. Editing the process source
4. Deploying business archives
4.1. Deploying process files and process resources
4.2. Deploying classes
5. Services
5.1. Process definition, process instance and executions
5.2. ProcessEngine
5.3. Deploying a process
5.4. Undeploying deployments
5.5. Deleting a deployment
5.6. Starting a new process instance
5.6.1. In latest
5.6.2. Specific process version
5.6.3. With a key
5.6.4. With variables
5.7. Signalling a waiting execution
5.8. TaskService
5.9. HistoryService
5.10. ManagementService
6. jPDL
6.1. process
6.2. Control flow activities
6.2.1. start
6.2.2. state
6.2.3. decision
6.2.4. concurrency
6.2.5. end
6.2.6. task
6.2.7. sub-process
6.2.8. custom
6.3. Automatic activities
6.3.1. java
6.3.2. script
6.3.3. hql
6.3.4. sql
6.3.5. mail
6.4. Common activity contents
6.5. Events
6.5.1. Event listener example
6.5.2. Event propagation
6.6. Asynchronous continuations
6.6.1. Async activity
6.6.2. Async fork
6.7. User code
7. Variables
7.1. Variable scoping
7.2. Variable types
8. Scripting
9. Configuration
9.1. Business calendar
9.2. Email

This documentation is best viewed in firefox. There are some known issues with internet explorer.

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 Java version are you using? use 'java -version' to find out
- Configuration : is your jbpm.cfg.xml only importing files 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 ==================================
past jPDL process here

=== API ===================================
past the relevant code snippet that you use to invoke jBPM

=== Stacktrace ==============================
past full stack trace here

=== Debug logs ==============================
past 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.

The 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, create a downloads directory in your ${jbpm.home}/install/downloads directory and place the zip file in there. Otherwise the script will download it for you. The same is true 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

After this is done JBoss 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)”

And you can surf to the jBPM console You can login as one of the following users:


Known limitation: Currently, the timeout of the console is too tight for the reporting to initialize. 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

Signavio is a web based process editor that can be used to author jPDL files. It is based on the Oryx open source project.

The targets install.signavio.into.jboss and install.signavio.into.tomcat will install the Signavio web based process editor into JBoss or Tomcat respectively.

This target needs to be called with the signavio.repo.path property specified like this:

ant -Dsignavio.repo.path=c:/software/jbpm-4.1/signavio-repo install.signavio.into.jboss

On Windows, make sure that you use forward slashes to specify this path.

Before copying it to the deploy folder, the script will replace the repository path property in the web.xml of the jbpmeditor.war (Signavio) web application.

The signavio webapp is not yet part of the default installation scripts as it depends on JDK 6.

After the application started, surf to http://localhost:8080/jbpmeditor/p/explorer

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.

This 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.


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, 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" />


Interacting with jBPM occurs through services. The service interfaces can be obtained from the ProcessEngine which is build from a Configuration.

A ProcessEngine is thread safe and can be stored in a static member field or even better in JNDI or some other central location. One ProcessEngine object can be used by all requests and threads in an application. Here's how you can obtain a ProcessEngine

The code snippets in this section and the next section about process deployments are taken from example org.jbpm.examples.services.ServicesTest

ProcessEngine processEngine = new Configuration()
      .buildProcessEngine();

The previous code snippet shows how to build a ProcessEngine from the default configuration file jbpm.cfg.xml which is expected in the root of the classpath. If you want to specify another resource location, use the setResource method like this:

ProcessEngine processEngine = new Configuration()
      .setResource("my-own-configuration-file.xml")
      .buildProcessEngine();

There are other setXxxx methods that allow to specify the configuration content as an InputStream, an xmlString, InputSource, URL or File.

From a ProcessEngine the following services can be obtained:

RepositoryService repositoryService = processEngine.getRepositoryService();
ExecutionService executionService = processEngine.getExecutionService();
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
ManagementService managementService = processEngine.getManagementService();

Process engine objects defined in the configuration can also be retrieved by type (processEngine.get(Class<T>)) or by name (processEngine.get(String))

The RepositoryService groups all methods to manage the repository of deployments. In this first example, we'll deploy one process resource from the classpath with the RepositoryService:

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 then the highest version number of deployed process definitions with the same key. If no process definitions with a similar key have been deployed, version number 1 is assigned.

In this first example, we'll supply a name and nothing else.

<process name="Insurance claim">
...
</process>

Let's assume that this is the first time that this process gets deployed. Then it will get the following properties:


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:


When using a state activity, the execution (or process instance) will wait when it arrives in the state, waiting for a signal (aka external trigger). Methods signalExecution 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 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();

This chapter will explain the jPDL file format for describing process definitions. The schemadocs can also serve as a quick reference for this information.

An example jPDL process file looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<process name="Purchase order" xmlns="http://jbpm.org/4.0/jpdl">

  <start>
    <transition to="Verify supplier" />
  </start>

  <state name="Verify supplier">
    <transition name="Supplier ok" to="Check supplier data" />
    <transition name="Supplier not ok" to="Error" />
  </state>

  <decision name="Check supplier data">
    <transition name="nok" to="Error" />
    <transition name="ok" to="Completed" />
  </decision>

  <end name="Completed" />

  <end name="Error" />

</process>

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.

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 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


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.

Ends the execution.

Creates a task for a person in the task component.

A simple task that will be assigned to a specific user



<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.



Here's an example process using task candidates:

<process name="TaskCandidates">

  <start>
    <transition to="review" />
  </start>

  <task name="review" 
        candidate-groups="sales-dept">
 
     <transition to="wait" />
  </task>
  
  <state name="wait"/>

</process>
        

After starting, a task will be created. The task will not show up in anyone's personal task list. Following task lists will be empty.

taskService.getAssignedTasks("johndoe");
taskService.getAssignedTasks("joesmoe");

But the task will show up in the 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.0/jpdl">

  <start g="20,20,48,48">
    <transition to="review" />
  </start>
  
  <task name="review" g="96,16,127,52">
    <assignment-handler class="org.jbpm.examples.task.assignmenthandler.AssignTask">
      <field name="assignee">
        <string value="johndoe" />
      </field>
    </assignment-handler>
    <transition to="wait" />
  </task>

  <state name="wait" g="255,16,88,52" />
  
</process>

The referenced class AssignTask looks like this:

public class AssignTask implements AssignmentHandler {

  String assignee;

  public void assign(Assignable assignable, OpenExecution execution) {
    assignable.setAssignee(assignee);
  }
}

Please note that potentially, AssignmentHandler implementations can use the process variables and any other Java API to access resources like your application database to calculate the assignee and candidate users and groups.

Starting a new process instance of the TaskAssignmentHandler process will immediately bring the new execution to the task activity. A new review task is created and at that point, the AssignTask assignment handler is called. That will set johndoe as the assignee. So John Doe will find the task in his personal task list.

Multiple tasks in a process should be assigned to the same user or candidates. Multiple tasks in a process can be associated to a single swimlane. The process instance will remember the candidates and user that performed the first task in the swimlane. And subsequent tasks in the same swimlane will be assigned to those user and candidates.

A swimlane can also be considered as a process role. In some cases, this might boil down to authorization roles in the identity component. But bare in mind that it is not always the same thing.


Swimlanes can be declared inside a process element:



The task swimlane example has the following process file :

<process name="TaskSwimlane" xmlns="http://jbpm.org/4.0/jpdl">

  <swimlane name="sales representative"
            candidate-groups="sales-dept" />

  <start>
    <transition to="enter order data" />
  </start>
  
  <task name="enter order data"
        swimlane="sales representative">

    <transition to="calculate quote"/>
  </task>

  <task 
      name="calculate quote" 
      swimlane="sales representative">
  </task>

</process>

In this example we create the following information in the identity component:

identityService.createGroup("sales-dept");

identityService.createUser("johndoe", "johndoe", "John", "Doe");
identityService.createMembership("johndoe", "sales-dept");

After starting a new process instance, user johndoe will be a candidate for task enter order data. Again like in the previous task candidates example, John Doe can now take this task like this:

taskService.takeTask(taskDbid, "johndoe");

Taking the task will make Litjohndoe the assignee for the task. And since this task is coupled to the swimlane sales representative, assignee johndoe will also be propagated as the assignee in the swimlane.

Next, John Doe can complete the task like this:

taskService.completeTask(taskDbid);

Completing the task will bring the process execution to the next task, which is calculate quote. Also this task is linked to the swimlane. Therefore, the task will be assigned to johndoe. Also the candidate users and candidate groups of the initial assignment will be copied from the swimlane to the task. This is relevant in case user johndoe would release the task and offer it back to the other candidates.

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.




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.






The SubProcessVariables example scenario will show the basic workings of the sub-process activity, how to feed information in the sub process when it starts and how to extract information out of the subprocess when it ends.

The parent process involves a document that needs to be reviewed.


<process name="SubProcessDocument" xmlns="http://jbpm.org/4.0/jpdl">

  <start>
    <transition to="review" />
  </start>

  <sub-process name="review"
               sub-process-key="SubProcessReview">
               
    <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.0/jpdl">

  <start>
    <transition to="get approval"/>
  </start>

  <task name="get approval"
        assignee="johndoe">
               
    <transition to="end"/>
  </task>
  
  <end name="end" />

</process>

The document process is started with a document variable:

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("document", "This document describes how we can make more money...");
    
ProcessInstance processInstance = executionService
    .startProcessInstanceByKey("SubProcessDocument", variables);

Then the parent process execution will arrive in the sub process activity. A sub process instance is created and linked with the super process execution. When the SubProcessReview process instance starts, it arrives in the task. A task will be created for johndoe.

List<Task> taskList = taskService.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.0/jpdl">

  <start>
    <transition to="get approval"/>
  </start>

  <task name="get approval"
        assignee="johndoe">
               
    <transition to="end"/>
  </task>
  
  <end name="end" />

</process>

A new document process instance is started like usual:

ProcessInstance processInstance = executionService
    .startProcessInstanceByKey("SubProcessDocument");

Then task is fetched from johndoe's task list

List<Task> taskList = taskService.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(quot;resultquot;, quot;okquot;);
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.0/jpdl">

  <start>
    <transition to="get approval"/>
  </start>

  <task name="get approval"
        assignee="johndoe">
               
    <transition name="ok" to="ok"/>
    <transition name="nok" to="nok"/>
    <transition name="reject" to="reject"/>
  </task>
  
  <end name="ok" />
  <end name="nok" />
  <end name="reject" />
  
</process>

A new document process instance is started like usual:

ProcessInstance processInstance = executionService
    .startProcessInstanceByKey("SubProcessDocument");

Then task is fetched from johndoe's task list

List<Task> taskList = taskService.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.0/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.



Consider the following example.


<process name="Java" xmlns="http://jbpm.org/4.0/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.


In the next example, we'll see how a script activity with an expression and how the result is stored in a variable.


<process name="ScriptExpression" xmlns="http://jbpm.org/4.0/jpdl">

  <start>
    <transition to="invoke script" />
  </start>

  <script name="invoke script" 
          expr="Send packet to #{person.address}"
          var="text">

    <transition to="wait" />
  </script>
  
  <state name="wait"/>

</process>

This example uses a Person class that looks like this.

public class Person implements Serializable {

  String address;
  
  public Person(String address) {
    this.address = address;
  }
  
  public String getAddress() {
    return address;
  }
  
  public void setAddress(String address) {
    this.address = address;
  }
}

When starting a process instance for this process, we supply a person with a given address property as variable person.

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("person", new Person("Honolulu"));
   
executionService.startProcessInstanceByKey("ScriptText", variables);

After the execution of the script activity, variable text will contain 'Send packet to Honolulu'.

Execution of this process is exactly the same as with the script expression above.

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.



Example usage:

<process name="InlineMail" xmlns="http://jbpm.org/4.0/jpdl">
  <start>
    <transition to="send birthday reminder note" />
  </start>
  <mail name="send birthday reminder note">
    <to addresses="[email protected]" />
    <subject>Reminder: ${person} celebrates his birthday!</subject>
    <text>Do not forget: ${date} is the birthday of ${person} </text>
    <transition to="end" />
  </mail>
  <state name="end"/>
</process>     	
     	

The default configuration after installation contains a jbpm.mail.properties for specifying the mail server to be used by jBPM. To use another mail server then localhost, property mail.smtp.host can be updated in that configuration file.

Refer to the Developers Guide for (unsupported) advanced mail configuration and usage.

Events specify points in a process on which a list of event listeners can be registered. When an execution passes that point in the process, the event listeners are notified. The events and listeners are not shown in the graphical view of the process. An event is fired by an element in the process definition like e.g. the process definition, an activity or a transition.

The EventListener interface looks like this:

public interface EventListener extends Serializable {
  
  void notify(EventListenerExecution execution) throws Exception;

}

All automatic activities can be used as event listeners as well.

To associate a list of event listeners with a process or an activity, use the on element to group the event listeners and specifiy the event. on can be nested as a subelement of process or any activity.

To associate a list of event listeners with a transition take event, just include the event listeners directly in the transition element.




Let's look at an example process with event listeners:


<process name="EventListener" xmlns="http://jbpm.org/4.0/jpdl">

  <on event="start">
    <event-listener class="org.jbpm.examples.eventlistener.LogListener">
      <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 in a static member field:

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]

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.


  • sync (default) keep executing the element as part of the ongoing transaction.
  • async introduces an asynchronous continuation (aka safe point). The ongoing transaction is committed and the element is executed in a new transaction. Transactional asynchronous messaging is used by the jBPM implementation to achieve this.
  • exclusive introduces a asynchronous continuation (aka safe point). The ongoing transaction is committed and the element is executed in a new transaction. Transactional asynchronous messaging is used by the jBPM implementation to achieve this. Exclusive messages will not be processed concurrently. jBPM will make sure that exclusive jobs for the same process instance are not executed concurrently, even if your jBPM configuration has multiple asynchronous message processors (like the JobExecutor) running on different systems. This can be used to prevent optimistic locking failures in case multiple, potentially conflicting jobs are scheduled in the same transaction.

Let's look at a couple of examples.


<process name="AsyncActivity" xmlns="http://jbpm.org/4.0/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.0/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);

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.

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).