Add temporary tasks to process instances in flowable

Add temporary tasks to process instances in flowable

  • background
  • logic
  • specific code
  • contact author

Background

In flowable-ui, the process definition can be composed by means of front-end drawing, but in actual requirements, there may be cases where it is necessary to temporarily add tasks to the process instance without changing the original process definition.
In this article, through code, add temporary tasks to process instances. Temporary tasks support custom tags, multiple instances, and task listeners.

Logic

By looking at the source code of flowable, it is found that there is already a method for adding temporary tasks to process instances (org.flowable.engine.impl.cmd.InjectUserTaskInProcessInstanceCmd), but it is relatively simple, and only temporary tasks can be added at the initial node of the process Tasks, our requirement is to add temporary tasks after the current user tasks in the process, and support multiple instances and task listeners. So refer to the source code to rewrite it to meet our needs.
First inherit InjectUserTaskInProcessInstanceCmd, inject the required parameters, and then rewrite the updateBpmnProcess() and updateExecutions() methods.

Specific code

package com.zxy.manage.flow.vo.flowable.processinstance;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * @date 2023/4/25 10:36
 * Temporary task parameter class
 * The front end passes the relevant parameters of the temporary task to this object
 */
@Data
public class UserTaskDTO implements Serializable {<!-- -->
    @ApiModelProperty(value = "current node id")
    private String activityId;
    @ApiModelProperty(value = "current process instance id")
    private String processInsId;
    @ApiModelProperty(value = "task name")
    private String name;
    @ApiModelProperty(value = "Task responsible person - single instance optional")
    private String assignee;
    @ApiModelProperty(value = "Description")
    private String documentation;
    @ApiModelProperty(value = "Task Type - Multi-instance optional: USERS, DEPTS, STEMS")
    private String dataType;
    @ApiModelProperty(value = "Sequential execution - multiple instances optional, true/false")
    private Boolean sequential;
    @ApiModelProperty(value = "Preset personnel - multiple instances are optional: dataType is USERS")
    private List<String> candidateUsers;
    @ApiModelProperty(value = "Preset grouping - multiple instances are optional: dataType is DEPTS, STEMS")
    private List<String> candidateGroups;
    @ApiModelProperty(value = "completion condition")
    private String completionCondition;
    @ApiModelProperty(value = "group name")
    private String text;
    @ApiModelProperty(value = "List of task listeners")
    private List<TaskListenerDTO> taskListenerList;
}

Temporary tasks add cmd class
Precautions:
1. Get the user list for multi-instance implementation, ${multiInstanceHandler.getUserIds(execution)}, rely on the multi-instance user list to get the class MultiInstanceHandler.
2. Drawing the bpm diagram of the process instance is simple and blunt. If you need a more impressive graph, you can draw it through the front end, and then pass the parameters to the back end to create it.

package com.zxy.manage.flow.cmd;

import cn.hutool.core.util.ObjectUtil;
import com.zxy.manage.flow.constant.ProcessConstants;
import com.zxy.manage.flow.constant.TaskConstants;
import com.zxy.manage.flow.model.flowable.ExtendHisprocinst;
import com.zxy.manage.flow.service.flowable.IExtendHisprocinstService;
import com.zxy.manage.flow.vo.flowable.processinstance.UserTaskDTO;
import com.zxy.manage.tools.utils.StringUtil;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.cmd.InjectUserTaskInProcessInstanceCmd;
import org.flowable.engine.impl.dynamic.BaseDynamicSubProcessInjectUtil;
import org.flowable.engine.impl.dynamic.DynamicUserTaskBuilder;
import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Add temporary tasks to the process instance
 */
public class InjectMultiInstanceUserTaskCmd extends InjectUserTaskInProcessInstanceCmd {<!-- -->

    private UserTask currentUserTask;
    private DynamicUserTaskBuilder dynamicUserTaskBuilder;

    private UserTaskDTO userTaskDTO;

    private IExtendHisprocinstService extendHisprocinstService;

    public InjectMultiInstanceUserTaskCmd(
            String processInstanceId,
            UserTaskDTO userTaskDTO,
            IExtendHisprocinstService extendHisprocinstService,
            DynamicUserTaskBuilderdynamicUserTaskBuilder) {<!-- -->
        super(processInstanceId, dynamicUserTaskBuilder);
        this.dynamicUserTaskBuilder = dynamicUserTaskBuilder;
        this. userTaskDTO = userTaskDTO;
        this.extendHisprocinstService = extendHisprocinstService;
    }

    @Override
    protected void updateBpmnProcess(CommandContext commandContext, Process process, BpmnModel bpmnModel, ProcessDefinitionEntity originalProcessDefinitionEntity, DeploymentEntity newDeploymentEntity) {<!-- -->
        // Create dynamic nodes
        UserTask userTask = new UserTask();
        if (dynamicUserTaskBuilder.getId() != null) {<!-- -->
            userTask.setId(dynamicUserTaskBuilder.getId());
        } else {<!-- -->
            userTask.setId(dynamicUserTaskBuilder.nextTaskId(process.getFlowElementMap()));
        }
        dynamicUserTaskBuilder.setDynamicTaskId(userTask.getId());

        userTask.setName(userTaskDTO.getName());
        userTask.setAssignee(userTaskDTO.getAssignee());

        // set task listener
        if (userTaskDTO.getTaskListenerList() != null & amp; & amp; userTaskDTO.getTaskListenerList().size() > 0) {<!-- -->
            List<FlowableListener> taskListeners = userTaskDTO.getTaskListenerList().stream()
                    .map(taskListenerDTO -> {<!-- -->
                        FlowableListener listener = new FlowableListener();
                        listener.setEvent(taskListenerDTO.getEvent());
                        listener.setImplementation(taskListenerDTO.getImplementation());
                        if (taskListenerDTO.getFieldExtensions() != null & amp; & amp; taskListenerDTO.getFieldExtensions().size() > 0) {<!-- -->
                            List<FieldExtension> fieldExtensions = taskListenerDTO.getFieldExtensions().stream()
                                    .map(fieldExtensionDTO -> {<!-- -->
                                        FieldExtension fieldExtension = new FieldExtension();
                                        fieldExtension.setFieldName(fieldExtensionDTO.getFieldName());
                                        fieldExtension.setStringValue(fieldExtensionDTO.getStringValue());
                                        return fieldExtension;
                                    }).collect(Collectors.toList());
                            listener.setFieldExtensions(fieldExtensions);
                        }
                        return listener;
                    }).collect(Collectors.toList());
            userTask.setTaskListeners(taskListeners);
        }

        Map<String, List<ExtensionAttribute>> attributeMap = new HashMap<>();
        // Set the custom attribute of the userTask tag
        ExtensionAttribute attribute1 = new ExtensionAttribute();
        attribute1.setName("flowable:" + ProcessConstants.TASK_CUSTOM_DATA_TYPE);
        attribute1.setValue(userTaskDTO.getDataType());

        ExtensionAttribute attribute2 = new ExtensionAttribute();
        attribute2.setName("flowable:" + ProcessConstants.TASK_CUSTOM_TEXT);
        attribute2.setValue(userTaskDTO.getText());
        attributeMap.put(ProcessConstants.NAMASPASE, Arrays.asList(attribute1, attribute2));
        userTask.setAttributes(attributeMap);

        // multiple instances
        if (ObjectUtil.isNotEmpty(userTaskDTO.getCandidateUsers()) || ObjectUtil.isNotEmpty(userTaskDTO.getCandidateGroups())) {<!-- -->
            // Create multiple instance objects, set multiple instance conditions
            MultiInstanceLoopCharacteristics characteristics = new MultiInstanceLoopCharacteristics();
            // Set multi-instance user variables
            characteristics.setElementVariable("assignee");
            // Set the method processor for obtaining user information
            // The setCollectionString() method cannot be used here, according to the official statement,
            // When the expression or class used to get the user list, setInputDataItem should be used instead
            characteristics.setInputDataItem("${multiInstanceHandler.getUserIds(execution)}");
            // Set whether to parallel or serial
            characteristics.setSequential(userTaskDTO.getSequential());
            // Set task completion conditions
            characteristics.setCompletionCondition(userTaskDTO.getCompletionCondition());
            if (TaskConstants.USER_GROUP_PREFIX.equals(userTaskDTO.getDataType())) {<!-- -->
                // set user list
                userTask.setCandidateUsers(userTaskDTO.getCandidateUsers());
            } else if (TaskConstants.DEPT_GROUP_PREFIX.equals(userTaskDTO.getDataType()) || TaskConstants.STEM_GROUP_PREFIX.equals(userTaskDTO.getDataType())) {<!-- -->
                // set department list OR system list
                userTask.setCandidateGroups(userTaskDTO.getCandidateGroups());
            }

            // Get the approver from the multi-instance variable
            userTask.setAssignee("${assignee}");
            userTask.setLoopCharacteristics(characteristics);
        } else {<!-- -->
            // set the approver
            userTask.setAssignee(userTaskDTO.getAssignee());
        }

        process.addFlowElement(userTask);
        // get the current node
        this.currentUserTask = (UserTask) process.getFlowElement(userTaskDTO.getActivityId());
        // get the exit
        List<SequenceFlow> outgoingFlows = this.currentUserTask.getOutgoingFlows();
        List<SequenceFlow> addFlows = new ArrayList<>();
        List<SequenceFlow> updateFlows = new ArrayList<>();
        // The outlet points to the current node
        // The change rule of the line is actually very simple
        // prev -> next
        // Modify to prev -> dynamicTask -> next
        for (SequenceFlow flow : outgoingFlows) {<!-- -->
            SequenceFlow newFlow = new SequenceFlow();

            newFlow.setId("Flow_" + StringUtil.getRandomStr());
            newFlow.setSourceRef(userTask.getId());
            newFlow.setTargetRef(flow.getTargetRef());
            newFlow.setTargetFlowElement(flow.getTargetFlowElement());
            process.addFlowElement(newFlow);
            addFlows. add(newFlow);

            flow.setTargetRef(userTask.getId());
            flow.setTargetFlowElement(userTask);
            updateFlows. add(flow);
        }
        // add coordinates
        GraphicInfo elementGraphicInfo = bpmnModel.getGraphicInfo(currentUserTask.getId());
        if (elementGraphicInfo != null) {<!-- -->
            double yDiff = 0;
            double xDiff = 80;
            if (elementGraphicInfo.getY() < 173) {<!-- -->
                yDiff = 173 - elementGraphicInfo.getY();
                elementGraphicInfo.setY(173);
            }

            Map<String, GraphicInfo> locationMap = bpmnModel.getLocationMap();
            for (String locationId : locationMap. keySet()) {<!-- -->
                GraphicInfo locationGraphicInfo = locationMap.get(locationId);
                locationGraphicInfo.setX(locationGraphicInfo.getX() + xDiff);
                locationGraphicInfo.setY(locationGraphicInfo.getY() + yDiff);
            }

            Map<String, List<GraphicInfo>> flowLocationMap = bpmnModel.getFlowLocationMap();
            for (String flowId : flowLocationMap.keySet()) {<!-- -->
                List<GraphicInfo> flowGraphicInfoList = flowLocationMap.get(flowId);
                for (GraphicInfo flowGraphicInfo : flowGraphicInfoList) {<!-- -->
                    flowGraphicInfo.setX(flowGraphicInfo.getX() + xDiff);
                    flowGraphicInfo.setY(flowGraphicInfo.getY() + yDiff);
                }
            }
            int taskHeight = 80;
            int taskWidth = 100;
            int x = 160;
            int y = -160;
            /* Manually draw nodes */
            GraphicInfo newTaskGraphicInfo = new GraphicInfo(elementGraphicInfo.getX() + x, elementGraphicInfo.getY() + y, taskHeight, taskWidth);
            bpmnModel.addGraphicInfo(userTask.getId(), newTaskGraphicInfo);
            double nextX = 0;
            double nextY = 0;
            // Modify the original connection point
            for (SequenceFlow updateFlow : updateFlows) {<!-- -->
                List<GraphicInfo> list = bpmnModel.getFlowLocationGraphicInfo(updateFlow.getId());
                if (list. size() == 2) {<!-- -->
                    nextX = list.get(1).getX();
                    nextY = list.get(1).getY();
                    list.get(1).setX(elementGraphicInfo.getX() + x);
                    list.get(1).setY(elementGraphicInfo.getY() + y + taskHeight / 2);
                }
            }
            /* draw connection */
            for (SequenceFlow addFlow : addFlows) {<!-- -->
                bpmnModel.addFlowGraphicInfoList(addFlow.getId(), createWayPoints(elementGraphicInfo.getX() + x + taskWidth / 2, elementGraphicInfo.getY() + y + taskHeight,
                        nextX, nextY));
            }
        }

        BaseDynamicSubProcessInjectUtil.processFlowElements(commandContext, process, bpmnModel, originalProcessDefinitionEntity, newDeploymentEntity);
    }

    @Override
    protected void updateExecutions(CommandContext commandContext, ProcessDefinitionEntity processDefinitionEntity, ExecutionEntity processInstance, List<ExecutionEntity> childExecutions) {<!-- -->
        // Modify processDefinitionId
        ExtendHisprocinst extendHisprocinst = extendHisprocinstService.findExtendHisprocinstByProcessInstanceId(processInstanceId);
        extendHisprocinst.setProcessDefinitionId(processDefinitionEntity.getId());
        extendHisprocinstService.updateById(extendHisprocinst);
    }
}

method call

 @Autowired
    private ManagementService managementService;
    
    public void addTempTask(UserTaskDTO userTaskDTO) {<!-- -->
        DynamicUserTaskBuilder taskBuilder = new DynamicUserTaskBuilder();
        taskBuilder.setId("Activity_" + StringUtil.getRandomStr());
        taskBuilder.setName(userTaskDTO.getName());
        taskBuilder.setAssignee(userTaskDTO.getAssignee());
        managementService.executeCommand(new InjectMultiInstanceUserTaskCmd(userTaskDTO.getProcessInsId(), userTaskDTO, extendHisprocinstService, taskBuilder));
        this.evictHighLightedNodeCache(userTaskDTO.getProcessInsId());
    }

Contact the author

If you have any questions, please email the author.
[email protected]