Chapter 11. Working with Processes

11.1. BPMN 2.0 Notation

11.1.1. Business Process Model and Notation (BPMN) 2.0 Specification

The Business Process Model and Notation (BPMN) 2.0 specification defines a standard for graphically representing a business process; it includes execution semantics for the defined elements and an XML format to store and share process definitions.

The table below shows the supported elements of the BPMN 2.0 specification and includes some additional elements and attributes.

definitions
Supported attributesSupported elementsExtension attributesExtension elements
 

BPMNDiagram, itemDefinition, signal, process, relationship*

  
process
Supported attributesSupported elementsExtension attributesExtension elements

processType, isExecutable, name, id

property, laneSet, flowElement

packageName, adHoc, version

import, global

sequenceFlow
Supported attributesSupported elementsExtension attributesExtension elements

sourceRef, targetRef, isImmediate, name, id

conditionExpression

priority

 
interface
Supported attributesSupported elementsExtension attributesExtension elements

name, id

operation

  
operation
Supported attributesSupported elementsExtension attributesExtension elements

name, id

inMessageRef

  
laneSet
Supported attributesSupported elementsExtension attributesExtension elements
 

lane

  
lane
Supported attributesSupported elementsExtension attributesExtension elements

name, id

flowNodeRef

  
import
Supported attributesSupported elementsExtension attributesExtension elements
 

name

  
global
Supported attributesSupported elementsExtension attributesExtension elements
 

identifier, type

  

* Used for extension elements for BPMN2, such as simulation data.

BPMN 2.0 Supported Elements and Attributes (Events)

startEvent
Supported attributesSupported elementsExtension attributesExtension elements

name, id

dataOutput, dataOutputAssociation, outputSet, eventDefinition

x, y, width, height

 
endEvent
Supported attributesSupported elementsExtension attributesExtension elements

name, id

dataInput, dataInputAssociation, inputSet, eventDefinition

x, y, width, height

 
intermediateCatchEvent
Supported attributesSupported elementsExtension attributesExtension elements

name, id

dataOutput, dataOutputAssociation, outputSet, eventDefinition

x, y, width, height

 
intermediateThrowEvent
Supported attributesSupported elementsExtension attributesExtension elements

name, id

dataInput, dataInputAssociation, inputSet, eventDefinition

x, y, width, height

 
boundaryEvent
Supported attributesSupported elementsExtension attributesExtension elements

cancelActivity, attachedToRef, name, id

eventDefinition

x, y, width, height

 
terminateEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements
    
compensateEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements

activityRef

documentation, extensionElements

  
conditionalEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements
 

condition

  
errorEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements

errorRef

   
error
Supported attributesSupported elementsExtension attributesExtension elements

errorCode, id

   
escalationEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements

escalationRef

   
escalation
Supported attributesSupported elementsExtension attributesExtension elements

escalationCode, id

   
messageEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements

messageRef

   
message
Supported attributesSupported elementsExtension attributesExtension elements

itemRef, id

   
signalEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements

signalRef

   
timerEventDefinition
Supported attributesSupported elementsExtension attributesExtension elements
 

timeCycle, timeDuration

  

BPMN 2.0 Supported Elements and Attributes (Activities)

task
Supported attributesSupported elementsExtension attributesExtension elements

name, id

ioSpecification, dataInputAssociation, dataOutputAssociation

taskName, x, y, width, height

 
scriptTask
Supported attributesSupported elementsExtension attributesExtension elements

scriptFormat, name, id

script

x, y, width, height

 
script
Supported attributesSupported elementsExtension attributesExtension elements
 

text[mixed content]

  
userTask
Supported attributesSupported elementsExtension attributesExtension elements

name, id

ioSpecification, dataInputAssociation, dataOutputAssociation, resourceRole

x, y, width, height

onEntry-script, onExit-script

potentialOwner
Supported attributesSupported elementsExtension attributesExtension elements
 

resourceAssignmentExpression

  
resourceAssignmentExpression
Supported attributesSupported elementsExtension attributesExtension elements
 

expression

  
businessRuleTask
Supported attributesSupported elementsExtension attributesExtension elements

name, id

 

x, y, width, height, ruleFlowGroup

onEntry-script, onExit-script

manualTask
Supported attributesSupported elementsExtension attributesExtension elements

name, id

 

x, y, width, height

onEntry-script, onExit-script

sendTask
Supported attributesSupported elementsExtension attributesExtension elements

messageRef, name, id

ioSpecification, dataInputAssociation

x, y, width, height

onEntry-script, onExit-script

receiveTask
Supported attributesSupported elementsExtension attributesExtension elements

messageRef, name, id

ioSpecification, dataOutputAssociation

x, y, width, height

onEntry-script, onExit-script

serviceTask
Supported attributesSupported elementsExtension attributesExtension elements

operationRef, name, id

ioSpecification, dataInputAssociation, dataOutputAssociation

x, y, width, height

onEntry-script, onExit-script

subProcess
Supported attributesSupported elementsExtension attributesExtension elements

name, id

flowElement, property, loopCharacteristics

x, y, width, height

 
adHocSubProcess
Supported attributesSupported elementsExtension attributesExtension elements

cancelRemainingInstances, name, id

completionCondition, flowElement, property

x, y, width, height

 
callActivity
Supported attributesSupported elementsExtension attributesExtension elements

calledElement, name, id

ioSpecification, dataInputAssociation, dataOutputAssociation

x, y, width, height, waitForCompletion, independent

onEntry-script, onExit-script

multiInstanceLoopCharacteristics
Supported attributesSupported elementsExtension attributesExtension elements
 

loopDataInputRef, inputDataItem

  
onEntry-script
Supported attributesSupported elementsExtension attributesExtension elements

scriptFormat

 

script

 
onExit-script
Supported attributesSupported elementsExtension attributesExtension elements

scriptFormat

 

script

 

BPMN 2.0 Supported Elements and Attributes (Gateways)

parallelGateway
Supported attributesSupported elementsExtension attributesExtension elements

gatewayDirection, name, id

 

x, y, width, height

 
eventBasedGateway
Supported attributesSupported elementsExtension attributesExtension elements

gatewayDirection, name, id

 

x, y, width, height

 
exclusiveGateway
Supported attributesSupported elementsExtension attributesExtension elements

default, gatewayDirection, name, id

 

x, y, width, height

 
inclusiveGateway
Supported attributesSupported elementsExtension attributesExtension elements

default, gatewayDirection, name, id

 

x, y, width, height

 

BPMN 2.0 Supported Elements and Attributes (Data)

property
Supported attributesSupported elementsExtension attributesExtension elements

itemSubjectRef, id

   
dataObject
Supported attributesSupported elementsExtension attributesExtension elements

itemSubjectRef, id

   
itemDefinition
Supported attributesSupported elementsExtension attributesExtension elements

structureRef, id

   
signal
Supported attributesSupported elementsExtension attributesExtension elements

name, id

   
ioSpecification
Supported attributesSupported elementsExtension attributesExtension elements
 

dataInput, dataOutput, inputSet, outputSet

  
dataInput
Supported attributesSupported elementsExtension attributesExtension elements

name, id

   
dataInputAssociation
Supported attributesSupported elementsExtension attributesExtension elements
 

sourceRef, targetRef, assignment

  
dataOutput
Supported attributesSupported elementsExtension attributesExtension elements

name, id

   
dataOutputAssociation
Supported attributesSupported elementsExtension attributesExtension elements
 

sourceRef, targetRef, assignment

  
inputSet
Supported attributesSupported elementsExtension attributesExtension elements
 

dataInputRefs

  
outputSet
Supported attributesSupported elementsExtension attributesExtension elements
 

dataOutputRefs

  
assignment
Supported attributesSupported elementsExtension attributesExtension elements
 

from, to

  
formalExpression
Supported attributesSupported elementsExtension attributesExtension elements

language

text[mixed content]

  

BPMN 2.0 Supported Elements and Attributes (BPMNDI)

BPMNDiagram
Supported attributesSupported elementsExtension attributesExtension elements
 

BPMNPlane

  
BPMNPlane
Supported attributesSupported elementsExtension attributesExtension elements

bpmnElement

BPMNEdge, BPMNShape

  
BPMNShape
Supported attributesSupported elementsExtension attributesExtension elements

bpmnElement

Bounds

  
BPMNEdge
Supported attributesSupported elementsExtension attributesExtension elements

bpmnElement

waypoint

  
Bounds
Supported attributesSupported elementsExtension attributesExtension elements

x, y, width, height

   
waypoint
Supported attributesSupported elementsExtension attributesExtension elements

x, y

   

11.1.2. BPMN 2.0 Process Example

Here is a BPMN 2.0 process that prints out a "Hello World" statement when the process is started:

<?xml version="1.0" encoding="UTF-8"?>
<definitions id="Definition"
             targetNamespace="http://www.jboss.org/drools"
             typeLanguage="http://www.java.com/javaTypes"
             expressionLanguage="http://www.mvel.org/2.0"
             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
             xmlns:tns="http://www.jboss.org/drools">

  <process processType="Private" isExecutable="true" id="com.sample.bpmn.hello" name="Hello World" >

    <!-- nodes -->
    <scriptTask id="_2" name="Hello" >
      <script>System.out.println("Hello World");</script>
    </scriptTask>
    <startEvent id="_1" />
    <endEvent id="_3" >
        <terminateEventDefinition/>
    </endEvent>

    <!-- connections -->
    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />

  </process>

  <bpmndi:BPMNDiagram>
    <bpmndi:BPMNPlane bpmnElement="com.sample.bpmn.hello" >
      <bpmndi:BPMNShape bpmnElement="_2" >
        <dc:Bounds x="96" y="16" width="80" height="48" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_1" >
        <dc:Bounds x="30" y="22" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_3" >
        <dc:Bounds x="210" y="22" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="_1-_2" >
        <di:waypoint x="66" y="40" />
        <di:waypoint x="96" y="40" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_2-_3" >
        <di:waypoint x="176" y="40" />
        <di:waypoint x="210" y="40" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>

</definitions>

11.1.3. Supported Elements and Attributes in BPMN 2.0 Specification

Red Hat JBoss BPM Suite 6 does not implement all elements and attributes as defined in the BPMN 2.0 specification. However, we do support significant node types that you can use inside executable processes. This includes almost all elements and attributes as defined in the Common Executable subclass of the BPMN 2.0 specification, extended with some additional elements and attributes we believe are valuable in that context as well. The full set of elements and attributes that are supported can be found below, but it includes elements like:

Flow Objects
  • Events

    • Start Event (None, Conditional, Signal, Message, Timer)
    • End Event (None, Terminate, Error, Escalation, Signal, Message, Compensation)
    • Intermediate Catch Event (Signal, Timer, Conditional, Message)
    • Intermediate Throw Event (None, Signal, Escalation, Message, Compensation)
    • Non-interrupting Boundary Event (Escalation, Signal, Timer, Conditional, Message)
    • Interrupting Boundary Event (Escalation, Error, Signal, Timer, Conditional, Message, Compensation)
  • Activities

    • Script Task
    • Task
    • Service Task
    • User Task
    • Business Rule Task
    • Manual Task
    • Send Task
    • Receive Task
    • Reusable Sub-Process (Call Activity)
    • Embedded Sub-Process
    • Event Sub-Process
    • Ad-Hoc Sub-Process
    • Data-Object
  • Gateways

    • Diverging

      • Exclusive
      • Inclusive
      • Parallel
      • Event-Based
    • Converging

      • Exclusive
      • Inclusive
      • Parallel
  • Lanes
Data
  • Java type language
  • Process properties
  • Embedded Sub-Process properties
  • Activity properties
Connecting Objects
  • Sequence flow

11.1.4. Loading and Executing a BPMN2 Process Into Repository

The following example shows how you can load a BPMN2 process into your knowledge base:

import org.kie.api.KieServices;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieBuilder;
import org.kie.internal.io.ResourceFactory;
import org.kie.api.runtime.KieContainer;
import org.kie.api.KieBase;
...
KieServices kServices = KieServices.Factory.get();
KieRepository kRepository = kServices.getRepository();
KieFileSystem kFileSystem = kServices.newKieFileSystem();

kFileSystem.write(ResourceFactory.newClassPathResource("MyProcess.bpmn"));

KieBuilder kBuilder = kServices.newKieBuilder(kFileSystem);
kBuilder.buildAll();

KieContainer kContainer = kServices.newKieContainer(kRepository.getDefaultReleaseId());
KieBase kBase = kContainer.getKieBase();

For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies.

11.2. What Comprises a Business Process

A business process is a graph that describes the order in which a series of steps need to be executed using a flow chart. A process consists of a collection of nodes that are linked to each other using connections. Each of the nodes represents one step in the overall process, while the connections specify how to transition from one node to the other. A large selection of predefined node types have been defined.

