jwfdv0.96 工作流系统设计
--嵌入公式使用和结构说明
注:这篇文章中所使用的“函数”就是指java语言中的方法
作者 comsci 2010.11.18 四川。成都
总体设计思路
通过在流程的节点里面嵌入公式(脚本),保存在数据库中节点字段属性中的,然后流程引擎控制器通过执行脚本引擎获得运行公式的结果,然后通过这个运行结果控制流程的走向
设计说明:提前计算及其潜在问题
在实际的流程系统工程设计中,我们会遇到这样的问题,流程引擎要根据嵌入条件公式的运行结果来判断流程流转的方向,一般来讲流程引擎在控制流程流转的那个时刻才能够获得控制流转的参数,因为负责控制嵌入节点的脚本的脚本引擎是在流程流转到该节点的时候才启动,而这个时候脚本引擎运算的结果才作为流程引擎控制流转方向的参数发挥作用,那么我们在解决这个问题的时候,采取了一种叫“提前计算”的方法来处理这个问题,简单的讲,提前计算就是将流程引擎中对流程的流转控制所需的参数通过提前启动脚本引擎运行嵌入节点的脚本来提前获得这个参数,那么引擎一旦运行到这个节点的时候,这个关键的控制参数已经放在参数表中,引擎立即就可以确定流转的方向。
-------------------------------------------------------------------------------
嵌入公式的二次开发使用说明
当用户使用流程节点的属性编辑器写一段嵌入脚本公式进去之后,这段公式的数据就进入数据库的节点表step_main中的cond字段中的保存(请参考jwfd工作流系统的数据库结构文档)
对着设计器的节点点击鼠标右键“增加节点属性”
尝试点击编辑节点条件公式,出现流程节点嵌入表达式编辑界面
用户可以在这里面自定义嵌入表达式
这个界面所对应的代码是org.jwfd.workflowdesigner.uitools.swingtools.expresseditor.java
用户可以通过查看流程定义xml编辑窗口,看看你编辑的公式进入xml文档中没有
用户点下面这个标签,流程图.gxl的窗口就是xml的定义属性,如果用户编辑的公式已经在condition的属性中,那么用户提交流程的时候,这个字段就会被送入数据库中的节点表step_main中的cond字段进行保存
用户提交,出现异常之后,不管这个异常,直接看数据库中数据进入没有,如果进入就ok,这一步就到这里
那这一步就这样,这个异常我修改以后重新发布,用户暂时现在可以往下面的工作进行下去
就是对流程节点的嵌入脚本尝试计算。。然后根据计算结果来控制流程走向,请参考前面的设计说明
这个时候,用户需要调用代码包中的org.jwfd.workflowdesigner.uitools.database.mysql.flowssqlcontrolmodule.java这个类里面的这个sql操作方法public string get_condition()
这个方法通过传入的节点id(step_id)来获取流程数据表中节点的逻辑公式属性值(cond) 位置在这个类的第1440行
那么通过这个方法获取这个公式之后,下一步,我们就通过调用org.jwfd.workflowengines.grammaranalysis.grammarinterface.java这个类里面的public void scriptanalysis(string str){}这个方法来得到这个公式运行之后的结果值
通过这个scriptanalysis方法运行后,将公式结果直接屏幕输出值,用户要通过代码获得这个结果,需要修改一下这个方法的返回属性,修改为字符串(jwfd定义的嵌入公式都是字符串格式的,也可以定义为文本文件或者其它文件格式)
拿到这个结果之后,就需要进行下一步在org.jwfd.workflowengines.algorithm.topologyanalysis.java这个类中修改public void san(java.util.arraylist ln, string graphid,string prestep){}方法,这个san方法是流程引擎的核心控制器,这个控制器的原理在这篇文章中 http://www.xcomsci.cn/zblog/post/23.html
要根据公式计算结果来控制流程的流转方向,主要工作就在这个v0.96 引擎的san方法中处理,这个方法的前身其实就是一个图形拓扑遍历算法,算法的伪码描述在这里,san算法是在v0.94版本的引擎dsf算法的基础上面修改而成的,所以其基础结构还是dsf的结构
/****************** 流程引擎伪码描述算法 ***************************/
前驱路由点: 该点是n个节点的source点(n>1)
后驱路由点: 该点有n个节点的target点(n>1)
public dsf (){
for(int i = 0, i < 当前节点的邻接点个数, i ++ ){
if (该点是个前驱路由点) {
if (该点没有被访问过) {
设置访问次数加(从递归方法中获得的循环控制变量)
返回
}
else if( 如果已经访问过,但是访问次数<它的前驱节点总数){
设置访问次数加(从递归方法中获得的循环控制变量+1)
返回
}
else if(总计访问次数=它的前驱节点总数){
递归进入下一个节点的访问(把大循环体的,循环变量带进去递归方法中)
}
}
else if(如果是普通节点){
设置访问标志
递归进入下一个节点的访问(把大循环体的,循环变量带进去递归方法中,dfs(i))
}
}
}
}
/*****************************************************************************/
可以,用户在理解这个san方法的原理之后,可以自己写个简单的过程,其实很简单,就是一个for循环,然后里面嵌套若干个if语句,用户在其中加入对节点计算结果进行的if判断,就可以控制流程的流转方向了
由于有嵌入公式的加入,使得整个流程引擎结构的复杂程度上了一个台阶,因此,jwfdv0.96版本的引擎采用了“节点匹配搜索”算法来处理这个问题,要处理条件汇聚问题,就需要把节点匹配搜索这个算法描述清楚
请大家参考这几篇文章
带条件选择的并行汇聚路由问题
http://comsci.iteye.com/blog/339756
对“带条件选择的并行汇聚路由问题”的再思考
http://comsci.iteye.com/blog/466358
jwfd引擎设计-节点匹配搜索算法-简易说明
http://www.iteye.com/topic/513430
但是这个设计做完之后,我发现这个解决办法还无法完美的解决这个问题,因为提前启动脚本引擎进行运算,虽然可以提前于流程引擎获得运算结果,但是如果嵌入脚本需要前驱节点传递过来的表单或者业务数据才能够计算,那么这样的提前启动脚本引擎进行运行的方式就会因为缺少足够的计算参数而无法得到正确的结果,从而导致流程引擎无法提前获得确定的流转控制参数
对于这些新出现的问题,我在jwfd的下一个版本中,打算采用新的设计思路来处理该类问题
1:对用于处理流程节点的嵌入式脚本的脚本引擎的启动和计算参数的传递这一过程进行独立于流程引擎的设计,采取分立的方法,对这一系列的过程进行分别独立操作,结合脚本引擎和参数传递,提前计算的几个模块,形成一个独立于流程引擎的参数控制模块,当然这样会使得系统变得更加复杂了,系统的设计和开发成本又要上一个台阶了。
2: 独立的脚本和参数模块在运行控制的时间上面与流程引擎的拓扑结构的运行控制时间进行分离,也就是说,参数模块在运行的时间上面与流程引擎的运行时间互相分离,时间上面不同步,参数模块提前于流程引擎的运行,从而使得流程引擎在运行之前就能够获得充分的控制参数
但是这里有个问题,如果业务数据的处理过程是和流程引擎的运行过程不同步的话,那么如何提前运行嵌入式脚本模块来获得正确的参数呢? 要运行这个模块就必须获得表单传递过来的参数,而表单数据传递的过程如果和流程引擎不同步运行,那表单数据就无法按照流程的拓扑次序正确的传递过来,那么嵌入脚本模块也就不可能获得初始参数,也不可能运算出正确的流程条件逻辑参数,那么流程的流转就无法进行? 这个矛盾的确存在呀。。。
如果按照这个思路设计下去,我打算采用两个时间线(不一定是两个线程)来异步运行嵌入脚本引擎和流程主引擎,一条时间线(time line)或者进程来负责运行嵌入脚本引擎,作为主流程引擎进程的子进程,如果主流程引擎时间线在运行到条件汇聚节点的时候,这条子时间进程没有提前运行,那么这个时候,条件汇聚节点就无法正确的流转下去。。。。。。那么采取主流程引擎配合从参数引擎的方式来彻底解决这个问题,引擎的结构就变成两个主从模块共同工作了