Adding Default Task Listener on Camunda

Camunda is an open-source business process management(BPM) that users can create and deploy workflows and process them on it. As a developer, the definition that I like most is developer- friendly BPM tool because it allows developers to extend tool as they need. For some time, I had opportunity to work with Camunda to provide a ready-to-use platform for other developers in my company according to needs of our company.

In this article, I will mention about how to add default task listeners to user tasks of your processes at runtime. To understand more why we need this kind of development, let’s state a scenario. Camunda platform is configured to integrate with LDAP and all the users and the groups are gathered from LDAP. There is a only one LDAP environment to use in different stages and somehow you have to differentiate groups that are used in different environments because of security issues. One alternative to have different process definitions on different environments, so the process for test phase will have test candidate groups and one for production will have candidate groups for production. But the tested process won’t be same with the one deployed to production by this one and this approach is more error-prone.

Another way is deploying same process definition on every environment and Camunda platform will handle candidate groups according to the environment that it serves. To realize this, a task listener will be executed on user task creation event and this task listener will switch candidate groups with the ones appropriate with the current environment. We will develop SecurityGroupTaskCreateListener to switch candidate groups with the environment related ones by impelemented TaskListener interface provided by Camunda platform for task listener development.

public class SecurityGroupTaskCreateListener implements TaskListener {

@Value("${bpm.group.suffix}")
private String suffix;

@Override
public void notify(DelegateTask delegateTask) {
List<String> groupsToLink = new LinkedList<>();
TaskEntity taskEntity = (TaskEntity) delegateTask;
List<IdentityLinkEntity> candidates = taskEntity.getIdentityLinks();
if (candidates == null || candidates.isEmpty()) {
return;
}
ListIterator<IdentityLinkEntity> iterator = candidates.listIterator();
while (iterator.hasNext()) {
IdentityLinkEntity candidate = iterator.next();
String groupId = candidate.getGroupId();
if(groupId == null || StringUtils.isEmpty(groupId)){
continue;
}
groupsToLink.add(groupId.concat(suffix));
candidate.delete(false);
iterator.remove();
}

if (groupsToLink.size() > 0) {
delegateTask.addCandidateGroups(groupsToLink);
}
}
}

SecurityGroupTaskCreateListener firstly queries the candidates of the task by calling taskEntity.getIdentityLinks() and after validations on candidate list it iterates through this list and gets group id of the current candidate. The candidate group is removed with candidate.delete(false) and after the iteration ends, group list with ones concatenated with environment suffix added to candidate groups of the task with delegateTask.addCandidateGroups(groupsToLink).

We have task listener to realize what we aim but it should be triggered on user task creation event of user tasks of all processes. For this, we will implement AddTaskListenerParseListener.

public class AddTaskListenerParseListener extends AbstractBpmnParseListener {

@Autowired
private SecurityGroupTaskCreateListener securityGroupTaskCreateListener;

@Override
public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) {
ActivityBehavior activityBehavior = activity.getActivityBehavior();
if(activityBehavior instanceof UserTaskActivityBehavior){
UserTaskActivityBehavior userTaskActivityBehavior = (UserTaskActivityBehavior) activityBehavior;
TaskDefinition taskDefinition = userTaskActivityBehavior.getTaskDefinition();
taskDefinition.addTaskListener(ListenerConstants.CREATE_EVENT_TYPE, securityGroupTaskCreateListener);
}
}
}

AddTaskListenerParseListener extends AbstractBpmnParseListener that is provided to develop custom parse listener in Camunda Platform. AbstractBpmnParseListener has parse methods for different process definition elements and these methods are triggered when a process is parsed. Our case requires to add task listener on create event to user task, so AddTaskListenerParseListener overrides parseUserTask method. To add task listener to a task, the task definition of parsed user task is obtained. SecurityGroupTaskCreateListener is added in the task listener list of the user task for create event with taskDefinition.addTaskListener(ListenerConstants.CREATE_EVENT_TYPE, securityGroupTaskCreateListener). We need a last component to register AddTaskListenerParseListener to Camunda platform. AddTaskListenerParseListenerPlugin is implemented for this purpose.

public class AddParseListenerPlugin extends AbstractProcessEnginePlugin {

@Autowired
private AddTaskListenerParseListener addTaskListenerParseListener;

@Override
public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
List<BpmnParseListener> preParseListeners = processEngineConfiguration.getCustomPreBPMNParseListeners();
if(preParseListeners == null) {
preParseListeners = new ArrayList<BpmnParseListener>();
processEngineConfiguration.setCustomPreBPMNParseListeners(preParseListeners);
}
preParseListeners.add(addTaskListenerParseListener);
}

}

AddParseListenerPlugin helps us to add custom BPMN parse listeners and AddTaskListenerParseListener is registered to Camunda platform. Now for all user tasks in processes deployed to Camunda platform, SecurityGroupTaskCreateListener is triggered on task creation event and security groups are organized to be environment compatible.