- Java Delegates can be attached to a BPMN Service Task.
- Delegate Variable Mapping can be attached to a Call Activity.
- Execution Listeners can be attached to any event within the normal token flow, e.g., starting a process instance or entering an activity.
- Task Listeners can be attached to events within the user task lifecycle, e.g., creation or completion of a user task.
Java Delegate
To implement a class that can be called during process execution, this class needs to implement theorg.camunda.bpm.engine.delegate.JavaDelegate
interface and provide the required logic in the execute
method. When process execution arrives at this particular step, it
will execute this logic defined in that method and leave the activity
in the default BPMN 2.0 way.
As an example let’s create a Java class that can be used to change a
process variable String to uppercase. This class needs to implement
the org.camunda.bpm.engine.delegate.JavaDelegate
interface, which requires us to implement the execute(DelegateExecution)
method. It’s this operation that will be called by the engine and
which needs to contain the business logic. Process instance
information such as process variables and other information can be accessed and
manipulated through the DelegateExecution interface (click on the link for a detailed Javadoc of its operations).
Note!Each time a delegation class referencing activity is executed, a separate instance of this class will be created. This means that each time an activity is executed there will be used another instance of the class to call
execute(DelegateExecution).camunda:class ) are NOT instantiated during deployment.
Only when a process execution arrives at the point in the process where the class is used for the
first time, an instance of that class will be created. If the class cannot be found,
a ProcessEngineException will be thrown. The reason for this is that the environment (and
more specifically the classpath) when you are deploying is often different than the actual runtime
environment.
Activity Behavior
Instead of writing a Java Delegate, it is also possible to provide a class that implements theorg.camunda.bpm.engine.impl.pvm.delegate.ActivityBehavior
interface. Implementations then have access to the more powerful ActivityExecution that for example also allows to influence the control flow of the process. However, note that this is not a very good practice and should be avoided as much as possible. So, it is advised to only use the ActivityBehavior interface for advanced use cases and if you know exactly what you’re doing.
Field Injection
It is possible to inject values into the fields of the delegated classes. The following types of injection are supported:- Fixed string values
- Expressions
firstName has setter setFirstName(...)).
If no setter is available for that field, the value of private
member will be set on the delegate (but using private fields is not recommended - see warning below).
Regardless of the type of value declared in the process-definition, the type of the
setter/private field on the injection target should always be org.camunda.bpm.engine.delegate.Expression.
The following code snippet shows how to inject a constant value into a field.
Field Injection is supported when using the class or delegateExpression attribute. Note that we need
to declare a extensionElements XML element before the actual field injection
declarations, which is a requirement of the BPMN 2.0 XML Schema.
ToUpperCaseFieldInjected has a field
text which is of type org.camunda.bpm.engine.delegate.Expression.
When calling text.getValue(execution), the configured string value
Hello World will be returned.
Alternatively, for longs texts (e.g., an inline e-mail) the camunda:string sub element can be
used:
org.camunda.bpm.engine.delegate.Expression
which can be evaluated/invoked using the DelegateExecution
passed in the execute method.
DelegateExecution.
Note!The injection happens each time the service task is called since a separate instance of the class will be created. When the fields are altered by your code, the values will be re-injected when the activity is executed next time.
Delegate Variable Mapping
To implement a class that delegates the input and output variable mapping for a call activity, this class needs to implement theorg.camunda.bpm.engine.delegate.DelegateVariableMapping
interface. The implementation must provide the methods mapInputVariables(DelegateExecution, VariableMap) and mapOutputVariables(DelegateExecution, VariableScope).
See the following example:
mapInputVariables method is called before the call activity is executed, to map the input variables.
The input variables should be put into the given variables map.
The mapOutputVariables method is called after the call activity was executed, to map the output variables.
The output variables can be directly set into the caller execution.
The behavior of the class loading is similar to the class loading on Java Delegates.
Execution Listener
Execution listeners allow you to execute external Java code or evaluate an expression when certain events occur during process execution. The events that can be captured are:- Start and end of a process instance.
- Taking a transition.
- Start and end of an activity.
- Start and end of a gateway.
- Start and end of intermediate events.
- Ending a start event or starting an end event.
org.camunda.bpm.engine.delegate.ExecutionListener interface. When the event occurs (in this case end event) the method notify(DelegateExecution execution) is called.
org.camunda.bpm.engine.delegate.JavaDelegate interface. These delegation classes can then be reused in other constructs, such as a delegation for a service task.
The second execution listener is called when the transition is taken. Note that the listener element
doesn’t define an event, since only take events are fired on transitions. Values in the event
attribute are ignored when a listener is defined on a transition. Also it contains a
camunda:script child element which defines a script which
will be executed as execution listener. Alternatively it is possible to specify the script source
code as external resources (see the documentation about script sources of script
tasks).
The last execution listener is called when activity secondTask ends. Instead of using the class on the listener declaration, a expression is defined instead which is evaluated/invoked when the event is fired.
Note!The
end event triggers under any circumstance in which the activity ends. That includes successful completion of the activity’s business logic, but also interruption and cancellation, for example when an attached boundary event triggers.Task Listener
A task listener is used to execute custom Java logic or an expression upon the occurrence of a certain task-related event. It can only be added in the process definition as a child element of a user task. Note that this also must happen as a child of the BPMN 2.0 extensionElements and in the ASEE Flow namespace, since a task listener is a construct specifically for the ASEE Flow engine.Task Listener Event Lifecycle
The execution of Task Listeners is dependent on the order of firing of the following task-related events: The create event fires when the task has been created as a transient object with all task properties. No other task-related event will be fired before the create event. The event allows us to inspect all properties of the task when we receive it in the create listener. The update event occurs when a task property (e.g. assignee, owner, priority, etc.) on an already created task is changed. This includes attributes of a task (e.g. assignee, owner, priority, etc.), as well as dependent entities (e.g. attachments, comments, task-local variables). Note that the initialization of a task does not fire an update event (the task is being created). This also means that the update event will always occur after a create event has already occurred. The assignment event specifically tracks the changes of the Task’sassignee property. The event
may be fired on two occasions:
- When a task with an
assigneeexplicitly defined in the process definition has been created. In this case, the assignment event will be fired after the create event. - When an already created task is assigned, i.e. the Task’s
assigneeproperty is changed. In this case, the assignment event will follow the update event since changing theassigneeproperty results in an updated task.
timeout event may occur after a Task has been
created, and before it has been completed.
The complete event occurs when the task is successfully completed and just before the task
is deleted from the runtime data. A successful execution of a task’s complete Task Listeners
results in an end of the task event lifecycle.
The delete event occurs just before the task is deleted from the runtime data, because of:
- An interrupting Boundary Event;
- An interrupting Event Subprocess;
- A Process Instance deletion;
- A BPMN Error thrown inside a Task Listener.
Task Event Chaining
The descriptions above lay out the order in which Task Events are fired. However, this order may be disrupted under the following conditions:- When calling
Task#complete()inside a Task Listener, the complete event will be fired right away. The related Task Listeners will be immediately invoked, after which the remaining Task Listeners for the previous event will be processed. - By using the
TaskServicemethods inside a Task Listener, which may cause the firing of additional Task Events. As with the complete event mentioned above, these Task Events will immediately invoke their related Listeners, after which the remaining Task Listeners will be processed. However, it should be noted that the chain of events triggered inside the Task Listener, by the invocation of theTaskServicemethod, will be in the previously described order. - By throwing a BPMN Error event inside a Task Listener (e.g. a complete event Task Listener). This would cancel the Task and cause a delete event to be fired.
Defining a Task Listener
A task listener supports the following attributes:- event (required): the type of task event on which the task listener will be invoked. Possible events are: create, assignment, update, complete, delete and timeout; Note that the timeout event requires a timerEventDefinition child element in the task listener and will only be fired if the Job Executor is enabled.
-
class: the delegation class that must be called. This class must implement the
org.camunda.bpm.engine.impl.pvm.delegate.TaskListenerinterface.
-
expression: (cannot be used together with the class attribute): specifies an expression that will be executed when the event happens. It is possible to pass the DelegateTask object and the name of the event (using task.eventName) to the called object as parameters.
-
delegateExpression: allows to specify an expression that resolves to an object implementing the TaskListener interface, similar to a service task.
-
id: a unique identifier of the listener within the scope of the user task, only required if the
eventis set totimeout.
class, expression and delegateExpression attributes, a
camunda:script child element can be used to specify a script as task listener.
An external script resource can also be declared with the resource attribute of the
camunda:script element (see the documentation about script sources of script
tasks).
event type timeout
in order to define the associated timer. The specified delegate will be called by the Job Executor when the timer is due.
The execution of the user task will not be interrupted by this.
Field Injection on Listener
When using listeners configured with the class attribute, Field Injection can be applied. This is exactly the same mechanism as described for Java Delegates, which contains an overview of the possibilities provided by field injection. The fragment below shows a simple example process with an execution listener with fields injected:ExampleFieldInjectedExecutionListener concatenates the 2 injected fields (one fixed and the other dynamic) and stores this in the process variable var.
Access Process Engine Services
It is possible to access the public API services (RuntimeService, TaskService, RepositoryService …) from the Delegation Code. The following is an example showing
how to access the TaskService from a JavaDelegate implementation.
Throw BPMN Errors from Delegation Code
It is possible to throwBpmnError from delegation code (Java Delegate, Execution and Task Listeners). This is done by using a provided Java exception class from within your Java code (e.g., in the JavaDelegate):
Throw BPMN Errors from Listeners
When implementing an error catch event, keep in mind that theBpmnError will be caught when they are thrown in normal flow of the following listeners:
- start and end execution listeners on activity, gateway, and intermediate events
- take execution listeners on transitions
- create, assign, and complete task listeners
BpmnError will not be caught for the following listeners:
- start and end process listeners
- delete task listeners
- listeners invoked outside of the normal flow:
- a process modification is performed which trigger subprocess scope initialization and some of its listeners throws an error
- a process instance deletion invokes an end listener throwing an error
- a listener is triggered due to interrupting boundary event execution, e.g message correlation on subprocess invokes end listeners throwing an error
Note!Throwing a
BpmnError in the delegation code behaves like modelling an error end event. See the reference guide about the details on the behavior, especially the error boundary event. If no error boundary event is found on the scope, the execution is ended.Set Business Key from Delegation Code
The option to set a new value of business key to already running process instance is shown in the example below:Exception codes
You can throw a[ProcessEngineException](https://docs.camunda.org/javadoc/camunda-bpm-platform/7.23/org/camunda/bpm/engine/ProcessEngineException.html)
from your delegation code and define your custom error code by passing it to the constructor or by
calling ProcessEngineException#setCode.
Also, you can create a custom exception class that extends the ProcessEngineException:
ProcessEngineException#getCode when calling ASEE Flow Java API or by evaluating the
code property in the response of an erroneous REST API call.
If you don’t set any code, the engine assigns 0, which a custom or built-in error code provider can override.
Also, you can register your custom exception code provider
to assign error codes to exceptions you cannot control via your Delegation Code.
Heads-up!
- A custom error code you define via delegation code has precedence over a custom error code provided by a Custom Code Provider.
- If your custom error code violates the reserved code range, it will be
overridden with
0unless you disable the built-in code provider.