Service Task
Overview
A Service Task is a specific type of task that represents an automated activity within a process. It is designed to invoke Java code, external services, APIs, or automated applications, to complete its function.
A service task is visualized as a rounded rectangle with a small gear icon in the top-left corner.
Depending on implementation, the service task may have different XML representation. See examples in the corresponding task type descriptions.
Properties
A service task has the following properties:
Setting the task properties:
-
First, you have to set general task properties.
-
Then, define a Task type and set the required parameters depending on the selected type.
-
Set up fail retry or leave it default (blank).
-
Create Execution listeners if they are necessary. See the Listeners section.
-
Decide whether you need extension properties.
-
If you need to create multi-instances, see the Multi-Instance section.
-
About the Async flag, see details in the Transactions section.
Actually, it is recommended always to set Async on (true). In some cases, it can affect performance. |
Types of Service Tasks
Parameter Task type defines the way service task is implemented. There are the following types of service tasks in Jmix BPM:
Let’s overview them briefly:
- Spring bean
-
In this case, specified method of the selected bean will be executed when the process reaches the task. Process variables or fixed values can be passed as method parameters, the result can be stored in a result variable.
- Java Delegate Class
-
In this case, the service task is implemented as JavaDelegate class, and its execute() method will be invoked. Also, it supports a Fields injection mechanism.
- Expression
-
It is a general way to call any method or evaluate inline expression.
- Delegate expression
-
Actually, it isn’t expression, it is a Spring bean that implements JavaDelegate interface. So it has features of both.
Spring Bean Service Task
Jmix BPM allows to invoke from the service task a Spring bean method and provide parameter values for it. This approach is the most recommended for using in service tasks. That’s why it is selected as a default task type.
You can create a new bean right from here by clicking the 'Plus' button:
Next, enter the bean name:
And you’ll be automatically switched to the code editor, where you can write required methods, for example:
@Component(value = "smpl_OrderStatusBean")
public class OrderStatusBean {
public Integer setStatus(String orderId, String status) {
// set status, returns quantity of items
return quantity;
}
}
Also, bean name and methods are selected from drop-down lists:
After the method is selected, a panel for entering method argument values is displayed:
The BPMN Inspector builds an expression for bean method invocation, thai isn’t editable. In the case of method from the screenshot above, the expression will be:
${smpl_OrderStatusBean.setStatus(OrderId,'Sent')}
Pay attention to the is var check box. It makes sense mostly for string parameters. If the checkbox is not selected, then the argument value will be written to the resulting expression in apostrophes. If the checkbox is selected, no apostrophes will be added and a variable with a provided name will be passed to the method.
-
${smpl_MyBean.someMethod('description')}
— this expression will use the string valuedescription
. -
${smpl_MyBean.someMethod(description)}
— this expression will use the value of the variable nameddescription
.
Result Variable
If the selected method returns any value, the Result variable field appears. You can put here one of the existing process variables or create a new one just entering its name.
Care about types when using existing variables.
If the result type differs from existing, a new process variable with the same name will be created.
If there was a |
The Result variable has a Use local scope checkbox.
When set to true
, this parameter ensures that the result variable created by the service task is scoped locally to the execution context of the task.
This means that the variable will only be accessible within the current execution and will not be propagated to the parent execution or process instance.
This setting helps in isolating the variable to the specific execution of the service task. If multiple instances of the same service task are running concurrently, each instance will have its own local variable, preventing interference between them.
Here you can see how all the Spring bean service task parameters are represented in XML:
<serviceTask id="set-status-service-task" name="Set order status"
flowable:async="true" (1)
flowable:expression="${smpl_OrderStatusBean.setStatus(orderId,'Sent')}" (2)
flowable:resultVariable="quantity" (3)
flowable:useLocalScopeForResultVariable="true" (4)
jmix:taskType="springBean" jmix:beanName="smpl_OrderStatusBean"> (5)
<extensionElements>
<jmix:springBean beanName="smpl_OrderStatusBean"
methodName="setStatus"> (6)
<jmix:methodParam name="orderId" type="java.lang.String"
isVariable="true">orderId</jmix:methodParam> (7)
<jmix:methodParam name="status" type="java.lang.String"
isVariable="false">Sent</jmix:methodParam> (8)
</jmix:springBean>
</extensionElements>
</serviceTask>
1 | — Async flag, by default it is 'false' and omitted. |
2 | — Generated expression, apostrophe symbols are substituted with ' . |
3 | — Result variable. |
4 | — Local scope flag, by default it is 'false' and omitted. |
5 | — Task type |
6 | — Spring bean name and method defined. |
7 | — Parameter passes as process variable. |
8 | — Parameter passed as direct value. |
Process variable “execution”
Spring bean doesn’t see a process context. But in many cases it is required. For example, to get access to process variables and the current task properties.
There is an embedded process variable named “execution” of the type DelegateExecution
that can be used as a Spring bean method parameter.
Create such a method, for example:
@Component("MyProcessBean")
public class MyProcessBean {
public void mySampleMethod(DelegateExecution execution) { (1)
String currentActivityId = execution.getCurrentActivityId();
Set<String> variableNames = execution.getVariableNames();
// etc.
}
}
1 | — execution parameter |
Then set this method in your service task:
Java Delegate Service Task
In this case, business logic will be executed by a class implementing org.flowable.engine.delegate.JavaDelegate
interface with execute() method.
The method receives execution
object as a parameter, so you’ll have access to process context, including all process variables.
If you select JavaDelegate class option in the Task type combo box, you can create a new class from here by clicking the 'Plus' button:
Type the name of a new Java Delegate class in the dialog window:
And you’ll be automatically switched to the code editor, where you can write the logic you need. For example, let’s implement the class creating a process variable with random value:
public class RandomIndexJavaDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
long randomIndex = new Random().nextLong(100L);
execution.setVariable("randomIndex", randomIndex);
}
}
To specify a class called during process execution,
the fully qualified class name needs to be provided by the flowable:class
attribute.
<serviceTask id="Activity_java_delegate" name="Java delegate"
flowable:class="com.company.jmixbpmtraining.delegate.RandomIndexJavaDelegate" (1)
jmix:taskType="javaDelegateClass"> (2)
<extensionElements />
</serviceTask>
1 | — Specifying Java Delegate class. |
2 | — Defining task type. |
Instantiating a Java Delegate Class
The classes that are used in service tasks of the Java Delegate type are NOT instantiated during deployment. When process engine achieves the task during execution for the first time, it creates an instance of the JavaDelegate class.
There will be only one instance of the Java class created for the serviceTask on which it is defined. If more than one service tasks within a process refer to the same Java Delegate class, for each will be created a separate instance. All process instances share the corresponding class instance for the task.
This means that the class must not use any member variables and must be thread-safe, as it can be executed simultaneously from different threads. This also may affect Fields injection.
Delegate Expression Service Task
A delegate expression is a powerful feature used in service tasks that allows for the dynamic resolution of a Java object at runtime.
For example, an expression like ${myServiceBean}
would resolve to a Spring bean named myServiceBean
.
In a Spring context, delegate expressions can reference Spring beans directly, enabling seamless integration with the Spring framework. This allows for dependency injection and the use of Spring’s features within the delegate implementation.
To be used in delegate expressions your JavaDelegate
class must be announced as a Spring bean by @Component
annotation.
In this case, it combines the features of both types — Spring bean and Java Delegate class:
@Component
public class MyDelegateExpression implements JavaDelegate {
// Class fields and injections
@Override
public void execute(DelegateExecution execution) {
// Required logic
}
}
In result, you have access to Spring context and process context from within this class. To invoke it, use the Delegate expression task type. For example:
Here you can create a new delegate expression class:
Or select one of the existing classes from a pull-down list:
To specify a class called during process execution, it is possible to use an expression that resolves to an object.
In XML, an attribute flowable:delegateExpression
is used for this purpose:
<serviceTask id="delegate-expression"
name="Delegate expression task"
flowable:delegateExpression="${smpl_MyDelegateExpression}"
jmix:taskType="delegateExpression">
</serviceTask>
Expression Service Task
Expression is the most general way to invoke Java logic. You can call a Spring bean method from expression:
Fail Retry
About the fail retry concept, see Fail Retry.
Configuring
To set a Fail retry parameters, find the corresponding property in the BPMN Inspector panel:
The value must be time cycle expression follows ISO 8601 standard, just like timer event expressions.
The example R5/PT7M
as above makes the job executor retry the job 5 times and wait 7 minutes between before each retry.
XML Representation
Fail retry parameter is presented by the flowable:failedJobRetryTimeCycle element
.
Here is a sample usage:
<serviceTask id="failingServiceTask"
flowable:async="true"
flowable:class="org.flowable.engine.test.jobexecutor.RetryFailingDelegate">
<extensionElements>
<flowable:failedJobRetryTimeCycle>R5/PT7M</flowable:failedJobRetryTimeCycle> (1)
</extensionElements>
</serviceTask>
1 | — Fail retry parameter. |
Process engine, in its default configuration, reruns a job three times if there’s any exception in the execution of a job. |
Field Injections
The field injections is a Flowable mechanism of passing parameter in Java Delegate class as fixed string values or expressions resolved in strings. It can be used with the following task types:
-
Java Delegate class
And, if a called object is Java Delegate class, in
-
Delegate expression
-
Expression
Injected field must always be of org.flowable.engine.delegate.Expression
type.
When the injected expression is resolved, it can be cast to the appropriate target type.
You can’t pass entities or other objects via Field injection. Actually, expression is resolved in |
How to inject fields:
-
Create fields definition in your
JavaDelegate
class:private Expression messageField; private Expression quantityField;
-
On the diagram, select the service task and create fields with the same name as you defined in code:
-
Then enter field values, as expressions or strings:
If you need to pass a numeric values, use expression like shown above, for example
${3}
. If you write just 3, this will be interpreted asString
object "3" and cannot be cast toInteger
type. -
At runtime, the process engine resolves expression and passes result strings in Java Delegate class.
-
In Java Delegate class, there must be a code getting values from the fields and casing them to desired types:
String message = (String) messageField.getValue(execution); Integer quantity = (Integer) quantityField.getValue(execution);
Field Injection and Thread Safety
In general, using service tasks with Java delegates and field injections are thread-safe. However, there are a few situations where thread-safety is not guaranteed, depending on the setup or environment Flowable is running in.
- Java delegate class task type
-
In this case, using field injection is always thread safe. For each service task that references a certain class, a new instance will be instantiated and fields will be injected once when the instance is created. Reusing the same class multiple times in different tasks or process definitions is no problem.
Keep in mind that different process instances use the same instance of Java Delegate class referred to a task. It’s possible to imagine that one process instance affects another, but this is very unlikely.
- Spring bean service and expression task type
-
Technically for Flowable, a Spring bean service task is represented by
flowable:expression
attribute.When using the
flowable:expression
attribute, use of field injection is unnecessary. Parameters are passed via method calls and these are always thread-safe.Strictly speaking, you can do field injection, but you shouldn’t.
- Delegate expression service task
-
When using the
flowable:delegateExpression
attribute, the thread-safety of the delegate instance will depend on how the expression is resolved. If the delegate expression is reused in various tasks or process definitions, and the expression always returns the same instance, using field injection is not thread-safe.Two service tasks can use the same delegate expression, but inject different values for the
Expression
field. If the expression resolves to the same instance, there can be race conditions in concurrent scenarios when it comes to injecting the field someField when the processes are executed.The easiest solution to solve this is to either:
-
Rewrite the Java Delegate to use an expression or Spring bean and pass the required data via a method arguments.
-
Return a new instance of the delegate class each time the delegate expression is resolved. For example, when using Spring, this means that the scope of the bean must be set to prototype (such as by adding the
@Scope(SCOPE_PROTOTYPE)
annotation to the delegate class).
-
Example
public class UpperCaseJavaDelegate implements JavaDelegate {
private Expression messageField;
private Expression quantityField;
@Override
public void execute(DelegateExecution execution) {
String message = (String) messageField.getValue(execution);
Integer quantity = (Integer) quantityField.getValue(execution);
String upperCaseMessage = message.toUpperCase();
for (int i = 0; i < quantity; i++) {
System.out.println(upperCaseMessage);
}
}
}