Skip to main content

Create Custom Backend Task

Target audience: Developers

Introduction

Flowable provides a wide variety of out-of-the-box tasks which you can use to model your processes and cases. But sometimes there are some custom elements which are specific to your domain or business. Since these tasks are not provided to you by Flowable, you can extend the set of out-of-the-box tasks with your own tasks.

This how-to describes how you can create your own backend tasks, use them in Flowable Design and execute them in Flowable Work/Engage.

A custom service task implementation is called in the Flowable Design context a palette element or stencil. A palette is then the name for all steps that are available for a specfic model type (for example, the process or BPMN palette). As an example we are going to create such a palette element which is calculating the addition of two numbers.

Note that this example could just be done with an expression ${a + b}, this how-to is rather to demonstrate the principle for more complex programming or integration logic.

The code for this how to can be found at GitHub.

Creating a Palette

The first step to your custom backend task is to create a palette file. This palette file can be for the case, for the process palette, or both. Those two model types have different palettes and execution engines, however you can easily write a common task. Depending on which model you would like to use, you can either patch the palette flowable-process-palette, the flowable-case-palette or both of them. Patching the existing palette allows to add or override elements to the default collection of steps available for that specific model type.

Flowable Design will look on the classpath location com/flowable/config/custom/palette for custom palette configuration files. We need to create a .palette file in this directory to make our own element available. To have suggestions and validation by your IDE, we recommend using the JSON Schema. In this example we are just calling the file demo.palette:

{
"$schema": "https://developer-docs.flowable.com/schemas/palette.json",
"Palette-Id": "demo-palette-extension",
"title": "Demo Palette Extension",
"patchPalettes": ["flowable-work-process-palette", "flowable-work-case-palette"],
"resourceBundles": ["com/flowable/config/custom/palette/translation"],
"groups": {
"math": {
"index": 10
}
},
"stencils": [
{
"id": "AdditionServiceTask",
"superId": "ServiceTask",
"groups": [
"math"
],
"properties": [
{
"id": "input-a",
"category": "commonDetails",
"type": "SimpleTextExpression",
"index": 10,
"optional": false
},
{
"id": "input-b",
"category": "commonDetails",
"type": "SimpleTextExpression",
"index": 20,
"optional": false
},
{
"id": "output",
"category": "commonDetails",
"type": "SimpleText",
"optional": false,
"index": 30,
"variableExtractor": {
"type": "simple"
}
},
{
"id": "delegateexpression",
"value": "${mathAPlusB}",
"visible": false
},
{
"id": "expression",
"visible": false
},
{
"id": "classfields",
"visible": false
},
{
"id": "class",
"visible": false
},
{
"id": "servicetasktriggerable",
"visible": false
},
{
"id": "resultvariable",
"visible": false
},
{
"id": "includeinhistory",
"visible": false
},
{
"id": "servicetaskUseLocalScopeForResultVariable",
"visible": false
}
]
}
]
}

As part of the palette configuration file a list of stencils can be provided.

The id is the unique identifier for the task. The attribute superId specifies from which stencil we inherit from. Since this implementation is for a technical backend task we will use ServiceTask here. The service task itself comes with a few pre-defined fields which you can see inside the user interface. It is possible to reconfigure the existing fields for your custom task: in the example above most of the fields are hidden, to make the user focus on the main fields.

The actual implementation we would like to invoke can be specified through the delegate expression. This expression refers to a variable or bean and is using the Java Unified Expression Language (JUEL) syntax ${...}. The delegate expression on the other hand is a flavor of the expression which expects that the result is an implementation of the type JavaDelegate for BPMN and PlanItemJavaDelegate for CMMN. In the Flowable Work and Engage product there is a common class called AbstractPlatformTask which can be implemented to have an implementation which is usable for both BPMN and CMMN.

In addition, there are a few properties like input-a, input-b and output, which have the purpose that the user can enter custom data. For the two input properties the type is SimpleTextExpression, which allows the user to enter either a static text or a JUEL with a reference to for example existing variables. The property output has defined a variableExtractor, which adds the input entered to the field to the auto complete suggestions for other fields. This is optional, but nice to have.

The remaining properties are specified to hide the not required properties from the service task. To achieve this, the same id is required with an additional flag visible set to false.

Once we start Flowable Design, we will see our task there.

There are no translations yet, this is why we will see the translation tags which we need to provide. Therefore, we can create a file com/flowable/config/custom/palette/translation.properties - exactly the name we have specified as resourceBundles, only with .properties at the end. In this file we can provide now our translations:

group.math.title=Math

AdditionServiceTask.title=Addition
AdditionServiceTask.description=Task to add two variables

property.input-a.title=Input A
property.input-a.description=The first input
property.input-b.title=Input B
property.input-b.description=The second input
property.output.title=Output Name
property.output.description=The output variable name

You could potentially specify an icon and bigIcon to customize the appearance of the new stencil. Therefore, you can add the following two properties to your stencil:

      "icon": "../palette-icons?id=component-presentations/palette-icons/add.png",
"bigIcon": "../palette-icons?id=component-presentations/palette-icons/add.svg",

The images need to be placed inside the folder com/flowable/config/custom/palette/component-presentations/palette-icons. The add.png needs to be a 16x16 PNG file, while the big icon add.svg is a SVG image.

Our palette file is now complete. The task is visible in Flowable Design:

Task in Flowable Design with one configured element

Of course, the variables a and b in the screenshot need to be configured. When you now publish the process and start an instance, you will see an error message and an exception in the log:

