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 attributes Supported elements Extension attributes Extension elements BPMNDiagram, itemDefinition, signal, process, relationship*
- process
Supported attributes Supported elements Extension attributes Extension elements processType, isExecutable, name, id
property, laneSet, flowElement
packageName, adHoc, version
import, global
- sequenceFlow
Supported attributes Supported elements Extension attributes Extension elements sourceRef, targetRef, isImmediate, name, id
conditionExpression
priority
- interface
Supported attributes Supported elements Extension attributes Extension elements name, id
operation
- operation
Supported attributes Supported elements Extension attributes Extension elements name, id
inMessageRef
- laneSet
Supported attributes Supported elements Extension attributes Extension elements lane
- lane
Supported attributes Supported elements Extension attributes Extension elements name, id
flowNodeRef
- import
Supported attributes Supported elements Extension attributes Extension elements name
- global
Supported attributes Supported elements Extension attributes Extension elements identifier, type
* Used for extension elements for BPMN2, such as simulation data.
BPMN 2.0 Supported Elements and Attributes (Events)
- startEvent
Supported attributes Supported elements Extension attributes Extension elements name, id
dataOutput, dataOutputAssociation, outputSet, eventDefinition
x, y, width, height
- endEvent
Supported attributes Supported elements Extension attributes Extension elements name, id
dataInput, dataInputAssociation, inputSet, eventDefinition
x, y, width, height
- intermediateCatchEvent
Supported attributes Supported elements Extension attributes Extension elements name, id
dataOutput, dataOutputAssociation, outputSet, eventDefinition
x, y, width, height
- intermediateThrowEvent
Supported attributes Supported elements Extension attributes Extension elements name, id
dataInput, dataInputAssociation, inputSet, eventDefinition
x, y, width, height
- boundaryEvent
Supported attributes Supported elements Extension attributes Extension elements cancelActivity, attachedToRef, name, id
eventDefinition
x, y, width, height
- terminateEventDefinition
Supported attributes Supported elements Extension attributes Extension elements - compensateEventDefinition
Supported attributes Supported elements Extension attributes Extension elements activityRef
documentation, extensionElements
- conditionalEventDefinition
Supported attributes Supported elements Extension attributes Extension elements condition
- errorEventDefinition
Supported attributes Supported elements Extension attributes Extension elements errorRef
- error
Supported attributes Supported elements Extension attributes Extension elements errorCode, id
- escalationEventDefinition
Supported attributes Supported elements Extension attributes Extension elements escalationRef
- escalation
Supported attributes Supported elements Extension attributes Extension elements escalationCode, id
- messageEventDefinition
Supported attributes Supported elements Extension attributes Extension elements messageRef
- message
Supported attributes Supported elements Extension attributes Extension elements itemRef, id
- signalEventDefinition
Supported attributes Supported elements Extension attributes Extension elements signalRef
- timerEventDefinition
Supported attributes Supported elements Extension attributes Extension elements timeCycle, timeDuration
BPMN 2.0 Supported Elements and Attributes (Activities)
- task
Supported attributes Supported elements Extension attributes Extension elements name, id
ioSpecification, dataInputAssociation, dataOutputAssociation
taskName, x, y, width, height
- scriptTask
Supported attributes Supported elements Extension attributes Extension elements scriptFormat, name, id
script
x, y, width, height
- script
Supported attributes Supported elements Extension attributes Extension elements text[mixed content]
- userTask
Supported attributes Supported elements Extension attributes Extension elements name, id
ioSpecification, dataInputAssociation, dataOutputAssociation, resourceRole
x, y, width, height
onEntry-script, onExit-script
- potentialOwner
Supported attributes Supported elements Extension attributes Extension elements resourceAssignmentExpression
- resourceAssignmentExpression
Supported attributes Supported elements Extension attributes Extension elements expression
- businessRuleTask
Supported attributes Supported elements Extension attributes Extension elements name, id
x, y, width, height, ruleFlowGroup
onEntry-script, onExit-script
- manualTask
Supported attributes Supported elements Extension attributes Extension elements name, id
x, y, width, height
onEntry-script, onExit-script
- sendTask
Supported attributes Supported elements Extension attributes Extension elements messageRef, name, id
ioSpecification, dataInputAssociation
x, y, width, height
onEntry-script, onExit-script
- receiveTask
Supported attributes Supported elements Extension attributes Extension elements messageRef, name, id
ioSpecification, dataOutputAssociation
x, y, width, height
onEntry-script, onExit-script
- serviceTask
Supported attributes Supported elements Extension attributes Extension elements operationRef, name, id
ioSpecification, dataInputAssociation, dataOutputAssociation
x, y, width, height
onEntry-script, onExit-script
- subProcess
Supported attributes Supported elements Extension attributes Extension elements name, id
flowElement, property, loopCharacteristics
x, y, width, height
- adHocSubProcess
Supported attributes Supported elements Extension attributes Extension elements cancelRemainingInstances, name, id
completionCondition, flowElement, property
x, y, width, height
- callActivity
Supported attributes Supported elements Extension attributes Extension elements calledElement, name, id
ioSpecification, dataInputAssociation, dataOutputAssociation
x, y, width, height, waitForCompletion, independent
onEntry-script, onExit-script
- multiInstanceLoopCharacteristics
Supported attributes Supported elements Extension attributes Extension elements loopDataInputRef, inputDataItem
- onEntry-script
Supported attributes Supported elements Extension attributes Extension elements scriptFormat
script
- onExit-script
Supported attributes Supported elements Extension attributes Extension elements scriptFormat
script
BPMN 2.0 Supported Elements and Attributes (Gateways)
- parallelGateway
Supported attributes Supported elements Extension attributes Extension elements gatewayDirection, name, id
x, y, width, height
- eventBasedGateway
Supported attributes Supported elements Extension attributes Extension elements gatewayDirection, name, id
x, y, width, height
- exclusiveGateway
Supported attributes Supported elements Extension attributes Extension elements default, gatewayDirection, name, id
x, y, width, height
- inclusiveGateway
Supported attributes Supported elements Extension attributes Extension elements default, gatewayDirection, name, id
x, y, width, height
BPMN 2.0 Supported Elements and Attributes (Data)
- property
Supported attributes Supported elements Extension attributes Extension elements itemSubjectRef, id
- dataObject
Supported attributes Supported elements Extension attributes Extension elements itemSubjectRef, id
- itemDefinition
Supported attributes Supported elements Extension attributes Extension elements structureRef, id
- signal
Supported attributes Supported elements Extension attributes Extension elements name, id
- ioSpecification
Supported attributes Supported elements Extension attributes Extension elements dataInput, dataOutput, inputSet, outputSet
- dataInput
Supported attributes Supported elements Extension attributes Extension elements name, id
- dataInputAssociation
Supported attributes Supported elements Extension attributes Extension elements sourceRef, targetRef, assignment
- dataOutput
Supported attributes Supported elements Extension attributes Extension elements name, id
- dataOutputAssociation
Supported attributes Supported elements Extension attributes Extension elements sourceRef, targetRef, assignment
- inputSet
Supported attributes Supported elements Extension attributes Extension elements dataInputRefs
- outputSet
Supported attributes Supported elements Extension attributes Extension elements dataOutputRefs
- assignment
Supported attributes Supported elements Extension attributes Extension elements from, to
- formalExpression
Supported attributes Supported elements Extension attributes Extension elements language
text[mixed content]
BPMN 2.0 Supported Elements and Attributes (BPMNDI)
- BPMNDiagram
Supported attributes Supported elements Extension attributes Extension elements BPMNPlane
- BPMNPlane
Supported attributes Supported elements Extension attributes Extension elements bpmnElement
BPMNEdge, BPMNShape
- BPMNShape
Supported attributes Supported elements Extension attributes Extension elements bpmnElement
Bounds
- BPMNEdge
Supported attributes Supported elements Extension attributes Extension elements bpmnElement
waypoint
- Bounds
Supported attributes Supported elements Extension attributes Extension elements x, y, width, height
- waypoint
Supported attributes Supported elements Extension attributes Extension 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
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.
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:
Task | Icon | Description |
---|---|---|
User |
|
Use the
|
Send |
|
Use the
|
Receive |
|
Use the
|
Manual |
|
Use the
|
Service |
|
Use the
|
Business Rule |
|
Use the
|
Script |
|
Use the
|
None |
|
A |
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:
Subprocess | Icon | Description |
---|---|---|
Reusable |
|
Use the
The |
Multiple Instances |
|
Use the
When the engine reaches a
A |
Embedded |
|
Use the
When you expand an
An |
Ad-Hoc |
|
Use the 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 |
Event |
|
Use the
The
An |
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);
WarningDo 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 parametery
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:
Event | Icon | Description |
---|---|---|
None |
|
Use the
|
Message |
|
Use the
|
Timer |
|
Use the
|
Escalation |
|
Use the
|
Conditional |
|
Use the
|
Error |
|
Use the
|
Compensation |
|
Use the
|
Signal |
|
Use the
|
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:
Event | Icon | Description |
---|---|---|
None |
|
Use the |
Message |
|
Use the |
Escalation |
|
Use the |
Error |
| 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 |
|
Use the |
Compensation |
|
Use the |
Signal |
|
Use the |
Terminate |
|
Use the |
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 theMessage
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 theTimer
intermediate event to delay the workflow execution until a specified point or duration. ATimer
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 theEscalation
catching intermediate event in your process to consume an Escalation object. AnEscalation
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 theConditional
intermediate event to execute a workflow when a specific business Boolean condition that it defines, evaluates to true. When placed in the process workflow, aConditional
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 theCompensation
intermediate event to handle compensation in case of partially failed operations. ACompensation
intermediate event is a boundary event that is attached to an activity in a transaction subprocess that may finish with aCompensation
end event or aCancel
end event. TheCompensation
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 theSignal
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 theMessage
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 theEscalation
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 anEscalation
start event or anEscalation
intermediate catch event, which is looking for this specific escalation object. -
Signal
: Use theSignal
throwing intermediate events to produces a signal object. Once it creates a signal object, the process execution continues. The signal object is consumed by aSignal
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.
ImportantMake 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 ExceptionMake 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 theOnEntry
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.
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
totrue
.
- 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:
- Update your Red Hat JBoss BPM Suite to version 6.4.2.
Set the property
org.jbpm.ejb.timer.tx
totrue
.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 theTaskName
property, create a data input calledCommandClass
and assign the fully-qualified class name of thisCommandClass
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 theCommandClass
at registration time instead of requiring it at design time, as with the first approach. But this means that an additional CDI bean that implementsWorkItemHandlerProducer
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 theTaskName
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:
-
Implement the
Command
interface. -
Transfer business data from the process engine to your
Command
implementation. - Configure the Job Executor.
-
Register the
AsyncWorkItemHandler
handler, which uses the Job Executor. - 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 theCommandCallback
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:
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();
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:
In your jBPM project, add the
src/main/resources/META-INF/drools.rulebase.conf
file with the following content:drools.workDefinitions = WorkDefinitions.wid
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" ], ]
Add the following BPMN diagram in the
src/main/resources
directory:In your diagram, create an
org.kie.api.executor.ExecutionResults
variable and map it to the Output variable of the asynchronous work item.Create a
Command
implementation insrc/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(); } }
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"); } }
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>
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
, or4h
. 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 isfalse
. -
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:
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.
- 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.
In your project, define a custom Work Item Definition that will trigger your
Command
implementation:-
Click Work Item Definitions
Work Definitions. The Work Item Definitions editor opens. 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() ] ]
- Click Save and Validate to ensure correctness of your Work Item Definition file.
-
Click Work Item Definitions
-
Click New Item
Business Process to create a new Business Process. -
On your canvas, click
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. - Connect the Work Item with the start and end event.
- Click on the Work Item and click to open the Properties tab. Click the 1 data inputs, 0 data outputs value and click to open the Data I/O window.
-
Set the
CommandClass
attribute todocs.command.HelloWorldCommand
. Alternatively, if you used a different package, enter the fully-qualified class name of your implementation. - Click Save to save the data input mappings.
- Click Save to save your process.
Register
AsyncWorkItemHandler
in Business Central:- Click Open Project Editor and navigate to the Deployment Descriptor for your project.
- Click Add under the Work Item handlers category.
-
Set the first
Value
field toasync
. Set the second
Value
field to:new org.jbpm.executor.impl.wih.AsyncWorkItemHandler(org.jbpm.executor.ExecutorServiceFactory.newExecutorService(null))
-
Set the resolver to
mvel
. - 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:
-
org.kie.executor.disabled
:true
orfalse
to enable or disable the executor. -
org.kie.executor.pool.size
: an Integer that specifies the thread pool size for the executor. The default value is 1. -
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. -
org.kie.executor.interval
: an Integer that specifies the time to wait between checking for waiting jobs. The default value is 3 seconds. -
org.kie.executor.timeunit
:NANOSECONDS
,MICROSECONDS
,MILLISECONDS
,SECONDS
,MINUTES
,HOURS
, orDAYS
. Specifies the unit for the interval property. The default isSECONDS
.
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, theonEntry
andonExit
properties -
Code in
WorkItemHandlers
associated withtask
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.
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()
orabortWorkItem
methods of the originalWorkItemHandler
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()
andabortWorkItem()
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?
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:
- Execution of the process instance stops: no other parts of the process are executed.
- 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
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>
-
This
<itemDefinition>
element defines a data structure that is used in theserviceInputItem
property in the process. -
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. -
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. 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
.
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 wrappedWorkItemHandler
implementation throws an exception. - 2
- Construction of the
SignallingTaskHandlerDecorator
class instance with theWorkItemHandler
implementation andeventType
as parameters: Note that aSignallingTaskHandlerDecorator
class constructor that takes an instance of aWorkItemHandler
implementation as its parameter is also available. This constructor is useful if theWorkItemHandler
implementation does not allow a no-argument constructor. - 3
- Registering the
WorkItemHandler
with the session: When an exception is thrown by the wrappedWorkItemHandler
, theSignallingTaskHandlerDecorator
saves it as a parameter in theWorkItem
instance with a parameter name configured in theSignallingTaskHandlerDecorator
(see the code below for theExceptionService
).
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:
Remove the line defining the
error
element and define a<signal>
element:<signal id="exception-signal" structureRef="_exceptionItem"/>
Change all references from the
_exception
value in the<error>
XML tag to theexception-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.
Field name | Type | Description |
---|---|---|
|
|
The id of the
Note that the |
|
| The id of the process definition that was used to start the process (that is, "ExceptionScriptTask" in ksession.startProcess("ExceptionScriptTask"); ) |
|
| The value of the (BPMN2) id attribute of the node that threw the exception |
|
| The value of the (BPMN2) name attribute of the node that threw the exception |
|
| The map containing the variables in the process instance (experimental) |
|
| The short message with information on the exception |
|
| 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()
andendNode()
methods. These methods return a specificNodeFactory
that allows you to set the properties of that node. Once you have finished configuring that specific node, thedone()
method returns you to the currentRuleFlowProcessFactory
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 createdRuleFlowProcess
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.
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.
Method | Description |
---|---|
|
This method is annotated as |
|
This method is annotated as |
To create a session, create RuntimeManager
and RuntimeEngine
first. Use the following methods to create and dispose of RuntimeManager
:
Method | Description |
---|---|
|
Creates one |
|
Creates |
|
Creates |
|
Creates |
|
Creates one |
|
Creates one |
|
Creates the lowest level of
|
|
Disposes of the currently active |
Method | Description |
---|---|
|
Returns a new |
|
Returns a new |
To test the current state of various assets, the following methods are available:
Assertion | Description |
---|---|
| Checks whether a process instance with the given ID is active. |
|
Checks whether a process instance with the given ID has completed successfully. Use this method in case session persistence is enabled. Otherwise, use |
|
Checks whether a process instance with the given ID was aborted. Use this method in case session persistence is enabled. Otherwise, use |
| Checks whether the given nodes exist within the specified process. |
| Checks whether a process instance with the given ID contains at least one active node with the specified node names. |
| For each given node name, checks whether a node instance was triggered during the execution of the specified process instance. |
| Retrieves the value of the given variable from the specified process instance. |
| Checks whether the given process contains the specified process variables. |
| Checks whether the given name matches the name of the specified process. |
| Checks whether the given process version matches the version of the specified process. |
Method | Description |
---|---|
| Configures a data source. |
| Returns the currently configured data source. |
|
Returns the currently configured |
| Returns a test work item handler that can be registered in addition to what is registered by default. |
| 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 isorg.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.
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.