JWFDv0.96开源工作流引擎设计
---XML to 数据库解析过程说明
注:这篇文章中所使用的“函数”就是指JAVA语言中的方法
作者 comsci 2011.4.25 四川。成都
简要说明:
实际上,用户通过JWFD流程设计器设计出的流程图的结构一般是保存在XML格式的文件中的(XML文件结构请参考”JWFDv0.96 开源工作流引擎设计-流程图XML结构说明.doc“),而流程引擎的运行和控制却是建立在对后台数据库的基本表结构的SQL操作基础上面的(数据库结构请参考”JWFDv0.96 开源工作流引擎设计-数据库结构说明.doc“),因此从前端设计器XML到后台数据库基本表的数据解析和转换过程对于整个JWFD工作流系统的设计就显得尤为重要,所以我在初步完成了JWFD工作流系统设计文档的编写之后,发现这个环节必须补上,因此我将在这篇文档中,详细介绍JWFD工作流系统的XML-数据库解析过程(包括代码实现)
我尽量把整个设计思想和实现代码的结构都用很通俗的语言来讲述清楚,方便大家理解,如果经过自己的思考之后,还有不理解的地方,可以给我发邮件或者在JWFD的论坛上面提问题,非常感谢fireflow的非也和openjweb的阿宝同志的大力支持,JWFD也有自己的论坛板块
http://www.fireflow.org/forum-33-1.html,如果大家对JWFD有什么意见和建议,可以再这个论坛上面发帖,包括提交BUG报告
设计与实现方法:
采用基于JGRAPH开源软件的流程图数据结构XML模型,通过调用JGraphGXLCodec类的流程图编码和解码方法,将设计器设计出来的流程图转换为GXL(XML)-文件格式存储在本地硬盘(服务器)上面(解释:gxl文件格式就是一种简单的图形xml文件存储格式),然后通过调用ParserGxl类和GxlToDatabase类实现将XML文件中存储的流程图数据转换到数据库中进行存储,以便实现下一步JWFD流程引擎对流程图数据的处理。
JWFD 流程图-数据库转换的实现过程
上面的图例是JWFD开源工作流系统里面的流程图XML-数据库转换的实现流程,而具体负责实现上述功能的代码是在JWFD的代码包里面的 org.jwfd.workflowDesigner.FLCLs.Gxl 的package里面的下面三个类,如下
org.jwfd.workflowDesigner.FLCLs.Gxl.GxlToDatabase.java
org.jwfd.workflowDesigner.FLCLs.Gxl.JgraphGxlCodec.java
org.jwfd.workflowDesigner.FLCLs.Gxl.ParserGxl.java
主要的实现方法用简单的语言来描述就是: 通过调用DOM(一种XML解析工具包)类中的函数对流程图文件的XML结构进行解析,对存储在XML文件里面的流程图的拓扑结构(节点和连接线)进行提取操作,然后把提取出来的流程图拓扑结构数据 通过数据库的SQL操作insert,update等方式插入到已经建立好的MYSQL数据库的JWFD流程基本表中(JWFD的流程基本表的表结构请参考 JWFDv0.96 开源工作流引擎设计-数据库结构说明.doc 一文)
需要说明的是,XML解析模块在JWFD中主要是依靠JGRAPH开源软件的XML处理模块来实现的,JWFD通过调用这个XML处理模块,然后加上XML数据整理和数据库操作方法来共同完成这一过程
public ParserGxl(String fe, String gid) throws Exception {}
这个类中的主函数 ParserGxl()是一个DOM类的变形函数,主要用于从流程图的XML文档中把流程图的节点,连接线,坐标等数据提取出来,如果大家需要改造流程图的定义XML文件格式,那么就需要对这个函数中的某些代码有所了解
现在我们来对具体的实现代码进行分析
File f = new File(fe); 这句代码的意义是通过参数fe传递过来的文件名称建立一个文件类型的变量f,这个变量f就是后面DOM类用来读取xml文件的文件名称
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
这句代码的意义是建立一个XML文档数据处理模型的实例 .newInstance的意思就是在内存中创建一个XML数据模型的实例
DocumentBuilder db = dbf.newDocumentBuilder();
这句代码的意思是实例化一个XML文档解析类
Document doc = db.parse(f);
这句代码的意思是装载前面导入的XML文件
Element gxl = (Element) doc.getDocumentElement();
NodeList graph_list = gxl.getChildNodes();
这两句代码的作用是初始化需要解析的XML文件,并获取该XML文件的第一个元素,并从第一个元素列表list开始解析这个XML数据
实际上,前面几句代码联合在一起就是一个初始化XML数据处理类的代码段,这些代码段是统一在一起的,不能够分开使用,这点需要注意,特别是大家在复制这段代码的时候要注意
它们是一个整体,作用是初始化一个XML数据模型实例,并为下面的提取XML数据元做准备
if (graph_list.getLength() == 0) { return;}
这段代码的意思通过判断xml数据列表的长度是否为0,并执行返回命令
for (int graph_index = 0; graph_index < graph_list.getLength(); graph_index++) {
for循环,循环控制变量graph_index是XML文件的数据段长度,从这个循环开始程序开始自动的提取xml文件的数据
Node graph_node = graph_list.item(graph_index);
定义一个Node类型变量 并从graph_list数组item中提取graph_node元数据
if (graph_node.getNodeName().equals("graph")){}
这个IF判断的用处是通过判断xml数据段的初始名称是否为"graph",如果是则决定执行下面的操作,getNodeName()方法是用来程序读取XML数据段节点名称的操作
Element graph_elem = (Element) graph_node;
定义一个Element元素变量,并将graph_node变量类型强行转化为Element类型
NodeList list = graph_elem.getChildNodes();
定义一个NodeList变量list 并将前面定义的graph_elem里面的子元素提取出来
for (int i = 0; i < list.getLength(); i++) {}
这个FOR循环把前面获得xml流程图的LIST数据列表的长度作为循环控制条件,循环体内部是读取XML流程节点和连接线数据的操作函数,读取这些数据并同步写入数据库的表中
注意,这个for循环和前面另外一个for循环共同发挥作用,一起来做这个XML数据提取工作,因为XML数据结构是嵌套的,因此需要两个FOR循环来一起使用
=================================================================
if (node.getAttributes() != null && node.getNodeName() != null) {
String type = node.getNodeName().toString().toLowerCase();
if (type.equals("node")) {
Node edgeid = node.getAttributes().getNamedItem("id");
String id = edgeid.getNodeValue();
这两个嵌套的if判断语句是用来过滤需要提取的XML数据的标志头的,第一个if判断语句是过滤空数据的操作,就是属性和名称都为空null的数据就不进入下一步的提取操作,而非空的数据则需要进行tostring()-字符串转换操作并转化为小写lowerCase()
第二个if语句是判断type数据名称是否为node-节点 如果数据段标志为node,则我们就可以对这个XML数据段中包含的数据进行提取,这里主要提取两个标志中的数据,一个是节点的id,一个是连接边edge的id,并在下面的wis.into_node()方法中把这两个关键id值写入到数据库的表结构中去
=================================================================
wis.into_node(getLabel(node),id,gid,getcondition(node));
这句代码是这个类中所有代码的一个核心语句,意义就是把提取出来的XML文件中的流程图节点的id和节点包含的条件控制参数数据一起写入数据库中,当然这个方法是引用另外一个类GxlToDatabase中的操作,该操作实际上是一个SQL操作,具体的代码如下
"insert into step_main(step_name,graph_id,step_id,cond) values ('" +step_name +"','" + gid + "','" + step_id + "','" + condition + "')"
Insert语句中使用的数据库表名称 step_main是JWFD数据结构的节点主表,其表结构的详细说明请参考文档 JWFDv0.96 开源工作流引擎设计-数据库结构说明.doc
这个语句中的insert插入操作仅仅涉及step_main表中的step_name(节点名称)字段,graph_id(流程图id)字段,step_id(节点id)字段,cond(节点嵌入条件控制参数)字段,而这些字段中的具体数据就是用前面所描述的那些JAVA语句从XML数据文件中提取出来的,所以按照insert操作的顺序我们可以了解 getLabel(node)是取节点名称的操作,id是流程节点的id,gid是流程图的id,getcondition(node)是取节点嵌入的条件控制参数的操作,这样的说明是否让大家对本语句所执行的操作和涉及到数据结构有比较清晰的理解了呢? 如果还有不清楚地地方,请给我发邮件或者在论坛上面发帖或者加我的QQ(784092877),我会尽量回答大家的问题
接下来的这个IF判断语句是继续前面的工作,不过这次是提取XML中的节点之间的连接线edge的数据,如果类型为edge 则进行下面的提取操作
if (type.equals("edge"))
=================================================================
String from = null;
String to = null;
String edge = null;
String prop = null;
Node edgeid = node.getAttributes().getNamedItem("id");
Node tmp = node.getAttributes().getNamedItem("from");
Node tmp1 = node.getAttributes().getNamedItem("to");
edge = edgeid.getNodeValue();
from = tmp.getNodeValue();
to = tmp1.getNodeValue();
这段代码完成几个工作,前面四行定义四个字符串(from,to,edge,prop)变量,用于存储接下来要提取的XML文件中的连接线数据,其中from用于存储节点连接线的起始端点,to变量用于存储节点连接线的终止端点,两个节点之间的连接线就是通过起始端点和终止端点来定义的,另外加上一个Edgeid变量,这个变量作为连接边的唯一标志id
而接下来的代码段中的粗体字所表示的代码就是本程序用于提取xml文档中的数据的操作函数,这些操作代码来源于JAVA的XML处理模块dom类,大家可以找找专门介绍DOM数据处理的文章来看看,我在这里就不做详细介绍了
本段最后的三行代码就是利用getNodeValue()函数把XML数据段中的流程图的边,边起始点,边终止点的具体数据提取出来,并存放在前面定义的字符串变量edge,from,to中,以便下一步的处理
=================================================================
try {
wis.into_edge(edge, from, to, gid, getcondition(node));
}catch (Exception ex) {
System.out.println("创建edge异常:" +ex);
}
在理解前面一段的代码的意义之后,我们再来看看这段代码,这是一个try-catch异常处理语句包围的功能执行语句,前面我们已经介绍过这种wis类中的一个函数 wis.into_node,而这里的wis.into_edge的功能和这个wis.into_node的功能是对应的,into_node()函数是系统在提取了xml数据段中的节点相关数据之后,往数据库中写入这些数据的功能代码函数,而
into_edge()函数却是系统在提取了XML数据段中的节点连接边的相关数据之后,往数据库中写入这些节点连接边数据的功能代码函数,这个功能函数其实和前面介绍过的into_node()函数一样,是一个SQL语句,具体代码如下
"insert into edge_control(edge_id,from_step,to_step,graph_id,prop) values ('" +edgeid + "','" + from +"','" + to + "','" + gid + "','" + prop + "')"
前面介绍过这种类似的SQL操作,这里就再简略介绍下
edge_control 是 JWFD流程数据库结构中的一个基础表,这个基础表是系统用于存储流程图中连接边edge的一张表,其详细的数据结构请参考 JWFDv0.96 开源工作流引擎设计-数据库结构说明.doc
向edge_control数据表中写入的数据参数分别如下
Edge_id 两个节点间连接线段的唯一标识id
From_step 连接线段的起始节点id
To_step 连接线段的终止节点id
Graph_id 该连接线段所在的流程图的id
Prop 该连接线所包含的嵌入式参数(未使用),这里通过getcondition(node)函数来获得这个参数,实际上并未使用,这个参数是节点的嵌入数据,而不是连接边的嵌入数据
所有这些参数都已经通过前面的XML数据提取操作保存在本类定义的字符串变量中了,这个sql操作就用这些字符串变量作为写入数据库中的实际数据变量
=================================================================
到这里为止,系统提取XML数据的功能代码 和写入数据库的操作代码 就基本介绍完了,这个类的剩下的函数的代码 都是同一结构的函数,其主要功能是提取xml数据段中的详细参数,比如说getlabel是提取xml数据段中的节点的名称,注意是名称而不是id,而getcondition是提取节点中嵌入的条件表达式参数,getBound是提取节点在整个流程图的二维坐标的数据,这些功能代码的结构均来自jgraph的一个子项目 jgraphpad的代码包中,我仅仅是做了下修改,具体的代码结构我就不做详细的介绍了
protected String getcondition(Node node){}
protected String getLabel(Node node) {}
protected Vector getBound(Node node) {}
只有一个地方需要注意下,如果大家要自定义这些函数,比如说根据系统的需要修改了XML文件的自定义的变量,现在需要提取这些新的定义变量,仅仅需要修改这些函数的一行,就可以实现代码复用
if (attr.getNodeName().equals("attr")
&& attr
.getAttributes()
.getNamedItem("name")
.getNodeValue()
.equals(
"Condition"))
如果大家需要复用这段代码,只需要修改这段代码的最后一行的参数 这里是Condition,而仅仅需要把这个condition修改为你定义的XML的参数即可,而整个函数都可以立即复用,并不需要修改其他的部分,当然需要修改下函数的名称
=================================================================
实际上,到目前为止,通过这里所介绍的代码,我们就完成了将通过流程设计器设计好的流程图的XML文件转换并保存在后台的数据库中这一过程了,在实现这一个过程之后,我们就可以通过对数据库中的这些流程图数据进行SQL操作,来实现一个流程引擎的一系列功能了,流程引擎的底层操作API的结构和说明请参考下面的文档和代码
JWFDv0.96 开源工作流系统-二次开发API简易说明.doc
JWFDv0.96 开源工作流引擎设计-流程图XML结构说明.doc
JWFDv0.96 开源工作流引擎设计-自动运行控制器结构说明.doc
上面所提及的文档,我均压缩并上传,本文的附件中有下载