Chapter 5. Advanced Process Modeling

5.1. Process modeling options

You can create processes in multiple ways:

Using one of the graphical editors
You can use two delivered graphical editors. Process Designer is available through Business Central and Eclipse Process Designer. See Red Hat JBoss BRMS User Guide for more information on how to use the editors.
Using an XML editor
You can use any XML or text editor to create a process specification using the BPMN2 XML schema.
Using the Process API
You can use the Red Hat JBoss BPM Suite core API directly. The most important process model elements are defined in the packages org.jbpm.workflow.core and org.jbpm.workflow.core.node.

5.1.1. Process modeling using XML

The BPMN2 file must meet the BPMN2 schema. The file content comprises the following parts:

XML prolog
The XML prolog consists of the XML declaration and DTD declaration.
The process element
The process element defines process attributes and contains definitions of the process elements (nodes and connections).
BPMN diagram definition
The BPMNDiagram element contains definitions for visualization of the Process elements in the Process Diagram.

Example 5.1. BPMN2 example file

<?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"Rule Task
             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>

5.1.2. Process modeling using API

To be able to execute processes from within your application, you need to perform the following in your code:

  1. Create a Runtime Manager in your Execution Server.

    • Singleton: allows sequential execution of multiple instances in the one session
    • PerProcessInstance: allows you to create multiple process instances; every instance is created within its own session.
    • PerRequestSession: every external interaction with a process instances causes that the process session finishes and the process instance is re-created in a new session.
  2. Get a runtime context and create a session in it.
  3. Start a Process from the underlying Knowledge Base.
  4. Close the Runtime Manager.

Example 5.2. Process instantiation in a session of Per Process Instance Runtime Manager

import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.manager.RuntimeManagerFactory.Factory;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.KieSession;
...
RuntimeManager manager =
    RuntimeManagerFactory.Factory.get()
        .newPerProcessInstanceRuntimeManager(environment);

RuntimeEngine runtime =
    manager.getRuntimeEngine(
        ProcessInstanceIdContext.get());

KieSession ksession = runtime.getKieSession();
// do something here, e.g.
ksession.startProcess(“org.jbpm.hello”);

manager.disposeRuntimeEngine(engine);
manager.close();

5.1.3. Process update

5.1.3.1. Process update

When updating a Process definition, the new Process definition must define an increased version number and an update policy: the update policy defines how to handle the running Process instances of the older Process definition. You can decide to apply on them one of the following strategies:

  • Abort: any running Process instances are aborted. If necessary, you can have the Process instance restarted using the new Process definition.
  • Transfer: any running Process instances are migrated to the new process definition: once the instance has been migrated successfully, it will continue its execution based on the updated process logic.

Note that the older version of the Process definition remains in the repository as well as in the respective sessions. Therefore, the new process should have a different ID, though the name can remain the same, and you can use the version parameter to show when a Process is updated (the version parameter is just a String and is not validated).

Example 5.3. Process abort update

import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieSessionConfiguration;

// build kbase with the replace-version-1.bpmn process
        KieBase kbase = KieServices.Factory.get().newKieSessionConfiguration();
        kbase.addKnowledgePackages(getProcessPackages("replace-version-1.bpmn"));

        KieSession ksession = kbase.newStatefulKnowledgeSession();
        try {
            // start a replace-version-1.bpmn process instance
            ksession.startProcess("com.sample.process", Collections.<String, Object>singletonMap("name", "process1"));

            // add the replace-version-2.bpmn process and start its instance
            kbase.addKnowledgePackages(getProcessPackages("replace-version-2.bpmn"));
            ksession.startProcess("com.sample.process", Collections.<String, Object>singletonMap("name", "process2"));

            // signal all processes in the session to continue (both instances finish)
            ksession.signalEvent("continue", null);
        } finally {
            ksession.dispose();
        }

5.1.4. Multi-threading

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, for example. From a functional standpoint, the original process splits in two processes that are executed in a parallel fashion.

The Process engine supports logical multi-threading; for example, in Processes that include a parallel Gateway. The logical multi-threading is implemented using one technical thread: A Process that includes logical multi-threading is executed in one technical thread. Avoiding technical multi-threading prevents further implementation complexity as multiple technical threads of one Process instance need to communicate their state information to each other. While it might seem that technical multi-threading would bring significant performance benefits, the extra logic needed to make sure the threads can work together well may cancel out these benefits.