A typical process consists of the following parts:

  • The header part that comprises global elements such as the name of the process, imports, and variables.
  • The nodes section that contains all the different nodes that are part of the process.
  • The connections section that links these nodes to each other to create a flow chart.

Figure 11.1. A Business Process

This image shows the steps of "self evaluation" through the project manager and HR manager.

Processes can be created with the following methods:

  • Using the Business Central or Red Hat JBoss Developer Studio with BPMN2 modeler.
  • As an XML file, according to the XML process format as defined in the XML Schema Definition in the BPMN 2.0 specification.
  • By directly creating a process using the Process API.
Note

The Red Hat JBoss Developer Studio Process editor has been deprecated in favor of BPMN2 Modeler for process modeling as it is not being developed any more. However, you can still use it for limited number of supported elements.

11.2.1. Process Nodes

Executable processes consist of different types of nodes which are connected to each other. The BPMN 2.0 specification defines three main types of nodes:

Events
Event elements represent a particular event that occurs or can occur during process runtime.
Activities
Activities represent relatively atomic pieces of work that need to be performed as part of the process execution.
Gateways
Gateways represent forking or merging of workflows during process execution.

11.2.2. Process Properties

Every process has the following properties:

  • ID: The unique ID of the process.
  • Name: The display name of the process.
  • Version: The version number of the process.
  • Package: The package (namespace) the process is defined in.
  • Variables (optional): Variables to store data during the execution of your process.
  • Swimlanes: Swimlanes used in the process for assigning human tasks.

11.2.3. Defining Processes Using XML

You can create processes directly in XML format using the BPMN 2.0 specifications. The syntax of these XML processes is defined using the BPMN 2.0 XML Schema Definition.

The process XML file consists of:

The process element
This is the top part of the process XML that contains the definition of the different nodes and their properties. The process XML consist of exactly one <process> element. This element contains parameters related to the process (its type, name, ID, and package name), and consists of three subsections: a header section (where process-level information like variables, globals, imports, and lanes can be defined), a nodes section that defines each of the nodes in the process, and a connections section that contains the connections between all the nodes in the process.
The BPMNDiagram element
This is the lower part of the process XML that contains all graphical information, like the location of the nodes. In the nodes section, there is a specific element for each node, defining the various parameters and, possibly, sub-elements for that node type.

The following XML fragment shows a simple process that contains a sequence of a Start Event, a Script Task that prints "Hello World" to the console, and an End Event:

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

<definitions
  id="Definition"
  targetNamespace="http://www.jboss.org/drools"
  typeLanguage="http://www.java.com/javaTypes"
  expressionLanguage="http://www.mvel.org/2.0"
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
  xmlns:g="http://www.jboss.org/drools/flow/gpd"
  xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
  xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
  xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
  xmlns:tns="http://www.jboss.org/drools">

  <process processType="Private" isExecutable="true" id="com.sample.hello" name="Hello Process">
    <!-- nodes -->
    <startEvent id="_1" name="Start" />

    <scriptTask id="_2" name="Hello">
      <script>System.out.println("Hello World");</script>
    </scriptTask>

    <endEvent id="_3" name="End" >
      <terminateEventDefinition/>
    </endEvent>

    <!-- connections -->

    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />
  </process>

  <bpmndi:BPMNDiagram>
    <bpmndi:BPMNPlane bpmnElement="com.sample.hello" >

      <bpmndi:BPMNShape bpmnElement="_1" >
        <dc:Bounds x="16" y="16" width="48" height="48" />
      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="_2" >
        <dc:Bounds x="96" y="16" width="80" height="48" />
      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="_3" >
        <dc:Bounds x="208" y="16" width="48" height="48" />
      </bpmndi:BPMNShape>

      <bpmndi:BPMNEdge bpmnElement="_1-_2" >
        <di:waypoint x="40" y="40" />
        <di:waypoint x="136" y="40" />
      </bpmndi:BPMNEdge>

      <bpmndi:BPMNEdge bpmnElement="_2-_3" >
        <di:waypoint x="136" y="40" />
        <di:waypoint x="232" y="40" />
      </bpmndi:BPMNEdge>

    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>

</definitions>

11.3. Activities

An activity is an action performed inside a business process. Activities are classified based on the type of tasks they do:

Task
Use this activity type in your business process to implement a single task which can not be further broken into subtasks.
Subprocess
Use this activity type in your business process when you have a group of tasks to be processed in a sequential order in order to achieve a single result.

Each activity has one incoming and one outgoing connection.

11.3.1. Tasks

A task is an action that is executed inside a business process. Tasks can be of the following types:

Table 11.1. Types of Tasks in Object Library

TaskIconDescription

User

6607

Use the User task activity type in your business process when you require a human actor to execute your task.

  • The User task defines within it, the type of task that needs to be executed. You must pass the data that a human actor may require to execute this task as the content of the task.
  • The User task has one incoming and one outgoing connection. You can use the User tasks in combination with Swimlanes to assign multiple human tasks to similar human actors.

Send

6608

Use the Send task to send a message.

  • A Send task has a message associated with it.
  • When a Send task is activated, the message data is assigned to the data input property of the Send task. A Send task completes when this message is sent.

Receive

6609

Use the Receive task in your process when your process is relying on a specific message to continue.

  • When a Receive task receives the specified message, the data from the message is transferred to the Data Output property of the Receive task and the task completes.

Manual

6610

Use the Manual task when you require a task to be executed by a human actor that need not be managed by your process.

  • The difference between a Manual task and a User task is that a User task is executed in the context of the process, requires system interaction to accomplish the task, and are assigned to specific human actors. The Manual tasks on the other hand, execute without the need to interact with the system and not managed by the process.

Service

6611

Use the Service task in your business process for specifying the tasks use a service (such as a web service) that must execute outside the process engine.

  • The Service task may use any service such as email server, message logger, or any other automated service.
  • You can specify the required input parameters and expected results of this task in its properties. When the associated work is executed and specified result is received, the Service task completes.

Business Rule

6612

Use the Business Rule task when you want a set of rules to be executed as a task in your business process flow.

  • During the execution of your process flow, when the engine reaches the Business Rule task, all the rules associated with this task are fired and evaluated.
  • The DataInputSet and DataOutputSet properties define the input to the rule engine and the calculated output received from the rule engine respectively.
  • The set of rules that this task runs are defined in .drl format.
  • All the rules that belong to a Business Rule task must belong to a specific ruleflow group. You can assign a rule its ruleflow group using the ruleflow-group attribute in the header of the rule. So when a Business Rule task executes, all the rules that belong to the ruleflow-group specified in the ruleflow-group property of the task are executed.

Script

6613

Use the Script task in your business process when you want a script to be executed within the task.

  • A Script task has an associated action that contains the action code and the language that the action is written in.
  • When a Script task is reached in the process, it executes the action and then continues to the next node.
  • Use a Script task in your process to for modeling low level behavior such as manipulating variables. For a complex model, use a Service task.
  • Ensure that the script associated with a Script task is executed as soon as the task is reached in a business process. If that is not possible, use an asynchronous Service task instead.
  • Ensure that your script does not contact an external service as the process engine has no visibility of the external services that a script may call.
  • Ensure that any exception that your script may throw must be caught within the script itself.

None

6614

A None task type is an abstract undefined task type.

11.3.2. Subprocesses

A subprocess is a process within another process. When a parent process calls a child process (subprocess), the child process executes in a sequential manner and once complete, the execution control then transfers to the main parent process. Subprocess can be of the following types:

Table 11.2. Types of Subprocesses in Object Library

SubprocessIconDescription

Reusable

6615

Use the Reusable subprocess to invoke another process from the parent process.

The Reusable subprocess is independent from its parent process.

Multiple Instances

6616

Use the Multiple Instances subprocess when you want to execute the contained subprocess elements multiple number of times.

When the engine reaches a Multiple Instance subprocess in your process flow, the subprocess instances are executed in a sequential manner.

A Multiple Instances subprocess is completed when the condition specified in the MI completion condition property is satisfied.

Embedded

6617

Use the Embedded subprocess if you want a decomposable activity inside your process flow that encapsulates a part of your main process.

When you expand an Embedded subprocess, you can see a valid BPMN diagram inside that comprises a Start Event and at least one End Event.

An Embedded subprocess allows you to define local subprocess variables that are accessible to all elements inside this subprocess.

Ad-Hoc

6618

Use the Ad-Hoc subprocess when you want to execute activities inside your process, for which the execution order is irrelevant. An Ad-Hoc subprocess is a group of activities that have no required sequence relationships.

You can define a set of activities for this subprocess, but the sequence and number of performances for the activities is determined by the performers of the activities.

Use an Ad-Hoc subprocesses for example when executing a list of tasks that have no dependencies between them and can be executed in any order.

Event

6619

Use the Event subprocess in your process flow when you want to handle events that occur within the boundary of a subprocess. This subprocess becomes active when its start event gets triggered.

The Event subprocess differs from the other subprocess as they are not a part of the regular process flow and occur only in the context of a subprocess.

An Event subprocess can be interrupting or non-interrupting. The interrupting Event subprocess interrupts the parent process unlike the non-interrupting Event subprocess.

Note

Only the Reusable subprocess can contain Swimlanes.

11.4. Data

Throughout the execution of a process, data can be retrieved, stored, passed on, and used. To store runtime data during the execution of the process, process variables are used. A variable is defined with a name and a data type. A basic data type could include the following: boolean, int, String, or any kind of object subclass.

Variables can be defined inside a variable scope. The top-level scope is the variable scope of the process itself. Sub-scopes can be defined using a sub-process. Variables that are defined in a sub-scope are only accessible for nodes within that scope.

Whenever a variable is accessed, the process will search for the appropriate variable scope that defines the variable. Nesting variable scopes are allowed. A node will always search for a variable in its parent container; if the variable cannot be found, the node will look in the parent’s parent container, and so on, until the process instance itself is reached. If the variable cannot be found, a read access yields null, and a write access produces an error message. All of this occurs with the process continuing execution.

Variables can be used in the following ways:

  • Process-level variables can be set when starting a process by providing a map of parameters to the invocation of the startProcess method. These parameters will be set as variables on the process scope.
  • Script actions can access variables directly simply by using the name of the variable as a local parameter in their script. For example, if the process defines a variable of type "org.jbpm.Person" in the process, a script in the process could access this directly:

    // call method on the process variable "person"
    
    person.setAge(10);

    Changing the value of a variable in a script can be done through the knowledge context:

    kcontext.setVariable(variableName, value);
    Warning

    Do not create a script variable with the same name as a process variable. Otherwise, an error similar to the following error is thrown during the deployment of your application. In the following case, the variable person has been declared both in a script task and as a process variable.

    ERROR [org.drools.compiler.kie.builder.impl.AbstractKieModule] (default task-16) Unable to build KieBaseModel:defaultKieBase
    Process Compilation error : Process com.myteam.scripttask.ScriptTaskBP(ScriptTask.ScriptTaskBP)
    	com/myteam/scripttask/Process_com$u46$myteam$u46$scripttask$u46$ScriptTaskBP95786628.java (9:437) : Duplicate local variable person
  • Service tasks (and reusable sub-processes) can pass the value of process variables to the outside world (or another process instance) by mapping the variable to an outgoing parameter. For example, the parameter mapping of a service task could define that the value of the process variable x should be mapped to a task parameter y just before the service is invoked. You can also inject the value of the process variable into a hard-coded parameter String using #{expression}. For example, the description of a human task could be defined as the following:

    You need to contact person #{person.getName()}

    Where person is a process variable. This will replace this expression with the actual name of the person when the service needs to be invoked. Similar results of a service (or reusable sub-process) can also be copied back to a variable using result mapping.

  • Various other nodes can also access data. Event nodes, for example, can store the data associated to the event in a variable. Check the properties of the different node types for more information.

Finally, processes (and rules) have access to globals, for example, globally defined variables and data in the Knowledge Session. Globals are directly accessible in actions like variables. Globals need to be defined as part of the process before they can be used. Globals can be set using the following:

ksession.setGlobal(name, value)

Globals can also be set from inside process scripts using:

kcontext.getKieRuntime().setGlobal(name,value);.

11.5. Events

Events are triggers, which when occur, impact a business process. Events are classified as start events, end events, and intermediate events. A start event indicates the beginning of a business process. An end event indicates the completion of a business process. And intermediate events drive the flow of a business process. Every event has an event ID and a name. You can implement triggers for each of these event types to identify the conditions under which an event is triggered. If the conditions of the triggers are not met, the events are not initialized, and hence the process flow does not complete.

11.5.1. Start Events

A start event is a flow element in a business process that indicates the beginning of a business process flow. The execution of a business process starts at this node, so a process flow can only have one start event. A start event can have only one outgoing connection which connects to another node to take the process flow ahead. Start events are of the following types:

Table 11.3. Types of Start Events in Object Library

EventIconDescription

None

6620