Caused by: org.flowable.common.engine.impl.javax.el.PropertyNotFoundException: Cannot resolve identifier 'mathAPlusB'
at org.flowable.common.engine.impl.de.odysseus.el.tree.impl.ast.AstIdentifier.eval(AstIdentifier.java:97) ~[flowable-engine-common-6.7.1.10.jar:6.7.1.10]
at org.flowable.common.engine.impl.de.odysseus.el.tree.impl.ast.AstEval.eval(AstEval.java:53) ~[flowable-engine-common-6.7.1.10.jar:6.7.1.10]
at org.flowable.common.engine.impl.de.odysseus.el.tree.impl.ast.AstNode.getValue(AstNode.java:31) ~[flowable-engine-common-6.7.1.10.jar:6.7.1.10]
at org.flowable.common.engine.impl.de.odysseus.el.TreeValueExpression.getValue(TreeValueExpression.java:122) ~[flowable-engine-common-6.7.1.10.jar:6.7.1.10]
at org.flowable.engine.impl.delegate.invocation.ExpressionGetInvocation.invoke(ExpressionGetInvocation.java:34) ~[flowable-engine-6.7.1.10.jar:6.7.1.10]
at org.flowable.engine.impl.delegate.invocation.DelegateInvocation.proceed(DelegateInvocation.java:32) ~[flowable-engine-6.7.1.10.jar:6.7.1.10]
at org.flowable.engine.impl.delegate.invocation.DefaultDelegateInterceptor.handleInvocation(DefaultDelegateInterceptor.java:26) ~[flowable-engine-6.7.1.10.jar:6.7.1.10]
at org.flowable.engine.impl.el.JuelExpression.resolveGetValueExpression(JuelExpression.java:44) ~[flowable-engine-6.7.1.10.jar:6.7.1.10]
at org.flowable.common.engine.impl.el.JuelExpression.getValue(JuelExpression.java:52) ~[flowable-engine-common-6.7.1.10.jar:6.7.1.10]
... 148 common frames omitted

The reason is that we did not implement our delegate expression mathAPlusB. In the next section we are going to implement the backend task which we need to integrate into Flowable Work or Engage.

Implementing the Task

To resolve the error we need to create a bean with the name mathAPlusB inside the Flowable Work/Engage module. This can be a simple Java class annotated by the @Service annotation. Since it is a delegate expression it also needs to implement JavaDelegate for BPMN and PlanItemJavaDelegate for CMMN. A common abstract class implementing both of them is the AbstractPlatformTask.

A simple implementation which does nothing will look like this:

package com.flowable.palette.work;

import org.flowable.common.engine.api.variable.VariableContainer;
import org.springframework.stereotype.Service;

import com.flowable.platform.tasks.AbstractPlatformTask;
import com.flowable.platform.tasks.ExtensionElementsContainer;

@Service
public class MathAPlusB extends AbstractPlatformTask {

@Override
public void executeTask(VariableContainer variableContainer, ExtensionElementsContainer extensionElementsContainer) {

}
}
note

Please ensure that your class is in a package which is covered within your Spring Boot component scan. This is typically the case for the package in which you have your @SpringBootApplication and all sub-packages.

By restarting the application you make the bean available to Flowable Work.

Now the only missing part is to do the actual computation. However, for that we first need to get the information from the palette. There are helper methods available from the AbstractPlatformTask to give us access to the items of the palette.

The methods getExtensionElementValue and getStringExtensionElementValue can be used to get the information provided in Flowable Design. In addition to reading the value of the text those fields use the current context variableContainer to resolve the variables and give us the direct result value. Our field input-a will resolve to the text ${a} which is then looked up in the context and will result in the integer 20 in case we have defined this variable on our current process. The same will happen for the other fields.

An implementation for the executeTask method could be:

    @Override
public void executeTask(VariableContainer variableContainer, ExtensionElementsContainer extensionElementsContainer) {
int inputA = getExtensionElementValue("input-a", extensionElementsContainer, variableContainer, 0);
int inputB = getExtensionElementValue("input-b", extensionElementsContainer, variableContainer, 0);
String output = getStringExtensionElementValue("output", extensionElementsContainer, variableContainer, "r");

variableContainer.setVariable(output, inputA + inputB);
}

There are multiple parameters for the methods getExtensionElementValue and getStringExtensionElementValue:

  1. The name of the field as we have defined it in Flowable Design on our palette.
  2. The ExtensionElementsContainer which contains information about the configuration for the current service task.
  3. The VariableContainer, a common interface between DelegateExecution and PlanItemJavaDelegate. That gives access to all the variables available on the process/case diagram.
  4. The default value which will be used in case the extension element is not present.

As a result there are now three different variables, inputA, inputB and output. For the variables inputA and inputB the type is here declared as int, which means that this implementation expects those values as int. In case those are not integers, you would get a class cast exception. For real world scenarios you might need to take an Object and handle multiple scenarios. The variable output contains the name of the output variable, which leads us to the last line in which we create on our current context variableContainer a new variable with the in the palette defined name.

Once you execute the process instance again you will see that a new variable is created with the sum of the two input variables.

Conclusion

Flowable Design and Work, in addition to being a low-code platform, also allows adding custom tasks using a Java implementation. This is a powerful way to enrich the capabilities of modelling users with technical solutions specific to your business.

The purpose of this How-To was to show how the development setup can be done, and a simple task can be integrated inside Flowable Design and Work. To create more powerful tasks it's worth it to read the documentation about Implement a Custom Service Task and Palette Customization.