In general, the execution engine also executes actions in serial. For example, when the engine encounters a Script Task, it synchronously executes the script and waits for it to complete before continuing execution. Similarly, when a Process encounters a Parallel Gateway, it sequentially triggers each of the outgoing Flows. This is possible since execution is almost always instantaneous. Similarly, action scripts in a Process are also executed synchronously, and the engine waits for them to finish before continuing execution. That means, that if the execution needs to wait, for example, a Thread.sleep() call is being execution, the engine does not continue any execution — it remains blocked during the wait period.

5.1.4.1. Asynchronous execution

If a work item execution of a Task does not execute instantaneously, but needs to wait, for example, to receive a response from an external system, the service handler must handle your service asynchronously. The asynchronous handler only invokes the service and notifies the engine once the results are available. In the mean time, the process engine continues the execution of the process.

A typical example of a service that requires asynchronous invocation is a Human Task. The engine is not to wait until a human actor responds to the request but continue and process the result of the Task when it becomes available. The human task handler creates a new task on the task list of the assigned actor and the engine is then allowed to continue the execution: The handler notifies the engine asynchronously when the user completes the task.

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

Example 5.4. Example of asynchronous service handling in Java

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 {



  public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {

    new Thread(new Runnable() {

      public void run() {

        // The main thread with the parent element execution
        }
    }).start();
  }

  public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {

  }
}

It is recommended to have your handler contact a service that executes the business operation, instead of performing the task, as failure of the business operation will not affect your process. This approach also provides greater flexibility when developing and reusing services.

For example, your human task handler can invoke the human task service to add a task to the service. To implement an asynchronous handler, you usually have to do an asynchronous invocation of the handler. This usually depends on the technology you use to do the communication and might be an asynchronous invocation of a web service, or sending a JMS message to the external service.

The Red Hat JBoss BPM Suite Job Executor

Red Hat JBoss BPM Suite, from 6.1 version, implements a special component, the Job Executor, to accommodate asynchronous execution. The Executor provides an environment independent from the process runtime environment for instantiation of asynchronous executions defined in a Command object.

In particular, the JBoss BPM Suite Job Executor provides advanced handling of common asynchronous execution operations, like error handling, retry, cancellation and history logging. You can continue to delegate work to a separate thread, as described above, in a custom implementation of WorkItemHandler and use the JBoss BPM Suite Job Executor to handle these operations for you.

The JBoss BPM Suite Job Executor works on instances of the Command interface. This interface implements a single method execute() that accepts the CommandContext data transfer object with serializable data. The Command instance should only contain the business logic to be executed and have no reference to the underlying process engine or runtime related data. At the minimum, the CommandContext should provide the businessKey - the unique identifier of the caller and callback - the fully qualified classname (FQCN) of the CommandCallback instance to be called on command completion. Of course, when executed as part of a process (WorkItemHandler), you will need to provide additional data like the workItem, processInstanceId and deploymentId.

The result for the execution is returned via an instance of the ExecutionResults class. As with the input data, the data provided by this class must also be serializable.

Business Central provides a way for you to view the current jobs within the system; to schedule them, cancel them, or view their history. Go to DeployJobs to access this screen. You can even create a new job by clicking on the New Job button.

The Asynchronous WorkItemHandler

Red Hat JBoss BPM Suite provides an out of the box asynchronous WorkItemHandler that is backed by the JBoss BPM Suite Job Executor. All the features that the JBoss BPM Suite Executor delivers are available for background execution within a process instance. There are two ways to configure this AsyncWorkItemHandler class:

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

The Red Hat JBoss BPM Suite Job Executor allows you to specify the number of retries in case of errors while running a job. In addition to that, it enables you to configure retry delay on each job. You can customize the retry interval depending on the time required for you to correct the problems that caused the job to fail.

To customize the retry interval, you can either set the retryDelay parameter in the CommandContext or the RetryDelay parameter of asynchronous work item.

You can set the retry delay as:

  • A single time expression. For example, 5m, 2h.
  • A comma separated list of time expressions that must be used for subsequent retries. For example, 10s,10m,1h, and 1d.