Use the None start events when your processes do not need a trigger to be initialized.

  • You can use the start event if your process does not depend on any condition to begin.
  • The start event is mostly used to initialize a subprocess or a process that needs to trigger by default or the trigger for the process is irrelevant.

Message

6621

Use the Message start event when you require your process to start, on receiving a particular message.

  • You can have multiple Message start events in your process.
  • A single message can trigger multiple Message start events that instantiates multiple processes.

Timer

6622

Use the Timer start event when you require your process to initialize at a specific time, specific points in time, or after a specific time span.

  • The Timer start event is mostly used in cases where a waiting state is required, for example, in cases involving a Human Task.

Escalation

6623

Use the Escalation start event in your subprocesses when you require your subprocess to initialize as a response to an escalation.

  • An escalation is identified by an escalation object in the main process, which is inserted into the main process by an Escalation Intermediate event or/and Escalation end event. An Escalation Intermediate event or/and Escalation end event produce an escalation object, which can be consumed by an Escalation Start event or an Escalation intermediate catch event.
  • A process flow can have one or more Escalation start events and the process flow does not complete until all the escalation objects are caught and handled in subprocesses.

Conditional

6624

Use the Conditional start event to start a process instance based on a business condition.

  • A condition output is a Boolean value and when a condition is evaluated as true, the process flow is initialized.
  • You can have one or more Conditional start events in your business process.

Error

6625

Use the Error start event in a subprocess when you require your subprocess to trigger as a response to a specific error object.

  • An error object indicates an incorrect process ending and must be handled for the process flow to complete.
  • An error object is inserted into a business process by an Error end event and can be handled by a Error intermediate catch event, or Error start event depending on the scope of the error in a process flow.

Compensation

6626

Use the Compensation start event in a subprocess when you require to handle a compensation.

  • A compensation means undoing the results of an already completed action. Note that this is different than an error. An error suspends a process at the location where it occurs, however, a compensation compensates the results of an action the process has already committed and needs to be undone.
  • A Compensation start event starts a subprocess and is the target Activity of a Compensation intermediate event.

Signal

6627

Use the Signal start event to start a process instance based on specific signals received from other processes.

  • A signal is identified by a signal object. A signal object defines a unique reference ID that is unique in a session.
  • A signal object is inserted in a process by a throw signal intermediate event as an action of an activity.

11.5.2. End Events

An end event marks the end of a business process. Your business process may have more than one end event. An end event has one incoming connection and no outgoing connections. End events are of the following types:

Table 11.4. Types of End Events in Object Library

EventIconDescription

None

6628

Use the None error end event to mark the end of your process or a subprocess flow. Note that this does not influence the workflow of any parallel subprocesses.

Message

6629

Use the Message end event to end your process flow with a message to an element in another process. An intermediate catch message event or a start message event in another process can catch this message to further process the flow.

Escalation

6630

Use the Escalation end event to mark the end of a process as a result of which the case in hand is escalated. This event creates an escalation signal that further triggers the escalation process.

Error

6631

Use the Error end event in your process or subprocess to end the process in an error state and throw a named error, which can be caught by a Catching Intermediate event.

Cancel

6632

Use the Cancel end event to end your process as canceled. Note that if your process comprises any compensations, it completes them and then marks the process as canceled.

Compensation

6633

Use the Compensation end event to end the current process and trigger compensation as the final step.

Signal

6634

Use the Signal end event to end a process with a signal thrown to an element in one or more other processes. Another process can catch this signal using Catch intermediate events.

Terminate

6635

Use the Terminate end event to terminate the entire process instance immediately. Note that this terminates all the other parallel execution flows and cancels any running activities.

11.5.3. Intermediate Events

Intermediate events occur during the execution of a process flow, and they drive the flow of the process. Some specific situations in a process may trigger these intermediate events. Intermediate events can occur in a process with one or no incoming flow and an outgoing flow. Intermediate events can further be classified as:

  • Catching Intermediate Events;
  • Throwing Intermediate Events.

11.5.3.1. Catching Intermediate Events

Catching intermediate events comprises intermediate events which implement a response to specific indication of a situation from the main process workflow. Catching intermediate events are of the following types:

  • Message: Use the Message catching intermediate events in your process to implement a reaction to an arriving message. The message that this event is expected to react to, is specified in its properties. It executes the flow only when it receives the specific message.
  • Timer: Use the Timer intermediate event to delay the workflow execution until a specified point or duration. A Timer intermediate event has one incoming flow and one outgoing flow and its execution starts when the incoming flow transfers to the event. When placed on an activity boundary, the execution is triggered at the same time as the activity execution.
  • Escalation: Use the Escalation catching intermediate event in your process to consume an Escalation object. An Escalation catching intermediate event awaits a specific escalation object defined in its properties. Once it receives the object, it triggers execution of its outgoing flow.
  • Conditional: Use the Conditional intermediate event to execute a workflow when a specific business Boolean condition that it defines, evaluates to true. When placed in the process workflow, a Conditional intermediate event has one incoming flow and one outgoing flow and its execution starts when the incoming flow transfers to the event. When placed on an activity boundary, the execution is triggered at the same time as the activity execution. Note that if the event is non-interrupting, it triggers continuously while the condition is true.
  • Error: Use the Error catching intermediate event in your process to execute a workflow when it received a specific error object defined in its properties.
  • Compensation: Use the Compensation intermediate event to handle compensation in case of partially failed operations. A Compensation intermediate event is a boundary event that is attached to an activity in a transaction subprocess that may finish with a Compensation end event or a Cancel end event. The Compensation intermediate event must have one outgoing flow that connects to an activity that defines the compensation action needed to compensate for the action performed by the activity.
  • Signal: Use the Signal catching intermediate event to execute a workflow once a specified signal object defined in its properties is received from the main process or any other process.

11.5.3.2. Throwing Intermediate Events

Throwing intermediate events comprises events which produce a specified trigger in the form of a message, escalation, or signal, to drive the flow of a process. Throwing intermediate events are of the following types:

  • Message: Use the Message throw intermediate event to produce and send a message to a communication partner (such as an element in another process). Once it sends a message, the process execution continues.
  • Escalation: Use the Escalation throw intermediate event to produce an escalation object. Once it creates an escalation object, the process execution continues. The escalation object can be consumed by an Escalation start event or an Escalation intermediate catch event, which is looking for this specific escalation object.
  • Signal: Use the Signal throwing intermediate events to produces a signal object. Once it creates a signal object, the process execution continues. The signal object is consumed by a Signal start event or a Signal catching intermediate event, which is looking for this specific signal object.

11.6. Gateways

"Gateways are used to control how Sequence Flows interact as they converge and diverge within a Process."[1]

Gateways are used to create or synchronize branches in the workflow using a set of conditions which is called the gating mechanism. Gateways are either converging (multiple flows into one flow) or diverging (one flow into multiple flows).

One Gateway cannot have multiple incoming and multiple outgoing flows.

Depending on the gating mechanism you want to apply, you can use the following types of gateways:

  • Parallel (AND): in a converging gateway, waits for all incoming flows. In a diverging gateway, takes all outgoing flows simultaneously.
  • Inclusive (OR): in a converging gateway, waits for all incoming flows whose condition evaluates to true. In a diverging gateway takes all outgoing flows whose condition evaluates to true.
  • Exclusive (XOR): in a converging gateway, only the first incoming flow whose condition evaluates to true is chosen. In a diverging gateway only one outgoing flow is chosen.
  • Event-based: used only in diverging gateways for reacting to events. See Section 11.6.1.1, “Event-Based Gateway”.
  • Data-based Exclusive: used in both diverging and converging gateways to make decisions based on available data. See Section 11.6.1.4, “Data-Based Exclusive Gateway”.

11.6.1. Gateway Types

11.6.1.1. Event-Based Gateway

"The Event-Based Gateway has pass-through semantics for a set of incoming branches (merging behavior). Exactly one of the outgoing branches is activated afterwards (branching behavior), depending on which of events of the Gateway configuration is first triggered."[2]

The Gateway is only diverging and allows you to react to possible events as opposed to the Data-based Exclusive Gateway, which reacts to the process data. It is the event that actually occurs that decides which outgoing flow is taken. As it provides the mechanism to react to exactly one of the possible events, it is exclusive, that is, only one outgoing flow is taken.

The Gateway might act as a start event, where the process is instantiated only if one the Intermediate Events connected to the Event-Based Gateway occurs.

11.6.1.2. Parallel Gateway

"A Parallel Gateway is used to synchronize (combine) parallel flows and to create parallel flows."[3]

Diverging
Once the incoming flow is taken, all outgoing flows are taken simultaneously.
Converging
The Gateway waits until all incoming flows have entered and only then triggers the outgoing flow.

11.6.1.3. Inclusive Gateway

Diverging

Once the incoming flow is taken, all outgoing flows whose condition evaluates to true are taken. Connections with lower priority numbers are triggered before triggering higher priority ones; priorities are evaluated but the BPMN2 specification doesn’t guarantee this. So for portability reasons it is recommended that you do not depend on this.

Important

Make sure that at least one of the outgoing flow evaluates to true at runtime; otherwise, the process instance terminates with a runtime exception.

Converging
The Gateway merges all incoming flows previously created by a diverging Inclusive Gateway; that is, it serves as a synchronizing entry point for the Inclusive Gateway branches.
Attributes
Default gate
The outgoing flow taken by default if no other flow can be taken.

11.6.1.4. Data-Based Exclusive Gateway

Diverging

The Gateway triggers exactly one outgoing flow: the flow with the constraint evaluated to true and the lowest priority is taken. After evaluating the constraints that are linked to the outgoing flows: the constraint with the lowest priority number that evaluates to true is selected.

Possible Runtime Exception

Make sure that at least one of the outgoing Flows evaluates to true at runtime: if no Flow can be taken, the execution returns a runtime exception.

Converging
The Gateway allows a workflow branch to continue to its outgoing flow as soon as it reaches the Gateway; that is, whenever one of the incoming flows triggers the Gateway, the workflow is sent to the outgoing flow of the Gateway; if it is triggered from more than one incoming connection, it triggers the next node for each trigger.
Attributes
Default gate
The outgoing flow taken by default if no other flow can be taken.

11.7. Variables

Variables are elements that serve for storing a particular type of data during runtime. The type of data a variable contains is defined by its data type.

Just like any context data, every variable has its scope that defines its "visibility". An element, such as a process, subprocess, or task can only access variables in its own and parent contexts: variables defined in the element’s child elements cannot be accessed. Therefore, when an elements requires access to a variable on runtime, its own context is searched first. If the variable cannot be found directly in the element’s context, the immediate parent context is searched. The search continues to "level up" until the Process context is reached; in case of globals, the search is performed directly on the session container. If the variable cannot be found, a read access request returns null and a write access produces an error message, and the process continues its execution. Variables are searched for based on their ID.

In Red Hat JBoss BPM Suite, variables can live in the following contexts:

  • Session context: Globals are visible to all process instances and assets in the given session and are intended to be used primarily by business rules and by constrains. The are created dynamically by the rules or constrains.
  • Process context: Process variables are defined as properties in the BPMN2 definition file and are visible within the process instance. They are initialized at process creation and destroyed on process finish.
  • Element context: Local variables are available within their process element, such as an activity. They are initialized when the element context is initialized, that is, when the execution workflow enters the node and execution of the OnEntry action finished if applicable. They are destroyed when the element context is destroyed, that is, when the execution workflow leaves the element.

    Values of local variables can be mapped to global or process variables using the assignment mechanism (see Section 11.8, “Assignment”). This allows you to maintain relative independence of the parent element that accommodates the local variable. Such isolation may help prevent technical exceptions.

11.8. Assignment

The assignment mechanism allows you to assign a value to an object, such as a variable, before or after the particular element is executed.

When defining assignment on an activity element, the value assignment is performed either before or after activity execution. If the assignment defines mapping to a local variable, the time when the assignment is performed depends on whether the local variable is defined as an DataInput or DataOutput item.

For example, if you need to assign a task to a user whose ID is a process variable, use the assignment to map the variable to the parameter ActorId.

Assignment is defined in the Assignments property in case of activity elements and in the DataInputAssocations or DataOutputAssociations property in case of non-activity elements.

Data Types in Assignment

As parameters of the type String can make use of the assignment mechanism by applying the respective syntax directly in their value, #{userVariable}, assignment is rather intended for mapping of properties that are not of type String.

11.9. Action Scripts

Action scripts are pieces of code that define the Script property or an element’s interceptor action. Action scripts have access to global variables, process variables, and the predefined variable kcontext. Accordingly, kcontext is an instance of the ProcessContext interface. See the ProcessContext Javadoc for more information.

Currently, Java and MVEL are supported as dialects for action scripts definitions. MVEL accepts any valid Java code and additionally provides support for nested access to parameters. For example, the MVEL equivalent of Java call person.getName() is person.name.

