分析
关于并行任务的跳转,分享牛大神有他的代码。不过我觉得并行网关分支出来的任务,无论从现实业务还是逻辑实现上,都不应该可以随意跳转。
分享牛的跳转逻辑,无非是把并行分支的execution及其对应task都结束,然后跳转到目标节点上。然而这里其实涉及一些问题。从业务上来讲,某个分支流程,它自己是否要跳转到别的节点,不应该影响平行的其他任务节点,除非有明确的业务要求。从实现逻辑的角度来说,当存在多个并行网关多次分支的流程图中,跳转到某个节点时,到底需要提前结束哪些任务就会变得极难判断,如下图所示。综上所述,我认为并行网关中的任务不应该随意进行跳转。
实际业务可能不存在这样的流程,这里夸张了一点,纯粹举个例子。例如usertask6要跳转到usertask7,那根据图上来看,usertask5、usertask3的任务和execution必须删除。而且例如usertask4要跳转到usertask1,涉及的并行execution如何处理也是个问题。所以真不建议在并行网关中使用跳转。
只有一种情况的是可以进行跳转的,就是同一分支上的节点之间的跳转。例如下图这样:
usertask3跳转到usertask1,usertask2跳转到usertask6。类似这些在同一分支上的任务节点跳转,情况和普通的跳转类似,不过我们还是要对前面的例子改改
代码
这次我们的流程图如下,会有分支、以及多实例任务,我们计划是usertask1和usertask3之间可来回跳转,usertask2和usertask4也是如此:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="parallelJump" name="parallelJump" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="parallelgateway1"></sequenceFlow>
<userTask id="usertask1" name="usertask1">
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow2" sourceRef="parallelgateway1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="usertask2"></userTask>
<sequenceFlow id="flow3" sourceRef="parallelgateway1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="usertask3"></userTask>
<sequenceFlow id="flow4" sourceRef="usertask1" targetRef="usertask3"></sequenceFlow>
<userTask id="usertask4" name="usertask4">
<multiInstanceLoopCharacteristics isSequential="true">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow5" sourceRef="usertask2" targetRef="usertask4"></sequenceFlow>
<parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
<sequenceFlow id="flow6" sourceRef="usertask3" targetRef="parallelgateway2"></sequenceFlow>
<sequenceFlow id="flow7" sourceRef="usertask4" targetRef="parallelgateway2"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow8" sourceRef="parallelgateway2" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_parallelJump">
<bpmndi:BPMNPlane bpmnElement="parallelJump" id="BPMNPlane_parallelJump">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="110.0" y="250.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="parallelgateway1" id="BPMNShape_parallelgateway1">
<omgdc:Bounds height="40.0" width="40.0" x="190.0" y="247.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="250.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="250.0" y="290.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="410.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask4" id="BPMNShape_usertask4">
<omgdc:Bounds height="55.0" width="105.0" x="410.0" y="290.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="parallelgateway2" id="BPMNShape_parallelgateway2">
<omgdc:Bounds height="40.0" width="40.0" x="540.0" y="247.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="625.0" y="250.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="145.0" y="267.0"></omgdi:waypoint>
<omgdi:waypoint x="190.0" y="267.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="210.0" y="247.0"></omgdi:waypoint>
<omgdi:waypoint x="210.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="250.0" y="207.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="210.0" y="287.0"></omgdi:waypoint>
<omgdi:waypoint x="210.0" y="317.0"></omgdi:waypoint>
<omgdi:waypoint x="250.0" y="317.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="355.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="410.0" y="207.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="355.0" y="317.0"></omgdi:waypoint>
<omgdi:waypoint x="410.0" y="317.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="515.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="247.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="515.0" y="317.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="317.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="287.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
<omgdi:waypoint x="580.0" y="267.0"></omgdi:waypoint>
<omgdi:waypoint x="625.0" y="267.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
跳转的代码与前面MultiInstanceJumpCmd类似,但是要还要进行一些修改。之前我们是通过getProcessInstance获取其流程实例,进而获得其父execution,另外task也是与流程实例id关联的,通过流程实例id去控制是一种相对简便的处理方式。但在并行网关中,如果我们只处理单个分支的情况,通过流程实例id去处理task和execution,很可能会把另一分支也处理了,这不符合我们的预期,我们仅仅是需要处理当前分支对应的task和execution而已,所以不能通过流程实例id去操作,要老老实实根据活动节点的分类,判断是否获取其父节点进行操作:
package jump;
import java.util.List;
import java.util.Map;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.runtime.AtomicOperation;
import org.activiti.engine.runtime.Execution;
public class ParallelJumpCmd implements Command {
private String taskId;
private Map<String, Object> variables;
private String desActivityId;
private String scActivityId;
public ParallelJumpCmd(String taskId, Map<String, Object> variables, String scActivityId, String desActivityId) {
this.taskId = taskId;
this.variables = variables;
this.desActivityId = desActivityId;
this.scActivityId = scActivityId;
}
public Object execute(CommandContext commandContext) {
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
ExecutionEntity parentExecutionEntity = taskEntity.getProcessInstance();
String processDefinitionId = parentExecutionEntity.getProcessDefinitionId();
ProcessDefinitionEntity processDefinitionEntity = Context.getProcessEngineConfiguration().getDeploymentManager()
.findDeployedProcessDefinitionById(processDefinitionId);
ActivityImpl curActivityImpl = processDefinitionEntity.findActivity(scActivityId);
if(curActivityImpl.getActivityBehavior() instanceof SequentialMultiInstanceBehavior) {
parentExecutionEntity = taskEntity.getExecution().getParent();
}else if(curActivityImpl.getActivityBehavior() instanceof ParallelMultiInstanceBehavior) {
parentExecutionEntity = taskEntity.getExecution().getParent().getParent();
}else {
parentExecutionEntity = taskEntity.getExecution();
}
// 设置流程变量
parentExecutionEntity.setVariables(variables);
parentExecutionEntity.setExecutions(null);
parentExecutionEntity.setActivity(curActivityImpl);
parentExecutionEntity.setEventSource(curActivityImpl);
parentExecutionEntity.setActive(true);
// 触发全局事件转发器TASK_COMPLETED事件
if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(ActivitiEventBuilder
.createEntityWithVariablesEvent(ActivitiEventType.TASK_COMPLETED, this, variables, false));
}
// 删除任务
List<ExecutionEntity> childExecutionEntityList = parentExecutionEntity.getExecutions();
if(childExecutionEntityList.size() == 0) {
//普通任务
taskEntityManager.deleteTask(taskEntity, TaskEntity.DELETE_REASON_COMPLETED, false);
}
for(ExecutionEntity childExecutionEntity : childExecutionEntityList) {
List<ExecutionEntity> childExecutionEntityList1 = childExecutionEntity.getExecutions();
if(childExecutionEntityList1.size() == 0) {
//串行多实例任务处理
List<TaskEntity> taskList = childExecutionEntity.getTasks();
for(TaskEntity task : taskList) {
task.fireEvent(TaskListener.EVENTNAME_COMPLETE);
taskEntityManager.deleteTask(task, TaskEntity.DELETE_REASON_COMPLETED, false);
}
}else {
//并行多实例任务处理
for(ExecutionEntity childExecutionEntity1 : childExecutionEntityList1) {
List<TaskEntity> taskList1 = childExecutionEntity1.getTasks();
for(TaskEntity task : taskList1) {
task.fireEvent(TaskListener.EVENTNAME_COMPLETE);
taskEntityManager.deleteTask(task, TaskEntity.DELETE_REASON_COMPLETED, false);
}
}
}
}
ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();
List<ExecutionEntity> childExecutionList = executionEntityManager.findChildExecutionsByParentExecutionId(parentExecutionEntity.getId());
for(ExecutionEntity executionEntityChild : childExecutionList) {
List<ExecutionEntity> childExecutionList1 = executionEntityManager.findChildExecutionsByParentExecutionId(executionEntityChild.getId());
for(ExecutionEntity executionEntityChild1 : childExecutionList1) {
executionEntityChild1.remove();
Context.getCommandContext().getHistoryManager().recordActivityEnd(executionEntityChild1);
}
executionEntityChild.remove();
Context.getCommandContext().getHistoryManager().recordActivityEnd(executionEntityChild);
}
commandContext.getIdentityLinkEntityManager().deleteIdentityLinksByProcInstance(parentExecutionEntity.getId());
ActivityImpl desActivityimpl = processDefinitionEntity.findActivity(desActivityId);
parentExecutionEntity.removeVariable("nrOfInstances");
parentExecutionEntity.removeVariable("nrOfActiveInstances");
parentExecutionEntity.removeVariable("nrOfCompletedInstances");
parentExecutionEntity.removeVariable("loopCounter");
parentExecutionEntity.setActivity(desActivityimpl);
parentExecutionEntity.performOperation(AtomicOperation.TRANSITION_CREATE_SCOPE);
return null;
}
}
46-52行判断节点是串行多实例、并行多实例还是普通任务节点,分情况获取分支的父execution。68-92行也是分情况删除其execution下对应的task。如果还是像之前多实例任务跳转那样处理,就会把另一条分支上的task也删除了。另外59行要把状态设置成活动,因为多实例任务的父execution的isActive是false,跳转到普通任务节点,之后会导致普通任务节点的execution的usActive状态也是false,这就不对了。其他操作与前面的跳转是类似的,就不重复叙述了。