Activiti工作流节点的自由跳转

最近在搞openwebflow的工作流节点自由跳转功能,在网上看了一些资料,感觉不是很好,总结原因如下:

  • 直接手动调用SqlSession的操作,感觉会漏掉一些重要的初始化操作(如:启动新节点之后加载其用户授权策略,等);
  • 只有往前(往已执行过的节点)跳转的功能,没有往后节点(往还没有执行的节点)跳转的功能;
  • 新任务不是追加到已有执行路径上,而是覆盖老任务;

那么就自己动手吧!操作流程其实也简单,大概如下:

  1. 按照目标节点(activity)定义创建一个新的任务(task),这个创建过程必须和正常流程到了某个节点的时候完全一样(如:不应该忽略用户授权策略的加载,任务名称表达式的计算,等);
  2. 删除掉当前任务(task);

注意:直接删除当前节点会报错,因为它还在流程之中,所以要先解除任务与当前执行execution的关联;

以上操作如何安全的实现呢?看了一下源码,经过多次痛苦的尝试,积累了不少教训:

  • 直接SqlSession操作数据库是不行的,这种方法容易擦枪走火!
  • 直接taskService.saveTask也是不行的,因为它实际上仅仅是针对DbSqlSession的操作!不commit一切操作都白搭!

那么怎么办呢?我想说的是,Activiti的封装做得很厚,想完全看懂是太难的。目前我还没想完全看懂,直接吐槽一下,与后人分享其中的痛苦:

  • 太多的Command!一个saveTask()总会包装成SaveTask操作,关键代码总是藏得很深!
  • 太多的事件!
  • 太多的AtomicOperation!
  • 太多的Listener!
  • 太多的CommandInterceptor!隐隐约约感觉Activiti将各种CommandInterceptor组成一个chain,然后在执行核心代码的时候会一层一层的剥洋葱!
  • 还有就是栈式Context!看看下面这段代码就明白有多坑爹了:

  1. public static void setCommandContext(CommandContext commandContext) {  
  2.   getStack(commandContextThreadLocal).push(commandContext);  
  3. }  
  public static void setCommandContext(CommandContext commandContext) {
    getStack(commandContextThreadLocal).push(commandContext);
  }

总之,代码看得那是相当郁闷!debug的时候,调用栈极其深,而且大部分都会落在如下两段代码中:

代码1:

  1. public <T> T execute(CommandConfig config, Command<T> command) {  
  2.   if (!log.isDebugEnabled()) {  
  3.     // do nothing here if we cannot log  
  4.     return next.execute(config, command);  
  5.   }  
  6.   log.debug("\n");  
  7.   log.debug("--- starting {} --------------------------------------------------------", command.getClass().getSimpleName());  
  8.   try {  
  9.   
  10.     return next.execute(config, command);  
  11.   
  12.   } finally {  
  13.     log.debug("--- {} finished --------------------------------------------------------", command.getClass().getSimpleName());  
  14.     log.debug("\n");  
  15.   }  
  16. }  
  public <T> T execute(CommandConfig config, Command<T> 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

  1. try {  
  2.   // Push on stack  
  3.   Context.setCommandContext(context);  
  4.   Context.setProcessEngineConfiguration(processEngineConfiguration);  
  5.     
  6.   return next.execute(config, command);  
  7.     
  8. catch (Exception e) {  
  9.       
  10.   context.exception(e);  
  11.     
  12. finally {  
  13.   try {  
  14.       if (!contextReused) {  
  15.           context.close();  
  16.       }  
  17.   } finally {  
  18.       // Pop from stack  
  19.       Context.removeCommandContext();  
  20.       Context.removeProcessEngineConfiguration();  
  21.   }  
  22. }  
    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();
      }
    }

看到那么多的next没?足够让人疯掉的*_*

由于时间关系,我没有细细的去理解每个类,但最终还是找出了一个极妙、极安全的方法,那就是:自己写Command!然后扔给CommandExecutor()了事!

最终形成的代码如下所示:

  1. package org.openwebflow.ctrl;  
  2.   
  3. import org.activiti.engine.ProcessEngine;  
  4. import org.activiti.engine.TaskService;  
  5. import org.activiti.engine.impl.RuntimeServiceImpl;  
  6. import org.activiti.engine.impl.interceptor.Command;  
  7. import org.activiti.engine.impl.interceptor.CommandContext;  
  8. import org.activiti.engine.impl.persistence.entity.ExecutionEntity;  
  9. import org.activiti.engine.impl.persistence.entity.TaskEntity;  
  10. import org.activiti.engine.impl.pvm.process.ActivityImpl;  
  11. import org.openwebflow.util.ActivityUtils;  
  12.   
  13. public class TaskFlowControlService  
  14. {  
  15.     ProcessEngine _processEngine;  
  16.   
  17.     private String _processId;  
  18.   
  19.     public TaskFlowControlService(ProcessEngine processEngine, String processId)  
  20.     {  
  21.         _processEngine = processEngine;  
  22.         _processId = processId;  
  23.     }  
  24.   
  25.     /** 
  26.      * 跳转至指定活动节点 
  27.      *  
  28.      * @param targetTaskDefinitionKey 
  29.      * @throws Exception 
  30.      */  
  31.     public void jump(String targetTaskDefinitionKey) throws Exception  
  32.     {  
  33.         TaskEntity currentTask = (TaskEntity) _processEngine.getTaskService().createTaskQuery()  
  34.                 .processInstanceId(_processId).singleResult();  
  35.         jump(currentTask, targetTaskDefinitionKey);  
  36.     }  
  37.   
  38.     /** 
  39.      *  
  40.      * @param currentTaskEntity 
  41.      *            当前任务节点 
  42.      * @param targetTaskDefinitionKey 
  43.      *            目标任务节点(在模型定义里面的节点名称) 
  44.      * @throws Exception 
  45.      */  
  46.     private void jump(final TaskEntity currentTaskEntity, String targetTaskDefinitionKey) throws Exception  
  47.     {  
  48.         final ActivityImpl activity = ActivityUtils.getActivity(_processEngine,  
  49.             currentTaskEntity.getProcessDefinitionId(), targetTaskDefinitionKey);  
  50.   
  51.         final ExecutionEntity execution = (ExecutionEntity) _processEngine.getRuntimeService().createExecutionQuery()  
  52.                 .executionId(currentTaskEntity.getExecutionId()).singleResult();  
  53.   
  54.         final TaskService taskService = _processEngine.getTaskService();  
  55.   
  56.         //包装一个Command对象  
  57.         ((RuntimeServiceImpl) _processEngine.getRuntimeService()).getCommandExecutor().execute(  
  58.             new Command<java.lang.Void>()  
  59.             {  
  60.                 @Override  
  61.                 public Void execute(CommandContext commandContext)  
  62.                 {  
  63.                     //创建新任务  
  64.                     execution.setActivity(activity);  
  65.                     execution.executeActivity(activity);  
  66.   
  67.                     //删除当前的任务  
  68.                     //不能删除当前正在执行的任务,所以要先清除掉关联  
  69.                     currentTaskEntity.setExecutionId(null);  
  70.                     taskService.saveTask(currentTaskEntity);  
  71.                     taskService.deleteTask(currentTaskEntity.getId(), true);  
  72.   
  73.                     return null;  
  74.                 }  
  75.             });  
  76.     }  
  77. }  
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<java.lang.Void>()
			{
				@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;
				}
			});
	}
}