Example 11.1. Sample Action Script

The following action script prints out the name of the person:

// Java dialect
System.out.println(person.getName());
// MVEL dialect
System.out.println(person.name);
Process Instance Action Scripts

Additionally, you can use action scripts to view information about process instances.

Use the following commands to:

  • Return the ID of a process instance:

    System.out.println(kcontext.getProcessInstance().getId());
  • Return the parent process instance ID if a process instance has a parent:

    System.out.println(kcontext.getProcessInstance().getParentProcessInstanceId());
  • Return the ID of a process definition that is related to a process instance:

    System.out.println(kcontext.getProcessInstance().getProcessId());
  • Return the name of a process definition that is related to a process instance:

    System.out.println(kcontext.getProcessInstance().getProcessName());
  • Return the state of a process instance:

    System.out.println(kcontext.getProcessInstance().getState());

To set a process variable in an action script, use kcontext.setVariable("VARIABLE_NAME", "VALUE").

11.10. Constraints

There are two types of constraints in business processes: code constraints and rule constraints.

  • Code constraints are boolean expressions evaluated directly whenever they are reached; these constraints are written in either Java or MVEL. Both Java and MVEL code constraints have direct access to the globals and variables defined in the process.

    Here is an example of a valid Java code constraint, person being a variable in the process:

    return person.getAge() > 20;

    Here is an example of a valid MVEL code constraint, person being a variable in the process:

    return person.age > 20;
  • Rule constraints are equal to normal Drools rule conditions. They use the Drools Rule Language syntax to express complex constraints. These rules can, like any other rule, refer to data in the working memory. They can also refer to globals directly. Here is an example of a valid rule constraint:

    Person(age > 20)

    This tests for a person older than 20 in the working memory.

Rule constraints do not have direct access to variables defined inside the process. However, it is possible to refer to the current process instance inside a rule constraint by adding the process instance to the working memory and matching for the process instance in your rule constraint. Logic is included to make sure that a variable processInstance of type WorkflowProcessInstance will only match the current process instance and not other process instances in the working memory. Note, it is necessary to insert the process instance into the session. If it is necessary to update the process instance, use Java code or an on-entry, on-exit, or explicit action in the process. The following example of a rule constraint will search for a person with the same name as the value stored in the variable name of the process:

processInstance : WorkflowProcessInstance()
Person(name == (processInstance.getVariable("name")))
# add more constraints here ...

11.11. Timers

Timers wait for a predefined amount of time before triggering, once, or repeatedly. You can use timers to trigger certain logic after a certain period, or to repeat some action at regular intervals.

Configuring Timer with Delay and Period

A Timer node is set up with a delay and a period. The delay specifies the amount of time to wait after node activation before triggering the timer for the first time. The period defines the time between subsequent trigger activations. A period of 0 results in a one-shot timer. The (period and delay) expression must be of the form [#d][#h][#m][#s][#[ms]]. You can specify the amount of days, hours, minutes, seconds, and milliseconds. Milliseconds is the default value. For example, the expression 1h waits one hour before triggering the timer again.

Configuring Timer ISO-8601 Date Format

Since version 6, you can configure timers with valid ISO8601 date format that supports both one shot timers and repeatable timers. You can define timers as date and time representation, time duration or repeating intervals. For example:

Date - 2013-12-24T20:00:00.000+02:00 - fires exactly at Christmas Eve at 8PM
Duration - PT1S - fires once after 1 second
Repeatable intervals - R/PT1S - fires every second, no limit.
	Alternatively R5/PT1S fires 5 times every second
Configuring Timer with Process Variables

In addition to the above mentioned configuration options, you can specify timers using process variable that consists of string representation of either delay and period or ISO8601 date format. By specifying #{variable}, the engine dynamically extracts process variable and uses it as timer expression. The timer service is responsible for making sure that timers get triggered at the appropriate times. You can cancel timers so that they are no longer triggered. You can use timers in the following ways inside a process:

  • You can add a timer event to a process flow. The process activation starts the timer, and when it triggers, once or repeatedly, it activates the timer node’s successor. Subsequently, the outgoing connection of a timer with a positive period is triggered multiple times. Canceling a Timer node also cancels the associated timer, after which no more triggers occur.
  • You can associate timer with a sub-process or tasks as a boundary event.
Updating Timer Within a Running Process Instance

Sometimes a process requires the possibility to dynamically alter the timer period or delay without the need to restart the entire process workflow. In that case, an already scheduled timer can be rescheduled to meet the new requirements: for example to prolong or shorten the timer expiration time or change the delay, period, and repeat limit.

For this reason, jBPM offers a corresponding UpdateTimerCommand class which allows you to perform these several steps as an atomic operation. All of them are then done within the same transaction.

org.jbpm.process.instance.command.UpdateTimerCommand

It is supported to update the boundary timer events as well as the intermediate timer events.

You can reschedule the timer by specifying the two mandatory parameters and one of the three optional parameter sets of the UpdateTimerCommand class.

Both of the following two parameters are mandatory:

  • process instance ID (long);
  • timer node name (String).

Next, choose and configure one of the three following parameter sets:

  • delay (long);
  • period (long) and repeat limit (int);
  • delay, period, and repeat limit.

Example 11.2. Rescheduling Timer Event

// Start the process instance and record its ID:
long id = kieSession.startProcess(BOUNDARY_PROCESS_NAME).getId();

// Set the timer delay to 3 seconds:
kieSession.execute(new UpdateTimerCommand(id, BOUNDARY_TIMER_ATTACHED_TO_NAME, 3));

As you can notice, the rescheduling is performed using the kieSession executor to ensure execution within the same transaction.

Troubleshooting
Getting IllegalStateException Exception

The Intelligent Process Server uses EJB timer service by default for implementation of timer-based nodes. Consequently, the limitations described in the warning message here about Singleton strategy and CMT are valid for the out-of-the-box Intelligent Process Server setup. To resolve the issue:

  • Change the RuntimeManager strategy.
  • Disable the default EJB timer service for timer nodes by setting the system property org.kie.timer.ejb.disabled to true.
The Intelligent Process Server Throws InactiveTransactionException When Using Timers

When you deploy the Intelligent Process Server on Red Hat JBoss EAP 7 and configure a database for the EJB timer service, processes that require timers end in the InactiveTransactionException exception similar to the following:

WFLYEJB0018: Ignoring exception during setRollbackOnly: com.arjuna.ats.jta.exceptions.InactiveTransactionException: ARJUNA016102: The transaction is not active! Uid is ...

To resolve this issue:

  1. Update your Red Hat JBoss BPM Suite to version 6.4.2.
  2. Set the property org.jbpm.ejb.timer.tx to true.

    Note that the property is not available in previous versions of Red Hat JBoss BPM Suite. See chapter System Properties of Red Hat JBoss BPM Suite Administration and Configuration Guide for further information.

11.12. Multi-Threading

11.12.1. Multi-Threading

In the following text, we will refer to two types of "multi-threading": logical and technical. Technical multi-threading is what happens when multiple threads or processes are started on a computer, for example by a Java or C program. Logical multi-threading is what we see in a BPM process after the process reaches a parallel gateway. From a functional standpoint, the original process will then split into two processes that are executed in a parallel fashion.

The BPM engine supports logical multi-threading; for example, processes that include a parallel gateway are supported. We’ve chosen to implement logical multi-threading using one thread; accordingly, a BPM process that includes logical multi-threading will only be executed in one technical thread. The main reason for doing this is that multiple (technical) threads need to be be able to communicate state information with each other if they are working on the same process. This requirement brings with it a number of complications. While it might seem that multi-threading would bring performance benefits with it, the extra logic needed to make sure the different threads work together well means that this is not guaranteed. There is also the extra overhead incurred because we need to avoid race conditions and deadlocks.

11.12.2. Engine Execution

In general, the BPM engine executes actions in serial. For example, when the engine encounters a script task in a process, it will synchronously execute that script and wait for it to complete before continuing execution. Similarly, if a process encounters a parallel gateway, it will sequentially trigger each of the outgoing branches, one after the other. This is possible since execution is almost always instantaneous, meaning that it is extremely fast and produces almost no overhead. As a result, the user will usually not even notice this. Similarly, action scripts in a process are also synchronously executed, and the engine will wait for them to finish before continuing the process. For example, doing a Thread.sleep(…​) as part of a script will not make the engine continue execution elsewhere but will block the engine thread during that period.

The same principle applies to service tasks. When a service task is reached in a process, the engine will also invoke the handler of this service synchronously. The engine will wait for the completeWorkItem(…​) method to return before continuing execution. It is important that your service handler executes your service asynchronously if its execution is not instantaneous.

To implement an asynchronous service handler, implement the service in a new thread using the executeWorkItem() method in the work item handler that allows the process instance to continue its execution.

package documentation.wih.async;

import java.util.concurrent.TimeUnit;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.api.runtime.process.WorkItemManager;

public class MyServiceTaskHandler implements WorkItemHandler {
    private Thread asyncThread;
    public void executeWorkItem(final WorkItem workItem, final WorkItemManager manager) {

        asyncThread = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Hello number + " + i + " from async!");
                    waitASecond();
                }
            }
        });
        asyncThread.start();

        manager.completeWorkItem(workItem.getId(), null);
    }
    public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
        //asyncThread can't be aborted
    }
    private static void waitASecond() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException ignored) {}
    }
}

An example of this would be a service task that invokes an external service. Since the delay in invoking this service remotely and waiting for the results might be too long, it might be a good idea to invoke this service asynchronously. This means that the handler will only invoke the service and will notify the engine later when the results are available. In the mean time, the process engine then continues execution of the process.

Human tasks are a typical example of a service that needs to be invoked asynchronously, as we don’t want the engine to wait until a human actor has responded to the request. The human task handler will only create a new task (on the task list of the assigned actor) when the human task node is triggered. The engine will then be able to continue execution on the rest of the process (if necessary), and the handler will notify the engine asynchronously when the user has completed the task.

11.12.3. Job Executor for Asynchronous Execution

In Red Hat JBoss BPM Suite, the Job Executor component integrates with the runtime engine for processing asynchronous tasks. You can delegate asynchronous execution operations, such as error handling, retry, cancellation, and history logging in a new thread (using custom implementation of WorkItemHandler) and use the Job Executor to handle these operations for you. The Job Executor provides an environment for background execution of commands, which are nothing but business logic encapsulated within a simple interface.

The custom tasks that the process engine delegates to the Job Executor runs as asynchronous WorkItemHandler. Red Hat JBoss BPM Suite provides AsyncWorkItemHandler that is backed by the Red Hat JBoss BPM Suite Job Executor. During the execution, the AsyncWorkItemHandler sets contextual data available inside the command. You can configure the AsyncWorkItemHandler class in two ways:

  • As a generic handler where you provide the command name as part of the work item parameters. In Business Central while modeling a process, if you need to execute some work item asynchronously: specify async as the value for the TaskName property, create a data input called CommandClass and assign the fully-qualified class name of this CommandClass as the data input.
  • As a specific handler which is created to handle a given type of work item, thus allowing you to register different instances of AsyncWorkItemHandler for different work items. Commands are most likely to be dedicated to a particular work item, which allows you to specify the CommandClass at registration time instead of requiring it at design time, as with the first approach. But this means that an additional CDI bean that implements WorkItemHandlerProducer interface needs to be provided and placed on the application classpath so that the CDI container can find it. When you are ready to model your process, set the value of the TaskName property to the one provided at registration time.

11.12.4. Using Job Executor in Embedded Mode

The Job Executor API is a public API and is available within kie-api (org.kie.api.executor). You can run your background processes asynchronously using the Job Executor from Business Central or outside the Business Central in embedded mode. To use the Job Executor in Business Central, see Section 11.12.6, “Using Job Executor in Business Central”. Perform the following steps to use the Job Executor in the embedded mode:

  1. Implement the Command interface.
  2. Transfer business data from the process engine to your Command implementation.
  3. Configure the Job Executor.
  4. Register the AsyncWorkItemHandler handler, which uses the Job Executor.
  5. Provide the execution results to the process engine.

Wrapping Business Logic with the Command Interface

The Job Executor contains the business logic to be executed and does not have any process runtime related information. The Job Executor works on instances of the Command interface. It receives data through the CommandContext object and returns results of the execution with ExecutionResults class:

package org.kie.api.executor;

import org.kie.api.executor.ExecutionResults;

public interface Command {
  public ExecutionResults execute(CommandContext ctx) throws Exception;
}

Here, ctx is the contextual data given by the executor service.

Since the Job Executor is decoupled from the runtime process engine and provides only the logic that is to be executed as a part of that command, it promotes reuse of already existing logic by wrapping it with Command implementation.

Transferring Business Data from the Process Engine to the Command Interface

The input data is transferred from the process engine to the command using the data transfer object CommandContext. It is important that the data CommandContext holds is serializable.

package org.kie.api.executor;

import java.io.Serializable;

public class CommandContext implements Serializable {

  private static final long serialVersionUID = -1440017934399413860L;
  private Map<String, Object> data;

  public CommandContext() {
    data  = new HashMap<String, Object>();
  }

  public CommandContext(Map<String, Object> data) {
    this.data = data;
  }

  public void setData(Map<String, Object> data) {
    this.data = data;
  }

  public Map<String, Object> getData() {
    return data;
  }

  public Object getData(String key) {
    return data.get(key);
  }

  public void setData(String key, Object value) {
    data.put(key, value);
  }

  public Set<String> keySet() {
    return data.keySet();
  }

  @Override
  public String toString() {
    return "CommandContext{" + "data=" + data + '}';
  }
}

CommandContext should provide the following:

  • businessKey: a unique identifier of the caller.
  • callbacks: the fully qualified classname (FQCN) of the CommandCallback instance to be called on command completion.

Configuring the Executor

The Job Executor API usage scenarios are identical when used from Business Central and when used outside of Business Central. See example Job Executor configuration options:

  1. In-memory Job Executor with optional configuration:

    import org.jbpm.executor.ExecutorServiceFactory;
    
    ..
    
    // Configuration of in-memory executor service.
    executorService = ExecutorServiceFactory.newExecutorService();
    
    // Set number of threads which will be used by executor - default is 1.
    executorService.setThreadPoolSize(1);
    
    // Sets interval at which executor threads are running in seconds - default is 3.
    executorService.setInterval(1);
    
    // Sets time unit of interval - default is SECONDS.
    executorService.setTimeunit(TimeUnit.SECONDS);
    
    // Number of retries in case of excepting during execution of command - default is 3.
    executorService.setRetries(1);
    
    executorService.init();
  2. Executor configuration using EntityManagerFactory to store jobs into a database:

    emf = Persistence.createEntityManagerFactory("org.jbpm.executor");
    
    // Configuration of database executor service.
    executorService = ExecutorServiceFactory.newExecutorService(emf);
    
    // Optional configuration is skipped.
    executorService.init();

Registering the AsyncWorkItemHandler Handler

The AsyncWorkItemHandler handler uses Job Executor for scheduling tasks. See the following code sample to register the AsyncWorkItemHandler handler:

import java.util.List;
import java.util.Map;

import org.kie.api.event.process.ProcessEventListener;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.manager.RuntimeEnvironment;
import org.kie.api.runtime.manager.RuntimeEnvironmentBuilder;
import org.kie.api.runtime.manager.RuntimeManagerFactory;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.runtime.manager.context.EmptyContext;
import org.jbpm.runtime.manager.impl.DefaultRegisterableItemsFactory;

...

 RuntimeEnvironment environment = RuntimeEnvironmentBuilder
  .Factory.get().newDefaultBuilder()
  .userGroupCallback(userGroupCallback)
  .addAsset(ResourceFactory.newClassPathResource
    ("BPMN2-ScriptTask.bpmn2"), ResourceType.BPMN2)
  .registerableItemsFactory(new DefaultRegisterableItemsFactory() {

    @Override
    public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) {
      Map<String, WorkItemHandler> handlers = super.getWorkItemHandlers(runtime);
      handlers.put("async", new AsyncWorkItemHandler
        (executorService, "org.jbpm.executor.commands.PrintOutCommand"));
      return handlers;
    }

    @Override
    public List<ProcessEventListener> getProcessEventListeners( RuntimeEngine runtime) {
      List<ProcessEventListener> listeners = super.getProcessEventListeners(runtime);
      listeners.add(countDownListener);
      return listeners;
    }
  })

  .get();

manager = RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment);
assertNotNull(manager);

RuntimeEngine runtime = manager.getRuntimeEngine(EmptyContext.get());
KieSession ksession = runtime.getKieSession();
assertNotNull(ksession);

ProcessInstance processInstance = ksession.startProcess("ScriptTask");
assertEquals(ProcessInstance.STATE_ACTIVE, processInstance.getState());

Thread.sleep(3000);

processInstance = runtime.getKieSession().getProcessInstance(processInstance.getId());
assertNull(processInstance);

Providing Execution Results to the Process Engine

The outcome of the command is provided to process engine using the ExecutionResults class. ExecutionResults is a data transfer object. The data provided by this class must be serializable.

package org.kie.api.executor;

import org.kie.api.executor.ExecutorService;
import java.io.Serializable;

public class ExecutionResults implements Serializable {

  private static final long serialVersionUID = -1738336024526084091L;
  private Map<String, Object> data = new HashMap<String, Object>();

  public ExecutionResults() {}

  public void setData(Map<String, Object> data) {
    this.data = data;
  }

  public Map<String, Object> getData() {
    return data;
  }

  public Object getData(String key) {
    return data.get(key);
  }

  public void setData(String key, Object value) {
    data.put(key, value);
  }

  public Set<String> keySet() {
    return data.keySet();
  }

  @Override
  public String toString() {
    return "ExecutionResults{" + "data=" + data + '}';
  }
}

For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies.

11.12.5. Hello World Example with Embedded Job Executor

The following example uses the Job Executor in embedded mode. If you are using Maven, see example Embedded jBPM Engine Dependencies for a list of Maven dependencies. The following example uses Red Hat JBoss Developer Studio to model and execute the project. To use the Job Executor in embedded mode:

  1. In your jBPM project, add the src/main/resources/META-INF/drools.rulebase.conf file with the following content:

    drools.workDefinitions = WorkDefinitions.wid
  2. Add the src/main/resources/META-INF/WorkDefinitions.wid file with the following content:

    import org.drools.core.process.core.datatype.impl.type.ObjectDataType;
    import java.lang.Long;
    import java.lang.Integer;
    import java.lang.Boolean;
    import java.lang.String;
    
    
    [
      [
        "name" : "AsyncWIH",
        "results" : [
            "Result" : new ObjectDataType(),
        ],
        "displayName" : "AsyncWIH",
        "icon" : "async-16x15.png"
      ],
    ]
  3. Add the following BPMN diagram in the src/main/resources directory:

    asyncWIH

    In your diagram, create an org.kie.api.executor.ExecutionResults variable and map it to the Output variable of the asynchronous work item.

  4. Create a Command implementation in src/main/java:

    package com.sample;
    
    import org.kie.api.executor.Command;
    import org.kie.api.executor.CommandContext;
    import org.kie.api.executor.ExecutionResults;
    
    public class HelloWorldCommand implements Command {
    
    	@Override
    	public ExecutionResults execute(CommandContext arg0) throws Exception {
    		System.out.println("Hello World from Business Command!");
    		return new ExecutionResults();
    	}
    }
  5. Create the main class that will register the work item handler and execute the process:

    package com.sample;
    
    import java.util.Properties;
    
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    
    import org.jbpm.test.JBPMHelper;
    import org.kie.api.KieBase;
    import org.kie.api.KieServices;
    import org.kie.api.runtime.KieContainer;
    import org.kie.api.runtime.KieSession;
    import org.kie.api.runtime.manager.RuntimeEngine;
    import org.kie.api.runtime.manager.RuntimeEnvironmentBuilder;
    import org.kie.api.runtime.manager.RuntimeManager;
    import org.kie.api.runtime.manager.RuntimeManagerFactory;
    
    import bitronix.tm.resource.jdbc.PoolingDataSource;
    
    import org.kie.api.executor.ExecutorService;
    import org.jbpm.executor.ExecutorServiceFactory;
    import org.jbpm.executor.impl.wih.AsyncWorkItemHandler;
    
    public class ProcessMain {
    
    	static EntityManagerFactory emf;
    
    	public static void main(String[] args) throws InterruptedException {
    		KieServices ks = KieServices.Factory.get();
    		KieContainer kContainer = ks.getKieClasspathContainer();
    		KieBase kbase = kContainer.getKieBase("kbase");
    
    		RuntimeManager manager = createRuntimeManager(kbase);
    		RuntimeEngine engine = manager.getRuntimeEngine(null);
    		KieSession ksession = engine.getKieSession();
    
    		//Register the work item handler and point it to the FQCN of the command implementation.
    		ExecutorService executorService = ExecutorServiceFactory.newExecutorService(ProcessMain.emf);
    		ksession.getWorkItemManager().registerWorkItemHandler("AsyncWIH", new AsyncWorkItemHandler(executorService,"com.sample.HelloWorldCommand"));
    		executorService.init();
    
    		ksession.startProcess("com.sample.bpmn.hello");
    		manager.disposeRuntimeEngine(engine);
    
    		//Wait for the executor to finish. Otherwise, the process finishes before the job executor is checked.
    		Thread.sleep(5000);
    		System.exit(0);
    	}
    
    	private static RuntimeManager createRuntimeManager(KieBase kbase) {
    		JBPMHelper.startH2Server();
    
    		// Create a data source if no custom datasource is available
    		Properties properties = JBPMHelper.getProperties();
    		PoolingDataSource pds = new PoolingDataSource();
    
    		//Note the JNDI name
    		pds.setUniqueName("jndi:/example");
    		pds.setClassName("bitronix.tm.resource.jdbc.lrc.LrcXADataSource");
    		pds.setMaxPoolSize(5);
    		pds.setAllowLocalTransactions(true);
    		pds.getDriverProperties().put("user", properties.getProperty("persistence.datasource.user", "sa"));
    		pds.getDriverProperties().put("password", properties.getProperty("persistence.datasource.password", ""));
    		pds.getDriverProperties().put("url", properties.getProperty("persistence.datasource.url", "jdbc:h2:tcp://localhost/~/jbpm-db;MVCC=TRUE"));
    		pds.getDriverProperties().put("driverClassName", properties.getProperty("persistence.datasource.driverClassName", "org.h2.Driver"));
    		pds.init();
    
    		//Note the persistence unit name
    		ProcessMain.emf = Persistence.createEntityManagerFactory("org.jbpm.example");
    		RuntimeEnvironmentBuilder builder = RuntimeEnvironmentBuilder.Factory.get()
    			.newDefaultBuilder().entityManagerFactory(emf)
    			.knowledgeBase(kbase);
    		return RuntimeManagerFactory.Factory.get()
    			.newSingletonRuntimeManager(builder.get(), "com.sample:example:1.0");
    	}
    
    }
  6. Add the src/main/resource/persistence.xml file with the following content. If you have a custom datasource, configure your custom persistence unit.

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.0"
                 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd
                          http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
    
      <persistence-unit name="org.jbpm.example" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jndi:/example</jta-data-source>
        <mapping-file>META-INF/Executor-orm.xml</mapping-file>
        <properties>
          <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
          <property name="hibernate.max_fetch_depth" value="3" />
          <property name="hibernate.hbm2ddl.auto" value="update" />
          <property name="hibernate.show_sql" value="false" />
    
          <!-- BZ 841786: AS7/EAP 6/Hib 4 uses new (sequence) generators which seem to cause problems -->
          <property name="hibernate.id.new_generator_mappings" value="false" />
          <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.BitronixJtaPlatform" />
        </properties>
      </persistence-unit>
    </persistence>
  7. When you execute the main class, the expected output is:

    [main] INFO org.jbpm.executor.impl.ExecutorImpl - Starting Executor Component ...
     	 - Thread Pool Size: 1
     	 - Interval: 3 SECONDS
     	 - Retries per Request: 3
    
    [main] WARN org.jbpm.executor.impl.ExecutorImpl - Disabling JMS support in executor because: unable to initialize JMS configuration for executor due to unable to find a bound object at name 'java:/JmsXA'
    Hello World from Business Command!

11.12.6. Using Job Executor in Business Central

AsyncWorkItemHandler accepts the following input parameters:

  • CommandClass: A fully-qualified class name (FQCN) of the command to be executed. Mandatory unless the handler is configured with a default command class.
  • Retries: The number of retries for the command execution. This parameter is optional.
  • RetryDelay: A single value or a comma separated list of time expressions used in case of multiple retries. For example: 5s, 2m, 4h. This parameter is optional.

    If you provide a comma separated list of time expressions and if the number of retry delays is smaller than number of retries, the executor uses the last available value from the list.

    If you provide a single time expression for retry delay, the retries will be equally spaced.

  • Delay: A start delay for jobs. The value is a time expression: 5s, 2m, or 4h. The delay is calculated from the current time. This parameter is optional.
  • AutoComplete: Allows to use the fire and forget execution style. Thus, the handler does not wait for job completion. The default value is false.
  • Priority: Priority for the job execution. The value is a range from 0 (the lowest) to 9 (the highest).

The following data are available inside of the command during execution:

  • businessKey: A String generated from the process instance ID and the work item ID in the following format: [processInstanceId]:[workItemId].
  • workItem: A work item instance that is being executed, including all its parameters.
  • processInstanceId: The process instance ID that triggered this work item execution.

