也談一下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!看看下面這段代碼就明白有多坑爹了:

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

總之,代碼看得那是相當郁悶!debug的時候,調用棧極其深,而且大部分都會落在如下兩段代碼中:

代碼1:

  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

    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()了事!

最終形成的代碼如下所示:

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;
				}
			});
	}
}

最后寫了一個測試類,代碼如下:

	@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");
	}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM