在进行flowable工作流的运用中,会涉及到任务的的一些特殊操作,如:退回,跳转,删除,重新触发等。退回和重新触发 主要借助
changeActivityState
,删除主要用ExecutionEntityManager.deleteExecutionAndRelatedData()
方法。
主要实现方法如下。
- 退回功能,主要是找到当前节点(可能多个)的
ExecutionId
和目标节点的ActivityId
,然后通过 moveExecutionsToSingleActivityId().changeState()实现。- 这个功能也适用于任务的前进,比如: 流程是 A ->B->C->D->E , 当前在C节点, 可以通过这个功能将流程 退回到 B ,也可以 前进到E, 只要流程图线路是
可达的
。
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.iccboy.framework.flowable.core.FlowableConstant;
import com.iccboy.framework.flowable.core.util.FlowableUtils;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.delegate.ActivityBehavior;
import org.flowable.engine.impl.persistence.entity.ActivityInstanceEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import com.google.common.collect.Sets;
/**
* 任务退回(跳到指定节点)
* @Author iccboy
*/
public class BackTaskCmd implements Command<String>, Serializable {
public static final long serialVersionUID = 1L;
private final transient RuntimeService runtimeService;
/**
* 当前运行的任务,流程节点id
*/
protected String taskIdOrActivityIdOrExecutionId;
/**
* 需要跳转的任务节点定义ID
*/
protected String targetActivityId;
public BackTaskCmd(RuntimeService runtimeService, String taskId, String targetActivityId) {
this.runtimeService = runtimeService;
this.taskIdOrActivityIdOrExecutionId = taskId;
this.targetActivityId = targetActivityId;
}
@Override
public String execute(CommandContext commandContext) {
if (targetActivityId == null || targetActivityId.length() == 0) {
throw new FlowableException("TargetActivityId cannot be empty");
}
TaskEntity task = CommandContextUtil.getProcessEngineConfiguration().getTaskServiceConfiguration().getTaskService().getTask(taskIdOrActivityIdOrExecutionId);
String sourceActivityId = null;
String processInstanceId = null;
String processDefinitionId = null;
String executionId = null;
if (task != null) {
sourceActivityId = task.getTaskDefinitionKey();
processInstanceId = task.getProcessInstanceId();
processDefinitionId = task.getProcessDefinitionId();
executionId = task.getExecutionId();
} else {
ActivityInstanceEntity instanceEntity = CommandContextUtil.getActivityInstanceEntityManager().findById(taskIdOrActivityIdOrExecutionId);
if(instanceEntity != null) {
sourceActivityId = instanceEntity.getProcessInstanceId();
processInstanceId = instanceEntity.getActivityId();
processDefinitionId = instanceEntity.getProcessDefinitionId();
executionId = instanceEntity.getExecutionId();
} else {
ExecutionEntity executionEntity = CommandContextUtil.getExecutionEntityManager().findById(taskIdOrActivityIdOrExecutionId);
if(executionEntity != null) {
sourceActivityId = executionEntity.getActivityId();
processInstanceId = executionEntity.getProcessInstanceId();
processDefinitionId = executionEntity.getProcessDefinitionId();
executionId = executionEntity.getId();
}
}
}
if(sourceActivityId == null) {
throw new FlowableObjectNotFoundException("task " + taskIdOrActivityIdOrExecutionId + " doesn't exist", Task.class);
}
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowNode sourceFlowElement = (FlowNode) process.getFlowElement(sourceActivityId, true);
// 只支持从用户任务退回
if (!(sourceFlowElement instanceof UserTask)) {
//throw new FlowableException("Task with id:" + taskId + " is not a UserTask");
}
FlowNode targetFlowElement = (FlowNode) process.getFlowElement(targetActivityId, true);
// 退回节点到当前节点不可达到,不允许退回
if (!FlowableUtils.isReachable(process, targetFlowElement, sourceFlowElement)) {
throw new FlowableException("Cannot back to [" + targetActivityId + "]");
}
// ps:目标节点如果相对当前节点是在子流程内部,则无法直接退回,目前处理是只能退回到子流程开始节点
String[] sourceAndTargetRealActivityId = FlowableUtils.getSourceAndTargetRealActivityId(sourceFlowElement,
targetFlowElement);
// 实际应操作的当前节点ID
String sourceRealActivityId = sourceAndTargetRealActivityId[0];
// 实际应操作的目标节点ID
String targetRealActivityId = sourceAndTargetRealActivityId[1];
Map<String, Set<String>> specialGatewayNodes = FlowableUtils.getSpecialGatewayElements(process);
// 当前节点处在的并行网关list
List<String> sourceInSpecialGatewayList = new ArrayList<>();
// 目标节点处在的并行网关list
List<String> targetInSpecialGatewayList = new ArrayList<>();
setSpecialGatewayList(sourceRealActivityId, targetRealActivityId, specialGatewayNodes,
sourceInSpecialGatewayList, targetInSpecialGatewayList);
// 实际应筛选的节点ID
Set<String> sourceRealAcitivtyIds = null;
// 若退回目标节点相对当前节点在并行网关中,则要找到相对当前节点最近的这个并行网关,后续做特殊处理
String targetRealSpecialGateway = null;
// 1.目标节点和当前节点都不在并行网关中
if (targetInSpecialGatewayList.isEmpty() && sourceInSpecialGatewayList.isEmpty()) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
}
// 2.目标节点不在并行网关中、当前节点在并行网关中
else if (targetInSpecialGatewayList.isEmpty()) {
sourceRealAcitivtyIds = specialGatewayNodes.get(sourceInSpecialGatewayList.get(0));
}
// 3.目标节点在并行网关中、当前节点不在并行网关中
else if (sourceInSpecialGatewayList.isEmpty()) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
targetRealSpecialGateway = targetInSpecialGatewayList.get(0);
}
// 4.目标节点和当前节点都在并行网关中
else {
int diffSpecialGatewayLevel = FlowableUtils.getDiffLevel(sourceInSpecialGatewayList,
targetInSpecialGatewayList);
// 在并行网关同一层且在同一分支
if (diffSpecialGatewayLevel == -1) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
} else {
// 当前节点最内层并行网关不被目标节点最内层并行网关包含
// 或理解为当前节点相对目标节点在并行网关外
// 只筛选当前节点的execution
if (sourceInSpecialGatewayList.size() == diffSpecialGatewayLevel) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
}
// 当前节点相对目标节点在并行网关内,应筛选相对目标节点最近的并行网关的所有节点的execution
else {
sourceRealAcitivtyIds = specialGatewayNodes.get(sourceInSpecialGatewayList.get(diffSpecialGatewayLevel));
}
// 目标节点最内层并行网关包含当前节点最内层并行网关
// 或理解为目标节点相对当前节点在并行网关外
// 不做处理
if (targetInSpecialGatewayList.size() == diffSpecialGatewayLevel) {
}
// 目标节点相对当前节点在并行网关内
else {
targetRealSpecialGateway = targetInSpecialGatewayList.get(diffSpecialGatewayLevel);
}
}
}
// 筛选需要处理的execution
List<ExecutionEntity> realExecutions = this.getRealExecutions(commandContext, processInstanceId,
executionId, sourceRealActivityId, sourceRealAcitivtyIds);
// 执行退回,直接跳转到实际的 targetRealActivityId
List<String> realExecutionIds =
realExecutions.stream().map(ExecutionEntity::getId).collect(Collectors.toList());
runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId)
.moveExecutionsToSingleActivityId(realExecutionIds, targetRealActivityId).changeState();
// 目标节点相对当前节点处于并行网关内,需要特殊处理,需要手动生成并行网关汇聚节点(_end)的execution数据
if (targetRealSpecialGateway != null) {
createTargetInSpecialGatewayEndExecutions(commandContext, realExecutions, process,
targetInSpecialGatewayList, targetRealSpecialGateway);
}
return targetRealActivityId;
}
private void setSpecialGatewayList(String sourceActivityId, String targetActivityId,
Map<String, Set<String>> specialGatewayNodes,
List<String> sourceInSpecialGatewayList,
List<String> targetInSpecialGatewayList) {
for (Map.Entry<String, Set<String>> entry : specialGatewayNodes.entrySet()) {
if (entry.getValue().contains(sourceActivityId)) {
sourceInSpecialGatewayList.add(entry.getKey());
}
if (entry.getValue().contains(targetActivityId)) {
targetInSpecialGatewayList.add(entry.getKey());
}
}
}
private void createTargetInSpecialGatewayEndExecutions(CommandContext commandContext,
List<ExecutionEntity> excutionEntitys, Process process,
List<String> targetInSpecialGatewayList,
String targetRealSpecialGateway) {
// 目标节点相对当前节点处于并行网关,需要手动生成并行网关汇聚节点(_end)的execution数据
String parentExecutionId = excutionEntitys.iterator().next().getParentId();
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity parentExecutionEntity = executionEntityManager.findById(parentExecutionId);
int index = targetInSpecialGatewayList.indexOf(targetRealSpecialGateway);
for (; index < targetInSpecialGatewayList.size(); index++) {
String targetInSpecialGateway = targetInSpecialGatewayList.get(index);
String targetInSpecialGatewayEndId = targetInSpecialGateway + FlowableConstant.SPECIAL_GATEWAY_END_SUFFIX;
FlowNode targetInSpecialGatewayEnd = (FlowNode) process.getFlowElement(targetInSpecialGatewayEndId, true);
int nbrOfExecutionsToJoin = targetInSpecialGatewayEnd.getIncomingFlows().size();
// 处理目标节点所处的分支以外的分支,即 总分枝数-1 = nbrOfExecutionsToJoin - 1
for (int i = 0; i < nbrOfExecutionsToJoin - 1; i++) {
ExecutionEntity childExecution = executionEntityManager.createChildExecution(parentExecutionEntity);
childExecution.setCurrentFlowElement(targetInSpecialGatewayEnd);
ActivityBehavior activityBehavior = (ActivityBehavior) targetInSpecialGatewayEnd.getBehavior();
activityBehavior.execute(childExecution);
}
}
}
private List<ExecutionEntity> getRealExecutions(CommandContext commandContext, String processInstanceId,
String taskExecutionId, String sourceRealActivityId,
Set<String> activityIds) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity taskExecution = executionEntityManager.findById(taskExecutionId);
List<ExecutionEntity> executions =
executionEntityManager.findChildExecutionsByProcessInstanceId(processInstanceId);
Set<String> parentExecutionIds = FlowableUtils.getParentExecutionIdsByActivityId(executions,
sourceRealActivityId);
String realParentExecutionId = FlowableUtils.getParentExecutionIdFromParentIds(taskExecution,
parentExecutionIds);
return executionEntityManager.findExecutionsByParentExecutionAndActivityIds(realParentExecutionId,
activityIds);
}
上面方法的调用如下:
@Autowired
private ManagementService managementService;
//.....
public void backTask(String taskId, String activityId) {
String targetRealActivityId = managementService.executeCommand(new BackTaskCmd(runtimeService,
taskId, activityId));
log.info("TaskId:{},流程跳到到:{}", taskId, targetRealActivityId);
}
任务重新触发,主要用于当前节点是 ServiceTask的情况,比如:当ServiceTask是异步任务时,然后没有进行边界异常事件的处理,如果当任务执行异常时,会阻碍流程的继续,此时就需要重新触发任务的执行,让流程重新推进下去。主要实现思路与上面的任务跳转类似。就是当前节点跳当前节点。使用的方法还是
moveExecutionsToSingleActivityId().changeState()
.
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Sets;
import com.iccboy.framework.flowable.core.FlowableConstant;
import com.iccboy.framework.flowable.core.util.FlowableUtils;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.delegate.ActivityBehavior;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.ProcessInstance;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
/**
* 重新加载任务
* @author iccboy
* @date 2023年1月8日
*/
public class ReloadTaskCmd implements Command<String>, Serializable {
public static final long serialVersionUID = 1L;
protected RuntimeService runtimeService;
protected String businessKey;
protected String targetActivityId;
public ReloadTaskCmd(RuntimeService runtimeService, String businessKey, String targetActivityId) {
this.runtimeService = runtimeService;
this.businessKey = businessKey;
this.targetActivityId = targetActivityId;
}
@Override
public String execute(CommandContext commandContext) {
if (StrUtil.isBlank(targetActivityId)) {
throw new FlowableException("TargetActivityId cannot be empty");
}
if (StrUtil.isBlank(businessKey)) {
throw new FlowableException("BusinessKey cannot be empty");
}
String sourceActivityId = null;
String processInstanceId = null;
String processDefinitionId = null;
String executionId = null;
ActivityInstance activityInstance = null;
List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).list();
for (ProcessInstance processInstance : processInstanceList) {
List<ActivityInstance> activityInstances = runtimeService.createActivityInstanceQuery().processInstanceId(processInstance.getProcessInstanceId())
.activityId(targetActivityId).orderByActivityInstanceStartTime().desc().list();
if(CollUtil.isNotEmpty(activityInstances)) {
activityInstance = activityInstances.get(0);
sourceActivityId = activityInstance.getActivityId();
processInstanceId = activityInstance.getProcessInstanceId();
processDefinitionId = activityInstance.getProcessDefinitionId();
executionId = activityInstance.getExecutionId();
break;
}
}
if (activityInstance == null) {
for (ProcessInstance processInstance : processInstanceList) {
List<ExecutionEntity> executionEntitys = CommandContextUtil.getExecutionEntityManager()
.findExecutionsByParentExecutionAndActivityIds(processInstance.getProcessInstanceId(), Collections.singleton(targetActivityId));
if(CollUtil.isNotEmpty(executionEntitys)) {
ExecutionEntity executionEntity = executionEntitys.stream().max(Comparator.comparing(ExecutionEntity::getStartTime)).orElse(null);
sourceActivityId = executionEntity.getActivityId();
processInstanceId = executionEntity.getProcessInstanceId();
processDefinitionId = executionEntity.getProcessDefinitionId();
executionId = executionEntity.getId();
break;
}
}
}
if(sourceActivityId == null) {
throw new FlowableObjectNotFoundException("targetActivity: " + targetActivityId + " does not exist");
}
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowNode sourceFlowElement = (FlowNode) process.getFlowElement(sourceActivityId, true);
FlowNode targetFlowElement = (FlowNode) process.getFlowElement(targetActivityId, true);
// 退回节点到当前节点不可达到,不允许退回
if (!FlowableUtils.isReachable(process, targetFlowElement, sourceFlowElement)) {
throw new FlowableException("Cannot back to [" + targetActivityId + "]");
}
// ps:目标节点如果相对当前节点是在子流程内部,则无法直接退回,目前处理是只能退回到子流程开始节点
String[] sourceAndTargetRealActivityId = FlowableUtils.getSourceAndTargetRealActivityId(sourceFlowElement,
targetFlowElement);
// 实际应操作的当前节点ID
String sourceRealActivityId = sourceAndTargetRealActivityId[0];
// 实际应操作的目标节点ID
String targetRealActivityId = sourceAndTargetRealActivityId[1];
Map<String, Set<String>> specialGatewayNodes = FlowableUtils.getSpecialGatewayElements(process);
// 当前节点处在的并行网关list
List<String> sourceInSpecialGatewayList = new ArrayList<>();
// 目标节点处在的并行网关list
List<String> targetInSpecialGatewayList = new ArrayList<>();
setSpecialGatewayList(sourceRealActivityId, targetRealActivityId, specialGatewayNodes,
sourceInSpecialGatewayList, targetInSpecialGatewayList);
// 实际应筛选的节点ID
Set<String> sourceRealAcitivtyIds = null;
// 若退回目标节点相对当前节点在并行网关中,则要找到相对当前节点最近的这个并行网关,后续做特殊处理
String targetRealSpecialGateway = null;
// 1.目标节点和当前节点都不在并行网关中
if (targetInSpecialGatewayList.isEmpty() && sourceInSpecialGatewayList.isEmpty()) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
}
// 2.目标节点不在并行网关中、当前节点在并行网关中
else if (targetInSpecialGatewayList.isEmpty() && !sourceInSpecialGatewayList.isEmpty()) {
sourceRealAcitivtyIds = specialGatewayNodes.get(sourceInSpecialGatewayList.get(0));
}
// 3.目标节点在并行网关中、当前节点不在并行网关中
else if (!targetInSpecialGatewayList.isEmpty() && sourceInSpecialGatewayList.isEmpty()) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
targetRealSpecialGateway = targetInSpecialGatewayList.get(0);
}
// 4.目标节点和当前节点都在并行网关中
else {
int diffSpecialGatewayLevel = FlowableUtils.getDiffLevel(sourceInSpecialGatewayList,
targetInSpecialGatewayList);
// 在并行网关同一层且在同一分支
if (diffSpecialGatewayLevel == -1) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
} else {
// 当前节点最内层并行网关不被目标节点最内层并行网关包含
// 或理解为当前节点相对目标节点在并行网关外
// 只筛选当前节点的execution
if (sourceInSpecialGatewayList.size() == diffSpecialGatewayLevel) {
sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
}
// 当前节点相对目标节点在并行网关内,应筛选相对目标节点最近的并行网关的所有节点的execution
else {
sourceRealAcitivtyIds =
specialGatewayNodes.get(sourceInSpecialGatewayList.get(diffSpecialGatewayLevel));
}
// 目标节点最内层并行网关包含当前节点最内层并行网关
// 或理解为目标节点相对当前节点在并行网关外
// 不做处理
if (targetInSpecialGatewayList.size() == diffSpecialGatewayLevel) {
} else {
// 目标节点相对当前节点在并行网关内
targetRealSpecialGateway = targetInSpecialGatewayList.get(diffSpecialGatewayLevel);
}
}
}
// 筛选需要处理的execution
List<ExecutionEntity> realExecutions = this.getRealExecutions(commandContext, processInstanceId,
executionId, sourceRealActivityId, sourceRealAcitivtyIds);
// 执行退回,直接跳转到实际的 targetRealActivityId
List<String> realExecutionIds =
realExecutions.stream().map(ExecutionEntity::getId).collect(Collectors.toList());
runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId)
.moveExecutionsToSingleActivityId(realExecutionIds, targetRealActivityId).changeState();
// 目标节点相对当前节点处于并行网关内,需要特殊处理,需要手动生成并行网关汇聚节点(_end)的execution数据
if (targetRealSpecialGateway != null) {
createTargetInSpecialGatewayEndExecutions(commandContext, realExecutions, process,
targetInSpecialGatewayList, targetRealSpecialGateway);
}
return targetRealActivityId;
}
private void setSpecialGatewayList(String sourceActivityId, String targetActivityId,
Map<String, Set<String>> specialGatewayNodes,
List<String> sourceInSpecialGatewayList,
List<String> targetInSpecialGatewayList) {
for (Map.Entry<String, Set<String>> entry : specialGatewayNodes.entrySet()) {
if (entry.getValue().contains(sourceActivityId)) {
sourceInSpecialGatewayList.add(entry.getKey());
}
if (entry.getValue().contains(targetActivityId)) {
targetInSpecialGatewayList.add(entry.getKey());
}
}
}
private void createTargetInSpecialGatewayEndExecutions(CommandContext commandContext,
List<ExecutionEntity> excutionEntitys, Process process,
List<String> targetInSpecialGatewayList,
String targetRealSpecialGateway) {
// 目标节点相对当前节点处于并行网关,需要手动生成并行网关汇聚节点(_end)的execution数据
String parentExecutionId = excutionEntitys.iterator().next().getParentId();
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity parentExecutionEntity = executionEntityManager.findById(parentExecutionId);
int index = targetInSpecialGatewayList.indexOf(targetRealSpecialGateway);
for (; index < targetInSpecialGatewayList.size(); index++) {
String targetInSpecialGateway = targetInSpecialGatewayList.get(index);
String targetInSpecialGatewayEndId = targetInSpecialGateway + FlowableConstant.SPECIAL_GATEWAY_END_SUFFIX;
FlowNode targetInSpecialGatewayEnd = (FlowNode) process.getFlowElement(targetInSpecialGatewayEndId, true);
int nbrOfExecutionsToJoin = targetInSpecialGatewayEnd.getIncomingFlows().size();
// 处理目标节点所处的分支以外的分支,即 总分枝数-1 = nbrOfExecutionsToJoin - 1
for (int i = 0; i < nbrOfExecutionsToJoin - 1; i++) {
ExecutionEntity childExecution = executionEntityManager.createChildExecution(parentExecutionEntity);
childExecution.setCurrentFlowElement(targetInSpecialGatewayEnd);
ActivityBehavior activityBehavior = (ActivityBehavior) targetInSpecialGatewayEnd.getBehavior();
activityBehavior.execute(childExecution);
}
}
}
private List<ExecutionEntity> getRealExecutions(CommandContext commandContext, String processInstanceId,
String taskExecutionId, String sourceRealActivityId,
Set<String> activityIds) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity taskExecution = executionEntityManager.findById(taskExecutionId);
List<ExecutionEntity> executions =
executionEntityManager.findChildExecutionsByProcessInstanceId(processInstanceId);
Set<String> parentExecutionIds = FlowableUtils.getParentExecutionIdsByActivityId(executions,
sourceRealActivityId);
String realParentExecutionId = FlowableUtils.getParentExecutionIdFromParentIds(taskExecution,
parentExecutionIds);
return executionEntityManager.findExecutionsByParentExecutionAndActivityIds(realParentExecutionId,
activityIds);
}
}
Cmd调用
public String reloadTask(String orderNo, String targetActivityId) {
String targetRealActivityId = managementService.executeCommand(new ReloadTaskCmd(runtimeService,
orderNo, targetActivityId));
log.info("orderNo:{},重新加载:{}", orderNo, targetRealActivityId);
return targetRealActivityId;
}
上面的方法大多和任务跳转
代码相似,还可以继续简化。
上面的Cmd中,有个businessKey
, 这里主要是通过业务key来定位的ProcessInstance
, 也可以直接传参 ProcessInstanceId
进来,这样更简单。
直接调用 taskService.deleteTask()
方式时会报错:The task cannot be deleted because is part of a running process
通过ExecutionEntityManager.deleteExecutionAndRelatedData实现删除则没可以。
import cn.hutool.core.lang.Assert;
import com.github.xiaoymin.knife4j.core.util.StrUtil;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.io.Serializable;
/**
* 删除任务
* @author itchenchao
* @date 2023年1月8日
*/
public class DeleteTaskCmd implements Command<String>, Serializable {
public static final long serialVersionUID = 1L;
protected String executionId;
protected String deleteReason;
public DeleteTaskCmd(String executionId, String deleteReason) {
this.executionId = executionId;
this.deleteReason = deleteReason;
}
@Override
public String execute(CommandContext commandContext) {
if (StrUtil.isBlank(executionId)) {
throw new FlowableException("executionId cannot be empty");
}
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
ExecutionEntity executionEntity = CommandContextUtil.getExecutionEntityManager().findById(executionId);
Assert.notNull(executionEntity, "ExecutionEntity:{}不存在", executionId);
executionEntityManager.deleteExecutionAndRelatedData(executionEntity, deleteReason, false, false);
return executionId;
}
}
调用示例:
managementService.executeCommand(new DeleteTaskCmd(task.getExecutionId(), req.getDeleteReason()));
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Flowable 相关的工具方法
*
*/
public class FlowableUtils {
public final static String SPECIAL_GATEWAY_BEGIN_SUFFIX = "_begin";
public final static String SPECIAL_GATEWAY_END_SUFFIX = "_end";
public final static String FLOWABLE_NAMESPACE = "http://flowable.org/bpmn";
// ========== User 相关的工具方法 ==========
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
public static void setAuthenticatedUserId(String bpmUserId) {
Authentication.setAuthenticatedUserId(bpmUserId);
}
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
// ========== BPMN 相关的工具方法 ==========
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model
* @param clazz 指定元素。例如说,{@link org.flowable.bpmn.model.UserTask}、{@link org.flowable.bpmn.model.Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
/**
* 比较 两个bpmnModel 是否相同
* @param oldModel 老的bpmn model
* @param newModel 新的bpmn model
*/
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
/**
* 把 bpmnModel 转换成 byte[]
* @param model bpmnModel
*/
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
// ========== Execution 相关的工具方法 ==========
public static String formatCollectionVariable(String activityId) {
return activityId + "_assignees";
}
public static String formatCollectionElementVariable(String activityId) {
return activityId + "_assignee";
}
public static <T> Map<String, List<T>> groupListContentBy(List<T> source, Function<T, String> classifier) {
return source.stream().collect(Collectors.groupingBy(classifier));
}
public static Map<String, FlowNode> getCanReachTo(FlowNode toFlowNode) {
return getCanReachTo(toFlowNode, null);
}
public static Map<String, FlowNode> getCanReachTo(FlowNode toFlowNode, Map<String, FlowNode> canReachToNodes) {
if (canReachToNodes == null) {
canReachToNodes = new HashMap<>(16);
}
List<SequenceFlow> flows = toFlowNode.getIncomingFlows();
if (flows != null && flows.size() > 0) {
for (SequenceFlow sequenceFlow : flows) {
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
if (sourceFlowElement instanceof FlowNode) {
canReachToNodes.put(sourceFlowElement.getId(), (FlowNode) sourceFlowElement);
if (sourceFlowElement instanceof SubProcess) {
for (Map.Entry<String, FlowElement> entry :
((SubProcess) sourceFlowElement).getFlowElementMap().entrySet()) {
if (entry.getValue() instanceof FlowNode) {
FlowNode flowNodeV = (FlowNode) entry.getValue();
canReachToNodes.put(entry.getKey(), flowNodeV);
}
}
}
getCanReachTo((FlowNode) sourceFlowElement, canReachToNodes);
}
}
}
if (toFlowNode.getSubProcess() != null) {
getCanReachTo(toFlowNode.getSubProcess(), canReachToNodes);
}
return canReachToNodes;
}
public static Map<String, FlowNode> getCanReachFrom(FlowNode fromFlowNode) {
return getCanReachFrom(fromFlowNode, null);
}
public static Map<String, FlowNode> getCanReachFrom(FlowNode fromFlowNode,
Map<String, FlowNode> canReachFromNodes) {
if (canReachFromNodes == null) {
canReachFromNodes = new HashMap<>(16);
}
List<SequenceFlow> flows = fromFlowNode.getOutgoingFlows();
if (flows != null && flows.size() > 0) {
for (SequenceFlow sequenceFlow : flows) {
FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
if (targetFlowElement instanceof FlowNode) {
canReachFromNodes.put(targetFlowElement.getId(), (FlowNode) targetFlowElement);
if (targetFlowElement instanceof SubProcess) {
for (Map.Entry<String, FlowElement> entry :
((SubProcess) targetFlowElement).getFlowElementMap().entrySet()) {
if (entry.getValue() instanceof FlowNode) {
FlowNode flowNodeV = (FlowNode) entry.getValue();
canReachFromNodes.put(entry.getKey(), flowNodeV);
}
}
}
getCanReachFrom((FlowNode) targetFlowElement, canReachFromNodes);
}
}
}
if (fromFlowNode.getSubProcess() != null) {
getCanReachFrom(fromFlowNode.getSubProcess(), canReachFromNodes);
}
return canReachFromNodes;
}
public static Map<String, Set<String>> getSpecialGatewayElements(FlowElementsContainer container) {
return getSpecialGatewayElements(container, null);
}
public static Map<String, Set<String>> getSpecialGatewayElements(FlowElementsContainer container, Map<String,
Set<String>> specialGatewayElements) {
if (specialGatewayElements == null) {
specialGatewayElements = new HashMap<>(16);
}
Collection<FlowElement> flowelements = container.getFlowElements();
for (FlowElement flowElement : flowelements) {
boolean isBeginSpecialGateway =
flowElement.getId().endsWith(SPECIAL_GATEWAY_BEGIN_SUFFIX) && (flowElement instanceof ParallelGateway || flowElement instanceof InclusiveGateway || flowElement instanceof ComplexGateway);
if (isBeginSpecialGateway) {
String gatewayBeginRealId = flowElement.getId();
String gatewayId = gatewayBeginRealId.substring(0, gatewayBeginRealId.length() - 6);
Set<String> gatewayIdContainFlowelements = specialGatewayElements.computeIfAbsent(gatewayId,
k -> new HashSet<>());
findElementsBetweenSpecialGateway(flowElement,
gatewayId + SPECIAL_GATEWAY_END_SUFFIX, gatewayIdContainFlowelements);
} else if (flowElement instanceof SubProcess) {
getSpecialGatewayElements((SubProcess) flowElement, specialGatewayElements);
}
}
// 外层到里层排序
Map<String, Set<String>> specialGatewayNodesSort = new LinkedHashMap<>();
specialGatewayElements.entrySet().stream().sorted((o1, o2) -> o2.getValue().size() - o1.getValue().size()).forEach(entry -> specialGatewayNodesSort.put(entry.getKey(), entry.getValue()));
return specialGatewayNodesSort;
}
public static void findElementsBetweenSpecialGateway(FlowElement specialGatewayBegin, String specialGatewayEndId,
Set<String> elements) {
elements.add(specialGatewayBegin.getId());
List<SequenceFlow> sequenceFlows = ((FlowNode) specialGatewayBegin).getOutgoingFlows();
if (sequenceFlows != null && sequenceFlows.size() > 0) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
String targetFlowElementId = targetFlowElement.getId();
elements.add(specialGatewayEndId);
if (targetFlowElementId.equals(specialGatewayEndId)) {
continue;
} else {
findElementsBetweenSpecialGateway(targetFlowElement, specialGatewayEndId, elements);
}
}
}
}
/**
* Verifies if the element with the given source identifier can reach the element with the target identifier through
* following sequence flow.
*/
public static boolean isReachable(String processDefinitionId, String sourceElementId, String targetElementId) {
// Fetch source and target elements
org.flowable.bpmn.model.Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowElement sourceFlowElement = process.getFlowElement(sourceElementId, true);
FlowNode sourceElement = null;
if (sourceFlowElement instanceof FlowNode) {
sourceElement = (FlowNode) sourceFlowElement;
} else if (sourceFlowElement instanceof SequenceFlow) {
sourceElement = (FlowNode) ((SequenceFlow) sourceFlowElement).getTargetFlowElement();
}
FlowElement targetFlowElement = process.getFlowElement(targetElementId, true);
FlowNode targetElement = null;
if (targetFlowElement instanceof FlowNode) {
targetElement = (FlowNode) targetFlowElement;
} else if (targetFlowElement instanceof SequenceFlow) {
targetElement = (FlowNode) ((SequenceFlow) targetFlowElement).getTargetFlowElement();
}
if (sourceElement == null) {
throw new FlowableException("Invalid sourceElementId '" + sourceElementId + "': no element found for " +
"this" + " id n process definition '" + processDefinitionId + "'");
}
if (targetElement == null) {
throw new FlowableException("Invalid targetElementId '" + targetElementId + "': no element found for " +
"this" + " id n process definition '" + processDefinitionId + "'");
}
Set<String> visitedElements = new HashSet<>();
return isReachable(process, sourceElement, targetElement, visitedElements);
}
public static boolean isReachable(org.flowable.bpmn.model.Process process, FlowNode sourceElement, FlowNode targetElement) {
return isReachable(process, sourceElement, targetElement, new HashSet());
}
public static boolean isReachable(org.flowable.bpmn.model.Process process, FlowNode sourceElement, FlowNode targetElement,
Set<String> visitedElements) {
// Special case: start events in an event subprocess might exist as an execution and are most likely be able to
// reach the target
// when the target is in the event subprocess, but should be ignored as they are not 'real' runtime executions
// (but rather waiting for a
// trigger)
if (sourceElement instanceof StartEvent && isInEventSubprocess(sourceElement)) {
return false;
}
// No outgoing seq flow: could be the end of eg . the process or an embedded subprocess
if (sourceElement.getOutgoingFlows().size() == 0) {
visitedElements.add(sourceElement.getId());
FlowElementsContainer parentElement = process.findParent(sourceElement);
if (parentElement instanceof SubProcess) {
sourceElement = (SubProcess) parentElement;
// by zjm begin
// 子流程的结束节点,若目标节点在该子流程中,说明无法到达,返回false
if (((SubProcess) sourceElement).getFlowElement(targetElement.getId()) != null) {
return false;
}
// by zjm end
} else {
return false;
}
}
if (sourceElement.getId().equals(targetElement.getId())) {
return true;
}
// To avoid infinite looping, we must capture every node we visit
// and check before going further in the graph if we have already
// visited the node.
visitedElements.add(sourceElement.getId());
// by zjm begin
// 当前节点能够到达子流程,且目标节点在子流程中,说明可以到达,返回true
if (sourceElement instanceof SubProcess && ((SubProcess) sourceElement).getFlowElement(targetElement.getId()) != null) {
return true;
}
// by zjm end
List<SequenceFlow> sequenceFlows = sourceElement.getOutgoingFlows();
if (sequenceFlows != null && sequenceFlows.size() > 0) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
String targetRef = sequenceFlow.getTargetRef();
FlowNode sequenceFlowTarget = (FlowNode) process.getFlowElement(targetRef, true);
if (sequenceFlowTarget != null && !visitedElements.contains(sequenceFlowTarget.getId())) {
boolean reachable = isReachable(process, sequenceFlowTarget, targetElement, visitedElements);
if (reachable) {
return true;
}
}
}
}
return false;
}
protected static boolean isInEventSubprocess(FlowNode flowNode) {
FlowElementsContainer flowElementsContainer = flowNode.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
public static List<String> getParentProcessIds(FlowNode flowNode) {
List<String> result = new ArrayList<>();
FlowElementsContainer flowElementsContainer = flowNode.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof SubProcess) {
SubProcess flowElement = (SubProcess) flowElementsContainer;
result.add(flowElement.getId());
flowElementsContainer = flowElement.getParentContainer();
} else if (flowElementsContainer instanceof org.flowable.bpmn.model.Process) {
org.flowable.bpmn.model.Process flowElement = (org.flowable.bpmn.model.Process) flowElementsContainer;
result.add(flowElement.getId());
flowElementsContainer = null;
}
}
// 第一层Process为第0个
Collections.reverse(result);
return result;
}
/**
* 查询不同层级
*
* @param sourceList
* @param targetList
* @return 返回不同的层级,如果其中一个层级较深,则返回层级小的+1,从第0层开始,请注意判断是否会出现下标越界异常;返回 -1 表示在同一层
*/
public static Integer getDiffLevel(List<String> sourceList, List<String> targetList) {
if (sourceList == null || sourceList.isEmpty() || targetList == null || targetList.isEmpty()) {
throw new FlowableException("sourceList and targetList cannot be empty");
}
if (sourceList.size() == 1 && targetList.size() == 1) {
// 都在第0层且不相等
if (!sourceList.get(0).equals(targetList.get(0))) {
return 0;
} else {// 都在第0层且相等
return -1;
}
}
int minSize = sourceList.size() < targetList.size() ? sourceList.size() : targetList.size();
Integer targetLevel = null;
for (int i = 0; i < minSize; i++) {
if (!sourceList.get(i).equals(targetList.get(i))) {
targetLevel = i;
break;
}
}
if (targetLevel == null) {
if (sourceList.size() == targetList.size()) {
targetLevel = -1;
} else {
targetLevel = minSize;
}
}
return targetLevel;
}
public static Set<String> getParentExecutionIdsByActivityId(List<ExecutionEntity> executions, String activityId) {
List<ExecutionEntity> activityIdExecutions =
executions.stream().filter(e -> activityId.equals(e.getActivityId())).collect(Collectors.toList());
if (activityIdExecutions.isEmpty()) {
throw new FlowableException("Active execution could not be found with activity id " + activityId);
}
// check for a multi instance root execution
ExecutionEntity miExecution = null;
boolean isInsideMultiInstance = false;
for (ExecutionEntity possibleMiExecution : activityIdExecutions) {
if (possibleMiExecution.isMultiInstanceRoot()) {
miExecution = possibleMiExecution;
isInsideMultiInstance = true;
break;
}
if (isExecutionInsideMultiInstance(possibleMiExecution)) {
isInsideMultiInstance = true;
}
}
Set<String> parentExecutionIds = new HashSet<>();
if (isInsideMultiInstance) {
Stream<ExecutionEntity> executionEntitiesStream = activityIdExecutions.stream();
if (miExecution != null) {
executionEntitiesStream = executionEntitiesStream.filter(ExecutionEntity::isMultiInstanceRoot);
}
executionEntitiesStream.forEach(childExecution -> {
parentExecutionIds.add(childExecution.getParentId());
});
} else {
ExecutionEntity execution = activityIdExecutions.iterator().next();
parentExecutionIds.add(execution.getParentId());
}
return parentExecutionIds;
}
public static boolean isExecutionInsideMultiInstance(ExecutionEntity execution) {
return getFlowElementMultiInstanceParentId(execution.getCurrentFlowElement()).isPresent();
}
public static Optional<String> getFlowElementMultiInstanceParentId(FlowElement flowElement) {
FlowElementsContainer parentContainer = flowElement.getParentContainer();
while (parentContainer instanceof Activity) {
if (isFlowElementMultiInstance((Activity) parentContainer)) {
return Optional.of(((Activity) parentContainer).getId());
}
parentContainer = ((Activity) parentContainer).getParentContainer();
}
return Optional.empty();
}
public static boolean isFlowElementMultiInstance(FlowElement flowElement) {
if (flowElement instanceof Activity) {
return ((Activity) flowElement).getLoopCharacteristics() != null;
}
return false;
}
public static String getParentExecutionIdFromParentIds(ExecutionEntity execution, Set<String> parentExecutionIds) {
ExecutionEntity taskParentExecution = execution.getParent();
String realParentExecutionId = null;
while (taskParentExecution != null) {
if (parentExecutionIds.contains(taskParentExecution.getId())) {
realParentExecutionId = taskParentExecution.getId();
break;
}
taskParentExecution = taskParentExecution.getParent();
}
if (realParentExecutionId == null || realParentExecutionId.length() == 0) {
throw new FlowableException("Parent execution could not be found with executionId id " + execution.getId());
}
return realParentExecutionId;
}
public static String[] getSourceAndTargetRealActivityId(FlowNode sourceFlowElement, FlowNode targetFlowElement) {
// 实际应操作的当前节点ID
String sourceRealActivityId = sourceFlowElement.getId();
// 实际应操作的目标节点ID
String targetRealActivityId = targetFlowElement.getId();
List<String> sourceParentProcesss = FlowableUtils.getParentProcessIds(sourceFlowElement);
List<String> targetParentProcesss = FlowableUtils.getParentProcessIds(targetFlowElement);
int diffParentLevel = getDiffLevel(sourceParentProcesss, targetParentProcesss);
if (diffParentLevel != -1) {
sourceRealActivityId = sourceParentProcesss.size() == diffParentLevel ? sourceRealActivityId :
sourceParentProcesss.get(diffParentLevel);
targetRealActivityId = targetParentProcesss.size() == diffParentLevel ? targetRealActivityId :
targetParentProcesss.get(diffParentLevel);
}
return new String[]{sourceRealActivityId, targetRealActivityId};
}
public static String getAttributeValue(BaseElement element, String namespace, String name) {
return element.getAttributeValue(namespace, name);
}
public static String getFlowableAttributeValue(BaseElement element, String name) {
return element.getAttributeValue(FLOWABLE_NAMESPACE, name);
}
public static List<ExtensionElement> getExtensionElements(BaseElement element, String name) {
return element.getExtensionElements().get(name);
}
public static FlowElement getFlowElement(RepositoryService repositoryService, String processDefinitionId,
String flowElementId, boolean searchRecursive) {
Process process = repositoryService.getBpmnModel(processDefinitionId).getMainProcess();
FlowElement flowElement = process.getFlowElement(flowElementId, searchRecursive);
return flowElement;
}
public static FlowElement getFlowElement(RepositoryService repositoryService, String processDefinitionId,
String flowElementId) {
return getFlowElement(repositoryService, processDefinitionId, flowElementId, true);
}
}
package com.iccboy.framework.flowable.core;
public class FlowableConstant {
/**
* 约定的发起者节点id-taskDefinitionKey
*/
public final static String INITIATOR = "__initiator__";
public final static String SPECIAL_GATEWAY_BEGIN_SUFFIX = "_begin";
public final static String SPECIAL_GATEWAY_END_SUFFIX = "_end";
public final static String PROCESS_INSTANCE_FORM_DATA = "processInstanceFormData";
public final static String IDENTITY_USER = "1";
public final static String IDENTITY_GROUP = "2";
public final static String ID = "id";
public final static String CATEGORY = "category";
public final static String KEY = "key";
public final static String NAME = "name";
public final static String VERSION = "version";
public final static String SUSPENDED = "suspended";
public final static String LATEST_VERSION = "latestVersion";
public final static String STARTABLE_BY_USER = "startableByUser";
public final static String TENANT_ID = "tenantId";
public final static String PROCESS_INSTANCE_ID = "processInstanceId";
public final static String PROCESS_INSTANCE_NAME = "processInstanceName";
public final static String PROCESS_DEFINITION_NAME = "processDefinitionName";
public final static String PROCESS_DEFINITION_KEY = "processDefinitionKey";
public final static String PROCESS_DEFINITION_ID = "processDefinitionId";
public final static String BUSINESS_KEY = "businessKey";
public final static String INVOLVED_USER = "involvedUser";
public final static String FINISHED = "finished";
public final static String SUPER_PROCESS_INSTANCE_ID = "superProcessInstanceId";
public final static String EXCLUDE_SUBPROCESSES = "excludeSubprocesses";
public final static String FINISHED_AFTER = "finishedAfter";
public final static String FINISHED_BEFORE = "finishedBefore";
public final static String STARTED_AFTER = "startedAfter";
public final static String STARTED_BEFORE = "startedBefore";
public final static String STARTED_BY = "startedBy";
public final static String START_BY_ME = "startByMe";
public final static String CC_TO_ME = "ccToMe";
public final static String CC = "CC";
public final static String TASK_ID = "taskId";
public final static String TASK_NAME = "taskName";
public final static String TASK_DESCRIPTION = "taskDescription";
public final static String TASK_DEFINITION_KEY = "taskDefinitionKey";
public final static String TASK_ASSIGNEE = "taskAssignee";
public final static String TASK_OWNER = "taskOwner";
public final static String TASK_INVOLVED_USER = "taskInvolvedUser";
public final static String TASK_PRIORITY = "taskPriority";
public final static String PARENT_TASK_ID = "parentTaskId";
public final static String DUE_DATE_AFTER = "dueDateAfter";
public final static String DUE_DATE_BEFORE = "dueDateBefore";
public final static String TASK_CREATED_BEFORE = "taskCreatedBefore";
public final static String TASK_CREATED_AFTER = "taskCreatedAfter";
public final static String TASK_COMPLETED_BEFORE = "taskCompletedBefore";
public final static String TASK_COMPLETED_AFTER = "taskCompletedAfter";
public final static String TASK_CANDIDATE_USER = "taskCandidateUser";
public final static String TASK_CANDIDATE_GROUP = "taskCandidateGroup";
public final static String TASK_CANDIDATE_GROUPS = "taskCandidateGroups";
public final static String PROCESS_INSTANCE_BUSINESS_KEY = "processInstanceBusinessKey";
public final static String PROCESS_FINISHED = "processFinished";
public final static String EXECUTION_ID = "executionId";
public final static String FILE_EXTENSION_BAR = ".bar";
public final static String FILE_EXTENSION_ZIP = ".zip";
public final static String CATEGORY_TODO = "todo";
public final static String CATEGORY_TO_READ = "toRead";
public final static String BUTTONS = "buttons";
public final static String FLOWABLE_NAMESPACE = "http://flowable.org/bpmn";
}