To register the Asynchronous Work Item handler in Business Central:

  1. Implement the Command interface, for example:

    package docs.command;
    
    import org.kie.api.executor.Command;
    import org.kie.api.executor.CommandContext;
    import org.kie.api.executor.ExecutionResults;
    
    public class HelloWorldCommand implements Command {
    
    	public ExecutionResults execute(CommandContext commandContext) throws Exception {
    		System.out.println("Hello World from Business Command!");
    	    return new ExecutionResults();
    	}
    
    }

    Use the following pom.xml:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.docs</groupId>
      <artifactId>hello-commandimpl</artifactId>
      <version>1.0</version>
      <name>commandImpl</name>
      <description>Hello world command implementation</description>
    
      <dependencies>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>6.4.0.Final-redhat-8</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
    </project>

    See the Supported Component Versions of the Red Hat JBoss BPM Suite Installation Guide for the current version number. Also note that you must configure Maven to work with the Red Hat middleware repository. See Chapter 3, Apache Maven for further information.

  2. Build your Maven project, upload the JAR file to the Business Central, and add into your project dependencies. See the Registering Work Item handler in Business Central chapter for further information.
  3. In your project, define a custom Work Item Definition that will trigger your Command implementation:

    1. Click Work Item DefinitionsWork Definitions. The Work Item Definitions editor opens.
    2. Add your definition, specifying all parameters you want to use, for example:

      [
          "name" : "async",
          "displayName" : "Async Hello World!",
          "icon" : "defaultemailicon.gif",
          "parameters" : [
              "CommandClass" : new StringDataType()
              ]
      ]
    3. Click Save and Validate to ensure correctness of your Work Item Definition file.
  4. Click New ItemBusiness Process to create a new Business Process.
  5. On your canvas, click 3898 to open the Object Library pallet, expand Service Tasks and drag and drop the Work Item you created on the canvas, for example the Async Hello World! Service Task.
  6. Connect the Work Item with the start and end event.
  7. Click on the Work Item and click 3897 to open the Properties tab. Click the 1 data inputs, 0 data outputs value and click 6563 to open the Data I/O window.
  8. Set the CommandClass attribute to docs.command.HelloWorldCommand. Alternatively, if you used a different package, enter the fully-qualified class name of your implementation.
  9. Click Save to save the data input mappings.
  10. Click Save to save your process.
  11. Register AsyncWorkItemHandler in Business Central:

    1. Click Open Project Editor and navigate to the Deployment Descriptor for your project.
    2. Click Add under the Work Item handlers category.
    3. Set the first Value field to async.
    4. Set the second Value field to:

      new org.jbpm.executor.impl.wih.AsyncWorkItemHandler(org.jbpm.executor.ExecutorServiceFactory.newExecutorService(null))
    5. Set the resolver to mvel.
    6. Click Save and Validate to ensure correctness of your deployment descriptor.

You can now build, deploy, and start your process. If you followed the example above, you will see similar output in the in the command line:

09:46:03,473 INFO  [stdout] (Thread-637 (HornetQ-client-global-threads-1573025029)) Hello World from Business Command!
Executor Configuration

When you are not running the Executor Service in the embedded mode, you can use the following properties:

  1. org.kie.executor.disabled: true or false to enable or disable the executor.
  2. org.kie.executor.pool.size: an Integer that specifies the thread pool size for the executor. The default value is 1.
  3. org.kie.executor.retry.count: an Integer that specifies the default number of retries in case of an error executing a job. The default value is 3.
  4. org.kie.executor.interval: an Integer that specifies the time to wait between checking for waiting jobs. The default value is 3 seconds.
  5. org.kie.executor.timeunit: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, or DAYS. Specifies the unit for the interval property. The default is SECONDS.

11.12.7. Multiple Sessions and persistence

The simplest way to run multiple process instances is to run them in one knowledge session. However, it is possible to run multiple process instances in different knowledge sessions or in different technical threads.

When using multiple knowledge session with multiple processes and adding persistence, use a database that allows row-level as well as table-level locks: There could be a situation when there are 2 or more threads running, each within its own knowledge session instance. On each thread, a process is being started using the local knowledge session instance. In this use case, a race condition exists in which both thread A and thread B have coincidentally simultaneously finished a process instance. At this point, both thread A and B are committing changes to the database. If row-level locks are not possible, then the following situation can occur:

  • Thread A has a lock on the ProcessInstanceInfo table, having just committed a change to that table.
  • Thread A wants a lock on the SessionInfo table in order to commit a change.
  • Thread B has the opposite situation: It has a lock on the SessionInfo table, having just committed a change.
  • Thread B wants a lock on the ProcessInstanceInfo table, even though Thread A already has a lock on it.

This is a deadlock situation which the database and application are not be able to solve, unless row-level locks are possible and enabled in the database and tables used.

11.12.8. Asynchronous Events

In cases where several process instances from different process definitions are waiting for the same signal, they are generally executed sequentially in the same single thread. However, if one of those process instances throws a runtime exception, all the other process instances are affected, usually resulting in a rolled back transaction. To avoid this, Red Hat JBoss BPM Suite supports using asynchronous signals events for:

  • Throwing Intermediate Signal Events
  • End Events

From the Business Central, set the Data Input value of the throw event to async to automatically set the Executor Service on each ksession. This ensures that each process instance is signaled in a different transaction.

11.12.9. Technical exceptions

Technical exceptions occur when a technical component of a Process acts in an unexpected way. When using Java-based systems, this often results in a Java Exception. As these exceptions cannot be handled using BPMN2, it is important to handle them in expected ways.

The following types of code might throw exceptions:

  • Code present directly in the process definition
  • Code that is not part of the product executed during a Process
  • Code that interacts with a technical component outside of the Process Engine

This includes the following:

  • Code in Element properties, such as the Script property of a Script Task element or in the definitions of the interception actions, that is, the onEntry and onExit properties
  • Code in WorkItemHandlers associated with task and task-type nodes
Code in Element properties

Exceptions thrown by code defined in Element properties can cause the Process instance to fail in an unrecoverable way. Often, it is the code that starts the Process that will end up throwing the exception generated by a Process without returning a reference to the Process instance. Such code includes for example the onEntry and onExit properties, Script defined for the Script Task, etc.

Therefore, it is important to limit the scope of the code in these Elements so that is operates only over Process variables. Using a scriptTask to interact with a different technical component, such as a database or web service has significant risks because any exceptions thrown will corrupt or abort the Process instance.

To interact with other systems, use task Elements, serviceTask Elements and other task-type Elements. Do not use the scriptTask nodes for these purposes.

Note

If the script defined in a scriptTask causes the problem, the Process Engine usually throws the WorkflowRuntimeException with information on the Process (see Section 11.12.9.1.5, “Extracting information from WorkflowRuntimeException”).

Code in WorkItemHandlers

WorkItemHandlers are used when your Process interacts with other technical systems.

You can either build exception handling into your own WorkItemhandler implementations or wrap your implementation into the handler decorator classes (for examples and detailed information see Section 11.12.9.1.2, “Exception handling classes”). These classes include the logic that is executed when an exception is thrown during the execution or abortion of a work item:

SignallingTaskHandlerDecorator
catches the exception and signals it to the Process instance using a configurable event type when the executeWorkItem() or abortWorkItem methods of the original WorkItemHandler instance throw an exception. The exception thrown is passed as part of the event. This functionality can be also used to signal to an Event SubProcess defined in the Process definition.
LoggingTaskHandlerDecorator
logs error about any exceptions thrown by the executeWorkItem() and abortWorkItem() methods. It also saves any exceptions thrown to an internal list so that they can be retrieved later for inspection or further logging. The content and format of the message logged are configurable.

While the classes described above covers most cases involving exception handling as it catches any throwable objects, you might still want to write a custom WorkItemHandler that includes exception handling logic. In such a case, consider the following:

  • Does the implementation catch all exceptions the code could return?
  • Does the implementation complete or abort the work item after an exception has been caught or uses a mechanisms to retry the process later (in some cases, incomplete process instances might be acceptable)?
  • Does the implementation define any other actions that need to be taken when an exception is caught? Would it be beneficial to interact with other technical systems? Should a Sub-Process be triggered to handle the exception?
Important

If WorkItemManager signals that the work item has been completed or aborted, make sure the signal is sent after any signals to the Process instance were sent. Depending on how your Process definition, calling WorkItemManager.completeWorkItem() or WorkItemManager.abortWorkItem() triggers the completion of the Process instance as these methods trigger further execution of the Process execution flow.

11.12.9.1. Technical exception examples

11.12.9.1.1. Service Task handlers

The following example uses a Throwing Error Intermediate Event to throw an error. An Error Event Sub-Process then catches and handles the error.

When the Throwing Error Intermediate Event throws an error, the process instance is interrupted:

  1. Execution of the process instance stops: no other parts of the process are executed.
  2. The process instance finishes as ABORTED.

The process starts with a start event and continues to the Throw Exception service task. The task produces an exception, which is propagated as a signal object through the process instance and caught by the sub-process start event in the Exception Handler event sub-process. The workflow continues to the Handle Exception task and the process instance finishes with the sub-process end event.

Figure 11.2. Process with an exception handling Event Sub-Process

3389

The following XML is a representation of the process. It contains elements and IDs that are referenced in Section 11.12.9.1.2, “Exception handling classes”.

 <itemDefinition id="_stringItem" structureRef="java.lang.String" /> (1)
  <message id="_message" itemRef="_stringItem"/>  # (2)

  <interface id="_serviceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService">
    <operation id="_serviceOperation" name="throwException">
      <inMessageRef>_message</inMessageRef> (2)
    </operation>
  </interface>

  <error id="_exception" errorCode="code" structureRef="_exceptionItem"/> (3)

  <itemDefinition id="_exceptionItem" structureRef="org.kie.api.runtime.process.WorkItem"/> (4)
  <message id="_exceptionMessage" itemRef="_exceptionItem"/> (4)

  <interface id="_handlingServiceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService">
    <operation id="_handlingServiceOperation" name="handleException">
      <inMessageRef>_exceptionMessage</inMessageRef> (4)
    </operation>
  </interface>

  <process id="ProcessWithExceptionHandlingError" name="Service Process" isExecutable="true" processType="Private">
    <!-- properties -->
    <property id="serviceInputItem" itemSubjectRef="_stringItem"/> (1)
    <property id="exceptionInputItem" itemSubjectRef="_exceptionItem"/> (4)

    <!-- main process -->
    <startEvent id="_1" name="Start" />
    <serviceTask id="_2" name="Throw Exception" implementation="Other" operationRef="_serviceOperation">

    <!-- rest of the serviceTask element and process definition... -->

    <subProcess id="_X" name="Exception Handler" triggeredByEvent="true" >
      <startEvent id="_X-1" name="subStart">
        <dataOutput id="_X-1_Output" name="event"/>
        <dataOutputAssociation>
          <sourceRef>_X-1_Output</sourceRef>
          <targetRef>exceptionInputItem</targetRef> (4)
        </dataOutputAssociation>
        <errorEventDefinition id="_X-1_ED_1" errorRef="_exception" /> (3)
      </startEvent>

      <!-- rest of the subprocess definition... -->

    </subProcess>

  </process>
  1. This <itemDefinition> element defines a data structure that is used in the serviceInputItem property in the process.
  2. This <message> element (first reference) defines a message that has a String as its content, as defined by the <itemDefintion> element on line above. The <interface> element below it refers to it (second reference) in order to define what type of content the service (defined by the <interface>) expects.
  3. This <error> element (first reference) defines an error for use later in the process: an Event SubProcess is defined that is triggered by this error (second reference). The content of the error is defined by the <itemDefintion> element defined below the <error> element.
  4. This <itemDefintion> element (first reference) defines an item that contains a WorkItem instance. The <message> element (second reference) then defines a message that uses this item definition to define its content. The <interface> element below that refers to the <message> definition (third reference) in order to define the type of content that the service expects.

    In the process itself, a <property> element (fourth reference) is defined as having the content defined by the initial <itemDefintion>. This is helpful because it means that the Event SubProcess can then store the error it receives in that property (5th reference).

11.12.9.1.2. Exception handling classes

The BPMN process defined in Section 11.12.9.1.1, “Service Task handlers” contains two <serviceTask> activities. The org.jbpm.bpmn2.handler.ServiceTaskHandler class is the default task handler class used for <serviceTask> tasks. If you do not specify a Work Item Handler implementation for a <serviceTask> activity, the ServiceTaskHandler class is used.

The example below decorates the ServiceTaskHandler class with a SignallingTaskHandlerDecorator instance in order to define behavior when the ServiceTaskHandler class throws an exception.

In the example, the ServiceTaskHandler throws an exception because it calls the ExceptionService.throwException method, which throws an exception. (See the _handlingServiceInterface <interface> element in the BPMN2 XML schema.)