If you are providing a comma separated list of time expressions and if the number of retry delays is smaller than number of retries, the executor will use the last available value from the list. In case you provide a single time expression for retry delay, the retries will be equally spaced.

Configuring the JBoss BPM Suite Executor

The JBoss BPM Suite executor can be configured to allow fine tuning of its environment via the following system properties:

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

The JBoss BPM Suite Job Executor can also be used as an embedded service in customer application as it has been moved to the public kie-api. See Red Hat JBoss BPMS Development Guide for more information on how to use Job Executor in embedded mode.

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

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

5.1.5. Technical exceptions

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

The following types of code might throw exceptions:

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

This includes the following:

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

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

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

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

Note

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

Code in WorkItemHandlers

WorkItemHandlers are used when your Process interacts with other technical systems (for more information on WorkItemHandlers see Section 4.15.1, “Work Item Definition”).

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 5.1.5.1.2, “Exception handling classes”). These classes include the logic that is executed when an exception is thrown during the execution or abortion of a work item:

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

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

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

If WorkItemManager to 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.

5.1.5.1. Technical exception examples

5.1.5.1.1. Service Task handlers

The example involves a Throwing Error Intermediate Event caught by an Error Event Sub-Process.

When the Throwing Error Intermediate Event throws the Error, the Process instance is interrupted:

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

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

The Process starts with the 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 subStart event in the Exception Handler Event Sub-Process. The workflow continues to the Handle Exception Service Task and the Process instance finishes with the subEnd event.
Parts of the BPMN2 definition of the example Process relevant for exception handling
<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> 3
</operation>
</interface>

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

<itemDefinition id="_exceptionItem" structureRef="org.kie.api.runtime.process.WorkItem"/> 5
<message id="_exceptionMessage" itemRef="_exceptionItem"/> 6

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

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

<!-- 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> 10
</dataOutputAssociation>
<errorEventDefinition id="_X-1_ED_1" errorRef="_exception" /> 11
</startEvent>

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

</subProcess>

</process>
1 8
The itemDefinition element defines a data structure used in the serviceInputItem property of the Process.
2 3
The message element (1st reference) defines a message that contains the String defined by the itemDefinition element on the line above. The interface element below then refers to the itemDefinition element (2nd reference) in order to define what type of content the service (defined by the interface) expects.
4 11
The error element (1st reference) defines an error that is used to trigger the Event SubProcess of the Process. The content of the error is defined by the itemDefintion element defined below the error element.
5 6 7 9 10
This itemDefinition element (1st reference) defines an item that contains a WorkItem instance. The message element (2nd reference) then defines a message that uses this item definition to define its content. The interface element below that refers to the message definition (3rd reference) in order to define the type of content that the service expects. In the Process element itself, a property element (4th reference) that contains the initial itemDefinition. This allows the Event SubProcess to store the error it receives in that property (5th reference).
5.1.5.1.2. Exception handling classes

The serviceTask tasks use the org.jbpm.bpmn2.handler.ServiceTaskHandler class as its task handler class unless the serviceTask defines a custom WorkItemHandler implementation.

To catch and handle any technical exceptions a WorkItemHandler of a task might throw, wrap or decorate the handler class with a SignallingTaskHandlerDecorator instance.

Rules for sending signals

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

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

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

Example 5.5. Using SignallingTaskHandlerDecorator

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

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

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

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

public class ExceptionHandlingErrorExample {

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

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

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

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

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

In Section 5.1.5.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;
  }

}

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.

5.1.5.1.4. Handling errors with Signals

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

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

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

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

To use a Signal instead, do the following:

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

     <signal id="exception-signal" structureRef="_exceptionItem"/>
  2. Make sure to change all references from the “_exception`” <error> to the “exception-signal” <signal>.

    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"/>
5.1.5.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 5.1, “Information in WorkflowRuntimeException instances”. Values of all fields listed can be obtained using the standard get* methods.

Table 5.1. Information in WorkflowRuntimeException instances

Field nameTypeDescription

processInstanceId

long

The id of the ProcessInstance instance in which the exception occurred

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

processId

String

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

ksession.startProcess("ExceptionScriptTask");

)

nodeId

long

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

nodeName

String

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

variables

Map<String, Object>

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

message

String

The short message with information on the exception

cause

Throwable

The original exception that was thrown

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

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

public class ScriptTaskExceptionExample {

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

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