相比普通任务节点跳转,多实例任务的跳转要考虑更多的因素,主要是因为多实例任务包含了父execution和子execution,子execution又有其对应的task,另外还有控制多实例任务的内置变量。这几个特殊点是处理多实例任务跳转的难点。关于普通节点跳转的处理,请参考前文《activiti学习(二十二)——常规任务节点跳转(退回、自由跳转功能)》
与常规节点跳转相比,多实例任务有几个棘手的地方:
1、多实例任务存在父execution和子execution的情况,执行跳转时,必须删除子execution;
2、并行多实例任务存在多个task,进行跳转时,必须把多个task都删除;
3、因为并行多实例任务创建子execution的判断与流程变量有关,因此跳转时必须清除原来的流程变量;
我们的跳转命令必须解决以上三个问题。
这里简单写一个命令类供各位参考。
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.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;
public class MultiInstanceJumpCmd implements Command {
private String taskId;
private Map variables;
private String desActivityId;
private String scActivityId;
public MultiInstanceJumpCmd(String taskId, Map 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);
// 设置流程变量
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 taskList = taskEntityManager.findTasksByProcessInstanceId(parentExecutionEntity.getProcessInstanceId());
for(TaskEntity taskEntity1 : taskList) {
taskEntity1.fireEvent(TaskListener.EVENTNAME_COMPLETE);
taskEntityManager.deleteTask(taskEntity1, TaskEntity.DELETE_REASON_COMPLETED, false);
}
ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();
List childExecutionList = executionEntityManager.findChildExecutionsByParentExecutionId(parentExecutionEntity.getId());
for(ExecutionEntity executionEntityChild : childExecutionList) {
List 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;
}
}
这个类需要为其提供taskId,源节点id、目标节点id。35行获取父execution,36行获取流程定义id。54-58行通过流程定义id获取所有task,并删除之。60-70行删除子execution,之所以会有两个for循环,是因为并行多实例任务的子execution还会再创建子execution作为具体的执行实例。75-78行删除流程变量。81行不能使用普通节点跳转的executionEntity.executeActivity()。这是因为如果目标节点是多实例任务的话,不经过AtomicOperationTransitionCreateScope类,创建的execution会有问题。稍微跟踪一下AtomicOperation.TRANSITION_CREATE_SCOPE对应的AtomicOperationTransitionCreateScope类:
public class AtomicOperationTransitionCreateScope implements AtomicOperation {
//......
public void execute(InterpretableExecution execution) {
InterpretableExecution propagatingExecution = null;
ActivityImpl activity = (ActivityImpl) execution.getActivity();
if (activity.isScope()) {
propagatingExecution = (InterpretableExecution) execution.createExecution();
propagatingExecution.setActivity(activity);
propagatingExecution.setTransition(execution.getTransition());
execution.setTransition(null);
execution.setActivity(null);
execution.setActive(false);
log.debug("create scope: parent {} continues as execution {}", execution, propagatingExecution);
propagatingExecution.initialize();
} else {
propagatingExecution = execution;
}
propagatingExecution.performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_START);
}
}
对于普通节点,activity.isScope()判断为false,则执行第19行。若目标节点是普通任务节点,那不会有问题。但是目标节点是多实例任务时,会执行9-16行,除了创建一个子execution以外,还让子execution去执行接下来的原子操作。具体情况说起来比较绕口,就以数据库记录来解释方便一点,当通过跳转进入并行多实例任务时,act_ru_execution表如下:
2501是父execution,其id与流程实例id是一致的。12501是上面第9行代码创建出来的execution。12506、12507、12508则是进入并行多实例任务节点后,行为类根据节点配置的属性,创建对应数量的execution,这些execution的父亲是12501。而12506、12507、12508这三个execution,下面都有对应的task,当task完成时,对应的execution会结束。所以如果像普通节点跳转那样调用executionEntity.executeActivity()的话,就不会产生12501这个execution。
新建流程图multiInstanceJump.bpmn,与普通节点跳转的类似,区别是任务节点全变成了多实例任务。
3
3
3
3
接下来的部署和启动操作这里略过,不懂的读者请参考前面的文章,已经写过很多次了。其他配置与《activiti学习(二十二)——常规任务节点跳转(退回、自由跳转功能)》基本一致。
启动后查看act_ru_execution表:
userTask1为串行多实例节点。因此这时execution只有2个。
act_ru_task表:
该task对应2503那个execution。
现在我们准备从userTask1跳转到userTask4:
public void multiInstanceJump() {
String taskId = "2509";
Map variables = new HashMap();
String scActivityId = "usertask1";
String desActivityId = "usertask4";
MultiInstanceJumpCmd multiInstanceJumpCmd = new MultiInstanceJumpCmd(taskId, variables, scActivityId, desActivityId);
ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
CommandExecutor commandExecutor = service.getCommandExecutor();
commandExecutor.execute(multiInstanceJumpCmd);
}
执行后查看结果,act_ru_execution表:
因为userTask4为并行数3的并行多实例节点,因此会有5个execution。
act_ru_task表:
有3个任务,跳转正常。
考虑到实际流程中,必然存在普通任务节点,也存在多实例任务节点,因此跳转的命令必须能兼容这两种情况。目前简单测试了一下,该命令能正常进行普通任务节点和多实例任务节点的跳转。此次文章纯属抛砖引玉,读者如果有更好的方法,或者发现命令中存在一些错误,也请给予指正。