The example also configures which (error) event is sent to the process instance by the SignallingTaskHandlerDecorator instance. The SignallingTaskHandlerDecorator object does this when an exception is thrown in a task. In this example, because of the <error> definition with the error code code in the BPMN2 process, the signal is set to Error-code.

Rules for sending signals

When sending a signal of an event to the Process Engine, consider the rules for signaling process events:

  • Error events are signaled by sending an Error-ERRORCODE ATTRIBUTE VALUE value to the session.
  • Signal events are signaled by sending the name of the signal to the session.
  • If you wanted to send an error event to a Boundary Catch Error Event, the error type should be of the format: "Error-" + $AttachedNodeID + "-" + $ERROR_CODE. For example, Error-SubProcess_1-888 would be a valid error type.

    However, this is NOT a recommended practice because sending the signal this way bypasses parts of the boundary error event functionality and it relies on internal implementation details that might be changed in the future. For a way to programmatically trigger a boundary error event when an Exception is thrown in WorkItemHandler see this KnowledgeBase article.

Example 11.3. Using SignallingTaskHandlerDecorator

The ServiceTaskHandler calls the ExceptionService.throwException() method to throw an exception (refer to the _handlingServiceInterface interface element in the BPMN2).

The SignallingTaskHandlerDecorator that wraps the ServiceTaskHandler sends to the Process instance the error with the set error code.

import java.util.HashMap;
import java.util.Map;

import org.jbpm.bpmn2.handler.ServiceTaskHandler;
import org.jbpm.bpmn2.handler.SignallingTaskHandlerDecorator;
import org.jbpm.examples.exceptions.service.ExceptionService;
import org.kie.api.KieBase;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;

public class ExceptionHandlingErrorExample {

public static final void main(String[] args) {
runExample();
}

public static ProcessInstance runExample() {
KieSession ksession = createKieSession();

String eventType = "Error-code"; 1
SignallingTaskHandlerDecorator signallingTaskWrapper 2
= new SignallingTaskHandlerDecorator(ServiceTaskHandler.class, eventType);
signallingTaskWrapper.setWorkItemExceptionParameterName(ExceptionService.exceptionParameterName); 3
ksession.getWorkItemManager().registerWorkItemHandler("Service Task", signallingTaskWrapper);

Map<String, Object> params = new HashMap<String, Object>();
params.put("serviceInputItem", "Input to Original Service");
ProcessInstance processInstance = ksession.startProcess("ProcessWithExceptionHandlingError", params);
return processInstance;
}

private static KieSession createKieSession() {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource("exceptions/ExceptionHandlingWithError.bpmn2"), ResourceType.BPMN2);
KieBase kbase = kbuilder.newKnowledgeBase();
return kbase.newKieSession();
}
1
Definition of the Error-code event to be sent to the process instance when the wrapped WorkItemHandler implementation throws an exception.
2
Construction of the SignallingTaskHandlerDecorator class instance with the WorkItemHandler implementation and eventType as parameters: Note that a SignallingTaskHandlerDecorator class constructor that takes an instance of a WorkItemHandler implementation as its parameter is also available. This constructor is useful if the WorkItemHandler implementation does not allow a no-argument constructor.
3
Registering the WorkItemHandler with the session: When an exception is thrown by the wrapped WorkItemHandler, the SignallingTaskHandlerDecorator saves it as a parameter in the WorkItem instance with a parameter name configured in the SignallingTaskHandlerDecorator (see the code below for the ExceptionService).

For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies in chapter Dependency Management of the Red Hat JBoss BPM Suite Development Guide.

11.12.9.1.3. Exception service

In Section 11.12.9.1.1, “Service Task handlers”, the BPMN2 process definition defines the exception service using the ExceptionService class as follows:

<interface id="_handlingServiceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService">
<operation id="_handlingServiceOperation" name="handleException">

The exception service uses the ExceptionService class to provide the exception handling abilities. The class is implemented as follows:

import org.kie.api.runtime.process.WorkItem;
...
public class ExceptionService {

  public static String exceptionParameterName = "my.exception.parameter.name";
  public void handleException(WorkItem workItem) {
    System.out.println( "Handling exception caused by work item '" + workItem.getName() + "' (id: " + workItem.getId() + ")");
    Map<String, Object> params = workItem.getParameters();
    Throwable throwable = (Throwable) params.get(exceptionParameterName);
    throwable.printStackTrace();
  }
  public String throwException(String message) {
    throw new RuntimeException("Service failed with input: " + message );
  }
  public static void setExceptionParameterName(String exceptionParam) {
    exceptionParameterName = exceptionParam;
  }

}

For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies in chapter Dependency Management of the Red Hat JBoss BPM Suite Development Guide.

You can specify any Java class with the default or another no-argument constructor as the class to provide the exception service so that it is executed as part of a serviceTask.

11.12.9.1.4. Handling errors with Signals

In the example in Section 11.12.9.1.1, “Service Task handlers”, an Error event occurs during Process execution and the execution is interrupted immediately: no other Flows or Activities are executed.

However, you might want to complete the execution. In such case you can use a Signal event as the Process execution continues after the Signal is processed (that is, after the Signal Event SubProcess or another Activities that the Signal triggered, finish their execution). Also, the Process execution finished successfully, not in an aborted state, which is the case if an Error is used.

In the example process, we define the error element which is then used to throw the Error:

 <error id="_exception" errorCode="code" structureRef="_exceptionItem"/>

To use a Signal instead, do the following:

  1. Remove the line defining the error element and define a <signal> element:

     <signal id="exception-signal" structureRef="_exceptionItem"/>
  2. Change all references from the _exception value in the <error> XML tag to the exception-signal value of the <signal> XML tag.

    Change the <errorEventDefinition> element in the <startEvent>,

     <errorEventDefinition id="_X-1_ED_1" errorRef="_exception" />

    to a <signalEventDefinition>:

     <signalEventDefinition id="_X-1_ED_1" signalRef="exception-signal"/>
11.12.9.1.5. Extracting information from WorkflowRuntimeException

If a scripts in your Process definition may throw or threw an exception, you need to retrieve more information about the exception and related information.

If it is a scriptTask element that causes an exception, you can extract the information from the WorkflowRuntimeException as it is the wrapper of the scriptTask.

The WorkflowRuntimeException instance stores the information outlined in Table 11.5, “Information in WorkflowRuntimeException instances”. Values of all fields listed can be obtained using the standard get* methods.

Table 11.5. Information in WorkflowRuntimeException instances

Field nameTypeDescription

processInstanceId

long

The id of the ProcessInstance instance in which the exception occurred

Note that the ProcessInstance may not exist anymore or be available in the database if using persistence.

processId

String

The id of the process definition that was used to start the process (that is, "ExceptionScriptTask" in

ksession.startProcess("ExceptionScriptTask");

)

nodeId

long

The value of the (BPMN2) id attribute of the node that threw the exception

nodeName

String

The value of the (BPMN2) name attribute of the node that threw the exception

variables

Map<String, Object>

The map containing the variables in the process instance (experimental)

message

String

The short message with information on the exception

cause

Throwable

The original exception that was thrown

The following code illustrates how to extract extra information from a process instance that throws a WorkflowRuntimeException exception instance.

import org.jbpm.workflow.instance.WorkflowRuntimeException;
import org.kie.api.KieBase;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;

public class ScriptTaskExceptionExample {

 public static final void main(String[] args) {
  runExample();
 }

 public static void runExample() {
  KieSession ksession = createKieSession();
  Map < String, Object > params = new HashMap < String, Object > ();
  String varName = "var1";
  params.put(varName, "valueOne");
  try {
   ProcessInstance processInstance = ksession.startProcess("ExceptionScriptTask", params);
  } catch (WorkflowRuntimeException wfre) {
   String msg = "An exception happened in " + "process instance [" + wfre.getProcessInstanceId() + "] of process [" + wfre.getProcessId() + "] in node [id: " + wfre.getNodeId() + ", name: " + wfre.getNodeName() + "] and variable " + varName + " had the value [" + wfre.getVariables().get(varName) + "]";
   System.out.println(msg);
  }
 }
 private static KieSession createKieSession() {
  KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("exceptions/ScriptTaskException.bpmn2"), ResourceType.BPMN2);
  KieBase kbase = kbuilder.newKnowledgeBase();
  return kbase.newKieSession();
 }
}

Use the following Maven dependencies:

<dependencies>
  ...
  <dependency>
      <groupId>org.kie</groupId>
      <artifactId>kie-api</artifactId>
      <version>6.5.0.Final-redhat-2</version>
  </dependency>
  <dependency>
    <groupId>org.jbpm</groupId>
    <artifactId>jbpm-flow</artifactId>
    <version>6.5.0.Final-redhat-2</version>
  </dependency>
  <dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-internal</artifactId>
    <version>6.5.0.Final-redhat-2</version>
</dependency>
  ...
</dependencies>

For the current Maven artifact version, see chapter Supported Component Versions of the Red Hat JBoss BPM Suite Installation Guide.

11.13. Process Fluent API

11.13.1. Using the Process Fluent API to Create Business Process

While it is recommended to define processes using the graphical editor or the underlying XML, you can also create a business process using the Process API directly. The most important process model elements are defined in the packages org.jbpm.workflow.core and org.jbpm.workflow.core.node.

Red Hat JBoss BPM Suite provides you a fluent API that allows you to easily construct processes in a readable manner using factories. You can then validate the process that you were constructing manually.

11.13.2. Process Fluent API Example

Here is an example of a basic process with only a script task:

import org.kie.api.KieServices;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.ReleaseId;
import org.kie.api.io.Resource;
import org.jbpm.ruleflow.core.RuleFlowProcessFactory;
import org.jbpm.ruleflow.core.RuleFlowProcess;
import org.jbpm.bpmn2.xml.XmlBPMNProcessDumper;

...

RuleFlowProcessFactory factory = RuleFlowProcessFactory.createProcess("org.jbpm.HelloWorld");

factory
  // Header
  .name("HelloWorldProcess")
  .version("1.0")
  .packageName("org.jbpm")
  // Nodes
  .startNode(1).name("Start").done()
  .actionNode(2).name("Action")
  .action("java", "System.out.println(\"Hello World\");").done()
  .endNode(3).name("End").done()
  // Connections
  .connection(1, 2)
  .connection(2, 3);

RuleFlowProcess process = factory.validate().getProcess();
KieServices ks = KieServices.Factory.get();
KieFileSystem kfs = ks.newKieFileSystem();
Resource resource = ks.getResources().newByteArrayResource(
  XmlBPMNProcessDumper.INSTANCE.dump(process).getBytes());

resource.setSourcePath("helloworld.bpmn2");
kfs.write(resource);
ReleaseId releaseId = ks.newReleaseId("org.jbpm", "helloworld", "1.0");
kfs.generateAndWritePomXML(releaseId);
ks.newKieBuilder(kfs).buildAll();
ks.newKieContainer(releaseId).newKieSession().startProcess("org.jbpm.HelloWorld");

For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies.

In this example, we first call the static createProcess() method from the RuleFlowProcessFactory class. This method creates a new process and returns the RuleFlowProcessFactory that can be used to create the process.

A process consists of three parts:

  • Header: The header section comprises global elements such as the name of the process, imports, and variables.

    In the above example, the header contains the name and version of the process and the package name.

  • Nodes: The nodes section comprises all the different nodes that are part of the process.

    In the above example, nodes are added to the current process by calling the startNode(), actionNode() and endNode() methods. These methods return a specific NodeFactory that allows you to set the properties of that node. Once you have finished configuring that specific node, the done() method returns you to the current RuleFlowProcessFactory so you can add more nodes, if necessary.

  • Connections: The connections section links the nodes to create a flow chart.

    In the above example, once you add all the nodes, you must connect them by creating connections between them. This can be done by calling the method connection, which links the nodes.

    Finally, you can validate the generated process by calling the validate() method and retrieve the created RuleFlowProcess object.

11.14. Testing Business Processes

Although business processes should not contain any implementation details and should be as high-level as possible, they have a life cycle similar to other development artefacts. Because business processes can be updated dynamically and modifying them can cause errors, testing a process definition is a part of creating business processes.

Process unit tests ensure that the process behaves as expected in specific use cases. For example, an output can be tested based on a particular input. To simplify unit testing, Red Hat JBoss BPM Suite includes the org.jbpm.test.JbpmJUnitBaseTestCase class. The class provides the following:

  • Helper methods for creating a new knowledge base and a session for one or more given processes, with the possibility of using persistence. For more information, see Section 11.14.2, “Configuring Persistence”.
  • Assert statements to check:

    • The state of a process instance. A process instance can be active, completed, or aborted.
    • The node instances that are currently active.
    • Which nodes have been triggered. This enables to inspect the followed path.
    • The value of different variables.

Example 11.4. JUnit Test of hello.bpmn Process

The process below contains a start event, a script task, and an end event. The example JUnit test creates a new session, starts the hello.bpmn process, verifies whether the process instance has completed successfully, and whether the StartProcess, Hello, and EndProcess nodes were executed.

