最近在搞openwebflow的工作流节点自由跳转功能,在网上看了一些资料,感觉不是很好,总结原因如下:
那么就自己动手吧!操作流程其实也简单,大概如下:
注意:直接删除当前节点会报错,因为它还在流程之中,所以要先解除任务与当前执行execution的关联;
以上操作如何安全的实现呢?看了一下源码,经过多次痛苦的尝试,积累了不少教训:
那么怎么办呢?我想说的是,Activiti的封装做得很厚,想完全看懂是太难的。目前我还没想完全看懂,直接吐槽一下,与后人分享其中的痛苦:
public static void setCommandContext(CommandContext commandContext) {
getStack(commandContextThreadLocal).push(commandContext);
}
总之,代码看得那是相当郁闷!debug的时候,调用栈极其深,而且大部分都会落在如下两段代码中:
代码1:
public T execute(CommandConfig config, Command command) {
if (!log.isDebugEnabled()) {
// do nothing here if we cannot log
return next.execute(config, command);
}
log.debug("\n");
log.debug("--- starting {} --------------------------------------------------------", command.getClass().getSimpleName());
try {
return next.execute(config, command);
} finally {
log.debug("--- {} finished --------------------------------------------------------", command.getClass().getSimpleName());
log.debug("\n");
}
}
代码2:
try {
// Push on stack
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(processEngineConfiguration);
return next.execute(config, command);
} catch (Exception e) {
context.exception(e);
} finally {
try {
if (!contextReused) {
context.close();
}
} finally {
// Pop from stack
Context.removeCommandContext();
Context.removeProcessEngineConfiguration();
}
}
由于时间关系,我没有细细的去理解每个类,但最终还是找出了一个极妙、极安全的方法,那就是:自己写Command!然后扔给CommandExecutor()了事!
最终形成的代码如下所示:
package org.openwebflow.ctrl;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.RuntimeServiceImpl;
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.TaskEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.openwebflow.util.ActivityUtils;
public class TaskFlowControlService
{
ProcessEngine _processEngine;
private String _processId;
public TaskFlowControlService(ProcessEngine processEngine, String processId)
{
_processEngine = processEngine;
_processId = processId;
}
/**
* 跳转至指定活动节点
*
* @param targetTaskDefinitionKey
* @throws Exception
*/
public void jump(String targetTaskDefinitionKey) throws Exception
{
TaskEntity currentTask = (TaskEntity) _processEngine.getTaskService().createTaskQuery()
.processInstanceId(_processId).singleResult();
jump(currentTask, targetTaskDefinitionKey);
}
/**
*
* @param currentTaskEntity
* 当前任务节点
* @param targetTaskDefinitionKey
* 目标任务节点(在模型定义里面的节点名称)
* @throws Exception
*/
private void jump(final TaskEntity currentTaskEntity, String targetTaskDefinitionKey) throws Exception
{
final ActivityImpl activity = ActivityUtils.getActivity(_processEngine,
currentTaskEntity.getProcessDefinitionId(), targetTaskDefinitionKey);
final ExecutionEntity execution = (ExecutionEntity) _processEngine.getRuntimeService().createExecutionQuery()
.executionId(currentTaskEntity.getExecutionId()).singleResult();
final TaskService taskService = _processEngine.getTaskService();
//包装一个Command对象
((RuntimeServiceImpl) _processEngine.getRuntimeService()).getCommandExecutor().execute(
new Command()
{
@Override
public Void execute(CommandContext commandContext)
{
//创建新任务
execution.setActivity(activity);
execution.executeActivity(activity);
//删除当前的任务
//不能删除当前正在执行的任务,所以要先清除掉关联
currentTaskEntity.setExecutionId(null);
taskService.saveTask(currentTaskEntity);
taskService.deleteTask(currentTaskEntity.getId(), true);
return null;
}
});
}
}
最后写了一个测试类,代码如下:
@Test
public void testTaskSequence() throws Exception
{
//_processDef对应于vacationRequest流程,参见https://github.com/bluejoe2008/openwebflow/blob/master/models/test.bpmn
ProcessInstance instance = _processEngine.getRuntimeService().startProcessInstanceByKey(_processDef.getKey());
String instanceId = instance.getId();
TaskService taskService = _processEngine.getTaskService();
Task task1 = taskService.createTaskQuery().singleResult();
Assert.assertEquals("step2", task1.getTaskDefinitionKey());
Map vars = new HashMap();
vars.put("vacationApproved", false);
vars.put("numberOfDays", 10);
vars.put("managerMotivation", "get sick");
String taskId = taskService.createTaskQuery().taskCandidateUser("kermit").singleResult().getId();
taskService.complete(taskId, vars);
Task task2 = taskService.createTaskQuery().singleResult();
Assert.assertEquals("adjustVacationRequestTask", task2.getTaskDefinitionKey());
TaskFlowControlService tfcs = new TaskFlowControlService(_processEngine, instanceId);
//跳回至 step2
tfcs.jump("step2");
Task task3 = taskService.createTaskQuery().singleResult();
Assert.assertEquals("step2", task3.getTaskDefinitionKey());
//确认权限都拷贝过来了
//management可以访问该task
Assert.assertEquals(1, taskService.createTaskQuery().taskCandidateGroup("management").count());
//engineering不可以访问该task
Assert.assertEquals(0, taskService.createTaskQuery().taskCandidateGroup("engineering").count());
//确认历史轨迹里已保存
List activities = _processEngine.getHistoryService()
.createHistoricActivityInstanceQuery().processInstanceId(instanceId).list();
Assert.assertEquals(5, activities.size());
Assert.assertEquals("step1", activities.get(0).getActivityId());
Assert.assertEquals("step2", activities.get(1).getActivityId());
Assert.assertEquals("requestApprovedDecision", activities.get(2).getActivityId());
Assert.assertEquals("adjustVacationRequestTask", activities.get(3).getActivityId());
Assert.assertEquals("step2", activities.get(4).getActivityId());
//测试一下往前跳
tfcs.jump("adjustVacationRequestTask");
Task task4 = taskService.createTaskQuery().singleResult();
Assert.assertEquals("adjustVacationRequestTask", task4.getTaskDefinitionKey());
activities = _processEngine.getHistoryService().createHistoricActivityInstanceQuery()
.processInstanceId(instanceId).list();
Assert.assertEquals(6, activities.size());
Assert.assertEquals("adjustVacationRequestTask", activities.get(5).getActivityId());
_processEngine.getRuntimeService().deleteProcessInstance(instanceId, "test");
}