也谈一下Activiti工作流节点的自由跳转
最近在搞openwebflow的工作流节点自由跳转功能,在网上看了一些资料,感觉不是很好,总结原因如下:
- 直接手动调用SqlSession的操作,感觉会漏掉一些重要的初始化操作(如:启动新节点之后加载其用户授权策略,等);
- 只有往前(往已执行过的节点)跳转的功能,没有往后节点(往还没有执行的节点)跳转的功能;
- 新任务不是追加到已有执行路径上,而是覆盖老任务;
那么就自己动手吧!操作流程其实也简单,大概如下:
- 按照目标节点(activity)定义创建一个新的任务(task),这个创建过程必须和正常流程到了某个节点的时候完全一样(如:不应该忽略用户授权策略的加载,任务名称表达式的计算,等);
- 删除掉当前任务(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");
}
也谈一下Activiti工作流节点的自由跳转的更多相关文章
- activiti 工作流 动态 设置 指定 节点任务人、责任人、组 的实现方式
首先给大家看一下我的流程图: 流程文件leaveBill.bpmn <?xml version="1.0" encoding="UTF-8"?>&l ...
- activiti 工作流动态设置指定节点任务人、责任人、组的实现方式
首先给大家看一下我的流程图: 流程文件leaveBill.bpmn <?xml version="1.0" encoding="UTF-8"?>&l ...
- Activiti工作流几种驳回方式的实现与比较
最近公司做的一个项目要实现工作流程的收回,驳回等操作,而采用的工作流引擎并不支持驳回功能,这个项目恰好就我和一个实习生一块做,所以这个问题就落到我的头上来解决了... 客户提出的要求是驳回时要记录日志 ...
- activiti工作流入门学习
工作流一般在OA系统用的比较多,当然,只要有流程审批的地方都会用到,activiti只是开源的工作流中比较流行的一个,还有其他的开源的工作流,这里学习activiti工作流:前面部分是关于activi ...
- 项目实践之工作流引擎基本文档!Activiti工作流框架中流程引擎API和服务详解
流程引擎的API和服务 流程引擎API(ProcessEngine API)是与Activiti打交道的最常用方式 Activiti从ProcessEngine开始.在ProcessEngine中,可 ...
- Activiti工作流学习(三)Activiti工作流与spring集成
一.前言 前面Activiti工作流的学习,说明了Activiti的基本应用,在我们开发中可以根据实际的业务参考Activiti的API去更好的理解以及巩固.我们实际的开发中我们基本上都使用sprin ...
- activiti 任务节点 处理人设置【转】
转自http://blog.csdn.net/qq_30739519/article/details/51225067 1.1.1. 前言 分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自 ...
- Activiti工作流学习-----基于5.19.0版本(6)
七. BPMN的简介 读者了解到这里,应付一般的工作流开发已经足够了.此处应该有华丽的分割线,在工作流项目中核心开发人员主要是对工作流业务设计以及实现,而初级开发人员是对业务功能的代码实现.以后将主要 ...
- Activiti工作流学习-----基于5.19.0版本(1)
该版本的Activiti运行须知: 1.JDK 6+,Eclipse最好是Kepler以上版本. 2.试验功能都有EXPERIMENTAL标注,被标注的部分不应该视为稳定的. 有兴趣的同学可以去了解下 ...
随机推荐
- 【转】Spark是基于内存的分布式计算引擎
Spark是基于内存的分布式计算引擎,以处理的高效和稳定著称.然而在实际的应用开发过程中,开发者还是会遇到种种问题,其中一大类就是和性能相关.在本文中,笔者将结合自身实践,谈谈如何尽可能地提高应用程序 ...
- SGU131 - Hardwood floor(状态压缩DP)
题目大意 给定一个N*M大小的矩形,要求你用1*2和2*2(缺个角)的砖块把矩形铺满(不能重叠),问总共有多少种铺法? 题解 受POJ2411的影响,怎么都没想到3,4,5,6这几种情况该怎么放置,看 ...
- 总结与学习DIV+CSS网页布局技巧
以前用表格布局时设置网页居中非常方便,把表格对齐方式设置为居中就行了,就这么简单,现在呢,用DIV+CSS样式表控制,好像不是那么容易了,其实也很简单,只不过方式不同而已. <style> ...
- 转载 https协议和http协议的区别
转载原地址: http://aajs800.blog.51cto.com/519255/109555 什么是HTTPS: HTTPS(Secure Hypertext Transfer Protoco ...
- Shell 脚本学习资料搜集
Shell文档 ChinaUnix上大神“網中人”总结的Shell十三问,强烈推荐,这本书讲得比较精炼,而且都是一些Shell学习中容易把握不住的一些细节难点.每一问都写得非常精彩.ChinaUnix ...
- angular-tour 用户新手引导
下载安装 bower install angular-tour 建立依赖,引入文件 <script src="bower_components/jquery/jquery.js&quo ...
- JAVA js的escape函数、解析用js encodeURI编码的字符串、utf8转gb2312的函数
在使用webView时,如果url中参数有中文的话,拦截到的字符串就会类似这样的:http://api.letstar.cn/zq/news.html?id=20&cupName=%E6%B5 ...
- iOS- iPad UIPopoverController
在IPAD开发中,有一个很有趣的视图控制器,UIPopoverControllr,它的初始化必须要设置一个"内容视图",相当于它本身只是作为一个“容器”,而显示的内容还需要另外一个 ...
- 字母A-Z写法
#大写的a-z,ASCII编码 65..90|%{[char]$_} #小写的A-Z 97..122|%{[char]$_} 方法二: ([char[]](97..122) -as [ ...
- 项目androidAnt编译打包Android项目
时间紧张,先记一笔,后续优化与完善. Ant编译打包Android项目 在Eclipse中对Android项目停止编译和打包如果项目比较大的话会比较慢,所以改为Ant工具来停止编译和打包 Ant环境配 ...