1211
import org.jbpm.test.JbpmJUnitBaseTestCase;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.process.ProcessInstance;

public class ProcessPersistenceTest extends JbpmJUnitBaseTestCase {
  public ProcessPersistenceTest() {
    // Set up a data source and enable persistence:
    super(true, true);
  }

  @Test
  public void testProcess() {
    // Create a runtime manager with the hello.bpmn process:
    createRuntimeManager("hello.bpmn");
    // Get a runtime engine:
    RuntimeEngine runtimeEngine = getRuntimeEngine();
    // Get an access to an instance of a session:
    KieSession ksession = runtimeEngine.getKieSession();
    // Start the process:
    ProcessInstance processInstance = ksession.startProcess("com.sample.bpmn.hello");
    // Check whether the process instance has completed successfully:
    assertProcessInstanceCompleted(processInstance.getId());
    // Check whether the given nodes were executed:
    assertNodeTriggered(processInstance.getId(), "StartProcess", "Hello", "EndProcess");
  }
}

For a list of Maven dependencies, see section Testing Dependencies.

11.14.1. JbpmJUnitBaseTestCase

The JbpmJUnitBaseTestCase class acts as a base test case class that you can use for Red Hat JBoss BPM Suite-related tests. It provides four usage areas:

  • JUnit life cycle methods
  • Knowledge base and knowledge session management methods
  • Assertions
  • Helper methods

For the complete list of all methods, see the JbpmJUnitBaseTestCase Javadoc.

Table 11.6. JUnit Life Cycle Methods

MethodDescription

setUp

This method is annotated as @Before. It configures a data source and EntityManagerFactory and deletes the session ID of a Singleton.

tearDown

This method is annotated as @After. It removes history, closes EntityManagerFactory and a data source, and disposes RuntimeManager and RuntimeEngines.

To create a session, create RuntimeManager and RuntimeEngine first. Use the following methods to create and dispose of RuntimeManager:

Table 11.7. RuntimeManager Management Methods

MethodDescription

createRuntimeManager(String... process)

Creates one RuntimeManager with the Singleton strategy for one test. Each process is added to the knowledge base.

createRuntimeManager(Strategy strategy, String identifier, String... process)

Creates RuntimeManager with the given strategy and with all processes added to the knowledge base. The identifier parameter specifies a concrete RuntimeManager.

createRuntimeManager(Map<String, ResourceType> resources)

Creates RuntimeManager with the Singleton strategy and with all resources, such as processes and rules, added to the knowledge base.

createRuntimeManager(Map<String, ResourceType> resources, String identifier)

Creates RuntimeManager with the Singleton strategy and with all resources, such as processes and rules, added to the knowledge base. The identifier parameter specifies a concrete RuntimeManager.

createRuntimeManager(Strategy strategy, Map<String, ResourceType> resources)

Creates one RuntimeManager with the given strategy for one test, with all resources, such as processes and rules, added to the knowledge base.

createRuntimeManager(Strategy strategy, Map<String, ResourceType> resources, String identifier)

Creates one RuntimeManager with the given strategy for one test, with all resources, such as processes and rules, added to the knowledge base. The identifier parameter specifies a concrete RuntimeManager.

createRuntimeManager(Strategy strategy, Map<String, ResourceType> resources, RuntimeEnvironment environment, String identifier)

Creates the lowest level of RuntimeManager without any particular configuration, which enables you to configure each of its parts manually. Specify the following parameters:

  • strategy: one of the supported strategies.
  • resources: all the resources, such as rules and processes, that are added to the knowledge base.
  • environment: the runtime environment used for creating RuntimeManager.
  • identifier: the unique identifier of RuntimeManager.

disposeRuntimeManager

Disposes of the currently active RuntimeManager in the test scope.

Table 11.8. RuntimeEngine Management Methods

MethodDescription

getRuntimeEngine()

Returns a new RuntimeEngine built from the manager of a test case. The method uses the EmptyContext context suitable for the Singleton and Per Request strategies.

getRuntimeEngine(Context<?> context)

Returns a new RuntimeEngine built from the manager of a test case. The context parameter specifies an instance of the context used to create RuntimeEngine. To maintain the same session for process instances, use ProcessInstanceIdContext.

To test the current state of various assets, the following methods are available:

Table 11.9. Assertions

AssertionDescription

assertProcessInstanceActive(long processInstanceId, KieSession ksession)

Checks whether a process instance with the given ID is active.

assertProcessInstanceCompleted(long processInstanceId)

Checks whether a process instance with the given ID has completed successfully. Use this method in case session persistence is enabled. Otherwise, use assertProcessInstanceNotActive(long processInstanceId, KieSession ksession).

assertProcessInstanceAborted(long processInstanceId)

Checks whether a process instance with the given ID was aborted. Use this method in case session persistence is enabled. Otherwise, use assertProcessInstanceNotActive(long processInstanceId, KieSession ksession).

assertNodeExists(ProcessInstance process, String... nodeNames)

Checks whether the given nodes exist within the specified process.

assertNodeActive(long processInstanceId, KieSession ksession, String... name)

Checks whether a process instance with the given ID contains at least one active node with the specified node names.

assertNodeTriggered(long processInstanceId, String... nodeNames)

For each given node name, checks whether a node instance was triggered during the execution of the specified process instance.

getVariableValue(String name, long processInstanceId, KieSession ksession)

Retrieves the value of the given variable from the specified process instance.

assertProcessVarExists(ProcessInstance process, String... processVarNames)

Checks whether the given process contains the specified process variables.

assertProcessNameEquals(ProcessInstance process, String name)

Checks whether the given name matches the name of the specified process.

assertVersionEquals(ProcessInstance process, String version)

Checks whether the given process version matches the version of the specified process.

Table 11.10. Helper Methods

MethodDescription

setupPoolingDataSource

Configures a data source.

getDs

Returns the currently configured data source.

getEmf

Returns the currently configured EntityManagerFactory.

getTestWorkItemHandler

Returns a test work item handler that can be registered in addition to what is registered by default.

clearHistory

Clears a history log.

JbpmJUnitBaseTestCase supports all the predefined RuntimeManager strategies as part of the unit testing. Specify which strategy should be used whenever creating a runtime manager as part of a single test. The following example uses the PerProcessInstance strategy:

import java.util.List;

import org.jbpm.test.JbpmJUnitBaseTestCase;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.task.TaskService;
import org.kie.api.task.model.TaskSummary;
import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProcessHumanTaskTest extends JbpmJUnitBaseTestCase {
  private static final Logger logger = LoggerFactory.getLogger(ProcessHumanTaskTest.class);
  public ProcessHumanTaskTest() {
    super(true, false);
  }

  @Test
  public void testProcessProcessInstanceStrategy() {
    RuntimeManager manager = createRuntimeManager
      (Strategy.PROCESS_INSTANCE, "manager", "humantask.bpmn");
    RuntimeEngine runtimeEngine = getRuntimeEngine(ProcessInstanceIdContext.get());
    KieSession ksession = runtimeEngine.getKieSession();
    TaskService taskService = runtimeEngine.getTaskService();

    int ksessionID = ksession.getId();
    ProcessInstance processInstance = ksession.startProcess("com.sample.bpmn.hello");

    assertProcessInstanceActive(processInstance.getId(), ksession);
    assertNodeTriggered(processInstance.getId(), "Start", "Task 1");

    manager.disposeRuntimeEngine(runtimeEngine);

    runtimeEngine = getRuntimeEngine(ProcessInstanceIdContext.get(processInstance.getId()));

    ksession = runtimeEngine.getKieSession();
    taskService = runtimeEngine.getTaskService();

    assertEquals(ksessionID, ksession.getId());

    // Let John execute Task 1:
    List<TaskSummary> list = taskService.getTasksAssignedAsPotentialOwner("john", "en-UK");
    TaskSummary task = list.get(0);
    logger.info("John is executing task {}", task.getName());

    taskService.start(task.getId(), "john");
    taskService.complete(task.getId(), "john", null);

    assertNodeTriggered(processInstance.getId(), "Task 2");

    // Let Mary execute Task 2:
    list = taskService.getTasksAssignedAsPotentialOwner("mary", "en-UK");
    task = list.get(0);

    logger.info("Mary is executing task {}", task.getName());

    taskService.start(task.getId(), "mary");
    taskService.complete(task.getId(), "mary", null);

    assertNodeTriggered(processInstance.getId(), "End");
    assertProcessInstanceCompleted(processInstance.getId());
  }
}

For a list of Maven dependencies, see section Testing Dependencies.

11.14.2. Configuring Persistence

Persistence allows to store states of all process instances in a database and uses a history log to check assertions related to the execution history. When persistence is not used, process instances are stored in the memory and an in-memory logger is used for history transactions.

By default, the performed JUnit tests do not use persistence. To change this behavior, invoke a constructor of the superclass in one of the following ways:

  • default: This option uses a no-argument constructor; it does not initialize a data source and does not configure session persistence. This option is usually used for in-memory process management without any human task interaction.
  • super(boolean, boolean): This option allows to explicitly configure persistence and a data source. This is the most common way of bootstrapping test cases for Red Hat JBoss BPM Suite. Use

    • super(true, false) for execution with in-memory process management and human tasks persistence.
    • super(true, true) for execution with persistent process management and human tasks persistence.
  • super(boolean, boolean, string): This option is very similar to the last one, however, it enables you to use a different persistence unit name than the default one, which is org.jbpm.persistence.jpa.
import org.jbpm.test.JbpmJUnitBaseTestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProcessHumanTaskTest extends JbpmJUnitBaseTestCase {

  private static final Logger logger = LoggerFactory
    .getLogger(ProcessHumanTaskTest.class);

  public ProcessHumanTaskTest() {
    // Persistence will not be used for the
    // process engine but will be used for human tasks:
    super(true, false);
  }
}

11.14.3. Testing Integration with External Services

Business processes often include the invocation of external services. Unit testing of a business process allows you to register test handlers that verify whether the specific services are requested correctly, and provide test responses for those services as well.

To test the interactions with external services, use the TestWorkItemHandler handler, which is provided by default. TestWorkItemHandler can be registered to collect all the work items of a given type and contains data related to a task. A work item represents one unit of work, such as sending one specific email or invoking one specific service. This test handler then checks whether a specific work item was actually requested during an execution of a process, and whether the data associcated with the work item are correct.

Example 11.5. Testing Email Task

This example shows how to test a process that sends an email and whether an exception is raised if the email cannot be sent. This is accomplished by notifying the engine about the email delivery failure.

1212

Further notes describing the following source code are below.

// Not used in the snippet below but your class must extend JbpmJUnitBaseTestCase.
import org.jbpm.test.JbpmJUnitBaseTestCase;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.runtime.process.WorkItem;

...

public void testProcess2() {

  // Create a runtime manager with a single process:
  createRuntimeManager("sample-process.bpmn");
  // Get a runtime engine:
  RuntimeEngine runtimeEngine = getRuntimeEngine();
  // Get an access to an instance of a session:
  KieSession ksession = runtimeEngine.getKieSession();
  // Register a test handler for "Email":
  TestWorkItemHandler testHandler = getTestWorkItemHandler();
  ksession.getWorkItemManager().registerWorkItemHandler("Email", testHandler);

  // Start the process:
  ProcessInstance processInstance = ksession.startProcess("com.sample.bpmn.hello2");

  assertProcessInstanceActive(processInstance.getId(), ksession);
  assertNodeTriggered(processInstance.getId(), "StartProcess", "Email");

  // Check whether the email has been requested:
  WorkItem workItem = testHandler.getWorkItem();

  assertNotNull(workItem);
  assertEquals("Email", workItem.getName());
  assertEquals("me@mail.com", workItem.getParameter("From"));
  assertEquals("you@mail.com", workItem.getParameter("To"));

  // Simulate a failure of sending the email:
  ksession.getWorkItemManager().abortWorkItem(workItem.getId());

  assertProcessInstanceAborted(processInstance.getId());
  assertNodeTriggered(processInstance.getId(), "Gateway", "Failed", "Error");
}

The unit test uses a test handler that is executed when an email is requested and allows you to test the data related to the email, such as its sender and recipient. Once the abortWorkItem() method notifies the engine about the email delivery failure, the unit test verifies that the process handles such case by generating an error and logging the action. In this case, the process instance is eventually aborted.



[1] Business Process Model and Notation (BPMN). Version 2.0, OMG Document Number: formal/2011-01-03 http://www.omg.org/spec/BPMN/2.0
[2] Business Process Model and Notation (BPMN). Version 2.0, OMG Document Number: formal/2011-01-03 http://www.omg.org/spec/BPMN/2.0
[3] Business Process Model and Notation (BPMN). Version 2.0, OMG Document Number: formal/2011-01-03 http://www.omg.org/spec/BPMN/2.0