Customization
Use Case
As an example, assume a process instance that is dependent on a department within a company. The user task assignment is then dependent on the department in which process instance is executed. Expressions are a natural way of determining assignees.
Assume there is a reference to the department as a process instance variable. Then given the department id, it is used to find members of the department and use that information to determine assignees for the given process.
Implementation Using Beans
The most straight forward way to extend the functionality available in expressions is to expose a Spring bean with a method. By default, expressions can contain Spring beans references.
The bean in the Spring configuration is:
@Bean
public DepartmentResolver departmentResolver() {
return new DepartmentResolver();
}
And for illustrative purposes this is a simple resolver:
public class DepartmentResolver {
public String getAssignee(ExecutionEntity execution) {
String departmentId = execution.getVariable("departmentId", String.class);
return departmentId + ".assignee";
}
}
The final step is to use the bean in the process definition:
<process id="oneTaskProcess">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="theTask"/>
<userTask id="theTask" name="My Task" flowable:assignee="${departmentResolver.getAssignee(execution)}"/>
<sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />
<endEvent id="theEnd"/>
</process>
A JUnit test verifies that user task assignee is the assignee from the DepartmentResolver:
@Test
@Deployment(resources = "AssigneeExpressionBeanTest.oneTaskProcessWithBeanAssignment.bpmn20.xml")
public void springBeanAssignUserTask() {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("departmentId", "testDepartmentId"));
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
assertThat(task.getAssignee()).isEqualTo("testDepartmentId.assignee");
}
For a CMMN example see SpringCustomBeanTest and associated files and f resources.
Implementation Using Expression Resolvers
Using expression beans like ${departmentResolver.getAssignee(execution)}
are really
easy to use, but they are not first-class expression citizens.
They require the execution of a method call and passing execution as
a parameter into the method. To avoid this step use a
simple assignee expression format, ${departmentAssignee}
.
This requires extending the expression resolvers (ELResolvers)
in the engine’s expression manager.
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration(ApplicationContext applicationContext, DataSource dataSource, PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration configuration =
new SpringProcessEngineConfiguration();
configuration.setDataSource(dataSource);
configuration.setTransactionManager(transactionManager);
configuration.setDatabaseSchemaUpdate("true");
configuration.setExpressionManager(new CustomExpressionManager(applicationContext, Collections.emptyMap()));
return configuration;
}
The CustomExpressionManager
extends a set of available ELResolvers with a
new department specific ELResolver.
public static class CustomExpressionManager extends SpringExpressionManager {
@Override
protected ELResolver createElResolver(VariableContainer variableContainer) {
CompositeELResolver compositeElResolver = new CompositeELResolver();
compositeElResolver.add(createVariableElResolver(variableContainer));
compositeElResolver.add(createSpringElResolver());
compositeElResolver.add(new ArrayELResolver());
compositeElResolver.add(new ListELResolver());
compositeElResolver.add(new MapELResolver());
compositeElResolver.add(new JsonNodeELResolver());
compositeElResolver.add(new BeanELResolver());
compositeElResolver.add(new DepartmentELResolver(variableContainer));
compositeElResolver.add(new CouldNotResolvePropertyELResolver());
return compositeElResolver;
}
}
The DepartmentELResolver
resolves the "departmentAssignee" keyword and
returns its associated department specific value.
public static class DepartmentELResolver extends VariableContainerELResolver {
public DepartmentELResolver(VariableContainer variableContainer) {
super(variableContainer);
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null && "departmentAssignee".equals(property) && variableContainer instanceof ExecutionEntity) {
Object departmentId = variableContainer.getVariable("departmentId");
if (departmentId == null) {
throw new RuntimeException("departmentId was not found in execution " + ((ExecutionEntity) variableContainer).getId());
}
context.setPropertyResolved(true);
return getDepartmentAssignee(departmentId);
}
return null;
}
protected String getDepartmentAssignee(Object departmentId) {
return departmentId + ".assignee";
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
}
The process model user task specification is now:
<userTask id="theTask" name="My Task" flowable:assignee="${departmentAssignee}"/>
For further reading and to get more inspired have a look at the set of ELResolvers in CustomExpressionManager#createElResolver.
Setting Values
Up until this point, all the expression examples only return values. However, expressions can be used to change values too. For example, given this human task case model:
<case id="oneHumanTaskCase">
<casePlanModel id="myPlanModel" name="My CasePlanModel">
<planItem id="planItem1" name="The Task" definitionRef="theTask" />
<humanTask id="theTask" name="The Task" isBlocking="true" flowable:assignee="johnDoe"/>
</casePlanModel>
</case>
It is possible to update a case instance variable without any expression as demonstrated by this JUnit test:
@Test
@CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnTaskServiceTest.testOneHumanTaskCase.cmmn")
public void testOneHumanTaskCaseScopeExpression() {
// create case instance with one human task
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
.caseDefinitionKey("oneHumanTaskCase")
// create a variable and set its value. Default expression manager allows variable update.
.variable("variableToUpdate", "VariableValue")
.start();
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
// complete task and update variable in the case instance
cmmnTaskService.complete(task.getId(), Collections.singletonMap(
"${variableToUpdate}", "updatedVariableValue"
)
);
HistoricCaseInstance historicCaseInstance = cmmnHistoryService
.createHistoricCaseInstanceQuery()
.caseInstanceId(caseInstance.getId())
.includeCaseVariables().singleResult();
assertThat(historicCaseInstance.getCaseVariables().get("variableToUpdate"), is("updatedVariableValue"));
}
The code adds the variable, variableToUpdate
, to the case with the
value VariableValue
. At the completion of the task VariableContainerELResolver
resolves the expression ${variableToUpdate}
and sets a new value
for the variable, updatedVariableValue
. The assertThat()
verifies this in fact
happened.
More expression setting value examples are in CmmnTaskServiceTest.