Implement a Custom Service Task

When custom logic needs to be executed as part of a case or process model this is implemented with a custom service task. The service task element is available in Flowable Design in the CMMN and BPMN editor and can be configured with a Java class name or expression referencing a Spring bean. This document explains how to use the convenience classes provided with Flowable Work and Engage when implementing a custom service task.

Often, there is a need to use a custom service task in both the BPMN as well as the CMMN editor. As these models are executed in separate Engines, there is also a different Java interface that needs to be implemented with each custom service task. To prevent having to implement two classes, one for the BPMN and one for the CMMN engine, the com.flowable.platform.tasks.AbstractPlatformTask is provided as part of the flowable-platform-tasks module. When extending the AbstractPlatformTask class, only the executeTask method needs to be implemented with the custom logic.

@Override
public void executeTask(VariableContainer variableContainer,
                        ExtensionElementsContainer extensionElementsContainer) {
  // custom logic
}

Two parameters are passed into the executeTask method; the first one is the VariableContainer instance. This is an interface that is implemented in the BPMN and CMMN engine and provides access to the current variable context (e.g., process or case instance variable scope). The interface also provides a setVariable method to change an existing variable value or add a new variable. The second parameter is the ExtensionElementsContainer instance, which provides easy access to get extension element values that are defined as part of the custom service task. This is very useful to provide configuration options when using the custom service task in a case or process model.

In the generate document task that is provided as part of Flowable Work and Engage the same AbstractPlatformTask is used, and it is a good example of how to use both the VariableContainer and the ExtensionElementsContainer interfaces.

This is the XML representation of the generate document task:

<serviceTask id="generatedocumenttask1" name="Generate document" flowable:delegateExpression="${generateDocumentService}">
	<extensionElements>
		<design:outputvariablename xmlns:design="http://flowable.org/design"><![CDATA[myVar]]></design:outputvariablename>
		<design:name xmlns:design="http://flowable.org/design"><![CDATA[Generate document]]></design:name>
		<design:outputdocumentname xmlns:design="http://flowable.org/design"><![CDATA[test word doc ${customerId}]]></design:outputdocumentname>
		<design:templatekey xmlns:design="http://flowable.org/design"><![CDATA[myDocumentTemplate]]></design:templatekey>
	</extensionElements>
</serviceTask>

In order to generate the document, the values for the outputdocumentname and templatekey extension elements are needed. This can be done with the following logic taken from the executeTask method of the GenerateDocumentService.

@Override
public void executeTask(VariableContainer variableContainer, ExtensionElementsContainer extensionElementsContainer) {
    // The name of the document. This is the human-readable name, not the variable name.
	String outputDocumentName = getStringExtensionElementValue(TemplateProcessingConstants.OUTPUT_DOCUMENT_NAME, extensionElements, variableContainer, null);

	// An expression resolving to the key which is used to retrieve the Word template in the template engine.
	String templateKey = getStringExtensionElementValue(TemplateProcessingConstants.TEMPLATE_KEY, extensionElements, variableContainer, null);
}

The getStringExtensionElementValue is provided by the AbstractPlatformTask class and can be used to retrieve the value of an extension element. In the example of the outputdocumentname this also resolves the ${customerId} using the VariableContainer instance as well.

At the end of the generate document task logic, the generated document (which is a content item), is made available as a case or process variable.

This is done with the following logic:

@Override
public void executeTask(VariableContainer variableContainer, ExtensionElementsContainer extensionElementsContainer) {
    String variableName = getStringExtensionElementValue(TemplateProcessingConstants.OUTPUT_VARIABLE_NAME, extensionElements, variableContainer, null);
    variableContainer.setVariable(variableName, contentItems);
}

Because the variable name for the generated document is configurable, the configured name is first retrieved from the extension element outputvariablename and then the setVariable method of the VariableContainer is used to set the variable value.

When the custom service task is implemented, the next step is to make the class available as a Spring bean. This can be done with a Spring Boot auto-configuration.

The following auto-configuration is an example of how to expose the generate document task:

@Configuration
public class TasksAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(GenerateDocumentService.class)
    public GenerateDocumentService generateDocumentService() {
        return new GenerateDocumentService();
    }

}

The last step is to ensure that the TasksAutoConfiguration Spring Boot auto-configuration class is picked up when starting Flowable Work or Engage. This can be done with a spring.factories file in the META-INF folder (src/main/resources/META-INF for an Apache Maven based project).

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.flowable.autoconfigure.tasks.TasksAutoConfiguration
}

When the module containing the TasksAutoConfiguration class and the spring.factories file is made available on the classpath of Flowable Work or Engage as a JAR file and then the generate document task is automatically available as a Spring Bean. In Flowable Design a service task can now be configured with a delegate expression value of the exposed Spring bean, in this example, ${generateDocumentService}.

This document is focused on the Java (engine) implementation of the custom service task. The complete customer service task solution is to provide an element in the modeling palette of the BPMN and CMMN editor. Doing so removes the need to define a delegate expression every time the custom service task is needed in the model. It also enables the ability to define extension elements, exposed as configurable properties, in Flowable Design. To add the custom service task to the BPMN process palette see Add a Custom Service Task to the Flowable Design Process Palette and to add it to the CMMN palette see Add a Custom Service Task to the Flowable Design Case Palette.