最后写了一个测试类,代码如下:

  1. @Test  
  2. public void testTaskSequence() throws Exception  
  3. {  
  4.     //_processDef对应于vacationRequest流程,参见https://github.com/bluejoe2008/openwebflow/blob/master/models/test.bpmn  
  5.     ProcessInstance instance = _processEngine.getRuntimeService().startProcessInstanceByKey(_processDef.getKey());  
  6.     String instanceId = instance.getId();  
  7.   
  8.     TaskService taskService = _processEngine.getTaskService();  
  9.     Task task1 = taskService.createTaskQuery().singleResult();  
  10.     Assert.assertEquals("step2", task1.getTaskDefinitionKey());  
  11.   
  12.     Map<String, Object> vars = new HashMap<String, Object>();  
  13.     vars.put("vacationApproved"false);  
  14.     vars.put("numberOfDays"10);  
  15.     vars.put("managerMotivation""get sick");  
  16.   
  17.     String taskId = taskService.createTaskQuery().taskCandidateUser("kermit").singleResult().getId();  
  18.     taskService.complete(taskId, vars);  
  19.     Task task2 = taskService.createTaskQuery().singleResult();  
  20.     Assert.assertEquals("adjustVacationRequestTask", task2.getTaskDefinitionKey());  
  21.   
  22.     TaskFlowControlService tfcs = new TaskFlowControlService(_processEngine, instanceId);  
  23.   
  24.     //跳回至 step2  
  25.     tfcs.jump("step2");  
  26.     Task task3 = taskService.createTaskQuery().singleResult();  
  27.     Assert.assertEquals("step2", task3.getTaskDefinitionKey());  
  28.   
  29.     //确认权限都拷贝过来了  
  30.     //management可以访问该task  
  31.     Assert.assertEquals(1, taskService.createTaskQuery().taskCandidateGroup("management").count());  
  32.     //engineering不可以访问该task  
  33.     Assert.assertEquals(0, taskService.createTaskQuery().taskCandidateGroup("engineering").count());  
  34.   
  35.     //确认历史轨迹里已保存  
  36.     List<HistoricActivityInstance> activities = _processEngine.getHistoryService()  
  37.             .createHistoricActivityInstanceQuery().processInstanceId(instanceId).list();  
  38.     Assert.assertEquals(5, activities.size());  
  39.     Assert.assertEquals("step1", activities.get(0).getActivityId());  
  40.     Assert.assertEquals("step2", activities.get(1).getActivityId());  
  41.     Assert.assertEquals("requestApprovedDecision", activities.get(2).getActivityId());  
  42.     Assert.assertEquals("adjustVacationRequestTask", activities.get(3).getActivityId());  
  43.     Assert.assertEquals("step2", activities.get(4).getActivityId());  
  44.   
  45.     //测试一下往前跳  
  46.     tfcs.jump("adjustVacationRequestTask");  
  47.     Task task4 = taskService.createTaskQuery().singleResult();  
  48.     Assert.assertEquals("adjustVacationRequestTask", task4.getTaskDefinitionKey());  
  49.   
  50.     activities = _processEngine.getHistoryService().createHistoricActivityInstanceQuery()  
  51.             .processInstanceId(instanceId).list();  
  52.     Assert.assertEquals(6, activities.size());  
  53.     Assert.assertEquals("adjustVacationRequestTask", activities.get(5).getActivityId());  
  54.     _processEngine.getRuntimeService().deleteProcessInstance(instanceId, "test");  
  55. }  
	@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<String, Object> vars = new HashMap<String, Object>();
		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<HistoricActivityInstance> 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");
	}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值