工作流引擎过程定义的设计与实现
摘要本文介绍了工作流引擎中过程定义与解析的解决方案。文章首先介绍了工作流引擎的过程定义;接着简要介绍了可用于过程定义的XPDL语言以及其中的两个主要元素结构;接着文章给出基于XPDL的过程定义设计文件内容;然后,对该定义文件进行解析并给出源代码;最后,详细说明了流程版本解决方案的实现思路与具体实现函数。
【关键词】工作流引擎 过程定义 解析 版本控制
1 工作流引擎的过程定义
过程定义是一个业务过程支持自动化操作的形式化表现,过程定义由任务网络及其关系,过程开始和终止的条件,任务资源,诸如参与者、相关的IT应用及数据等组成。在工作流引擎的过程定义方面,工作流管理联盟定义了工作流模型的一个元模型,这个元模型有利于建立可以在多个工作流产品之间交换信息的模型。由该模型可知,工作流定义反映了企业中的一个经营过程的目的,它由多个活动与多个工作流相关数据组成,其中活动是它的组成核心。活动对应于企业经营过程中的任务,主要反映了完成企业经营过程需要执行哪些功能操作。活动所产生的任务项根据活动的类型执行,如果活动类型为手动,任务项就分配给活动的操作人员或组织单位的角色;如果活动类型为自动,引擎就自动激活相应的应用程序完成任务。转换条件和相关数据主要负责为过程实例的推进提供导航依据。
2 过程定义语言的采用
本设计的过程定义语言采用的是XPDL(XML Process Definition Language)。XPDL是基于过程定义元模型,是由工作流管理联盟制定的一种采用符合XML语法的文本描述语言。XPDL根据过程定义元模型,制定了自己的语言结构,用一个XML Schema表示,通过该语言结构实现过程定义之间的相互转换。其中解决方案使用的是XML Schema中定义的主要元素“Package”和“WorkflowProcess”,其元素结构如图1、图2所示。
3 过程定义文件设计
本设计方案选用了一个典型的经营过程:自行车的订购。根据流程图,用XPDL表示该经营过程的文件主要内容如下所示:
-
xmlns:xpdl="http://www.wfmc.org/standards/docs/xpdl"
xmlns:xsd="http://localhost/transaction.xsd">
+
+
-
-
+
+
+
+
+
+
+
+
+
4 过程定义的解析
XML开发组织为开发人员使用XML文档提供了许多API。通过这些API可以进行过程定义的解析,最流行和广泛使用的API中的四种:文档对象模型(Document Object Model (DOM))、用于 XML 的简单 API(Simple API for XML (SAX))、DOM4J和用于XML解析的Java API(Java API for XML Parsing (JAXP))[3]。根据解析的机理大致可分成两种。第一种解析机理:解析器一次性读取完整文档,接着在内存中构造一个树结构,然后代码就可使用相关接口对这个树结构进行操作。采用这种工作方式的有DOM、DOM4J、JAXP。第二种机理:解析器基于事件,实际上是通过串行的方式来处理文档的,当解析器发现元素、文本和文档等元素开始或结束时,它向应用程序发送消息,由应用程序决定如何进行处理,解析器本身不创建任何的对象,采用这种工作方式的有SAX。这两种方式各有优缺点:第一种方式由于创建了内存树,所以对内存树的操作就比较简单,功能也强大,程序可以通过各种办法对树进行遍历,查找需要的元素。这种方式的缺点是:a、由于需要在内存中构建完整文档的树结构,当文档很大时对内存就提出了比较高的要求。b、内存树会表示文档中每个元素,但是如果程序只需要文档的一小部分,那么那些根本就不会被使用对象的创建是很浪费的。c、解析器在程序运行前先要读取完整文档,那么当文档很大时,会引起程序运行的显著延迟。第二种方式由于基于事件响应,解析器一旦发现对象要素开始时就会产生一个事件,程序就会立即生成结果,而不必文档解析完毕后才开始。特别是,当程序只查找某些文档信息时,程序只要一找到所要的内容就会抛出一个异常。该异常会使SAX解析器停止,然后程序就可以用查到的信息开始做任何事了。当然也存在一定的缺点:a、SAX事件没处理文档各元素之间的关系,须自己编写程序才能处理元素的关系。b、SAX事件是暂时的,如果需要对XML中的数据进行多次访问,那么就需要对该文档进行多次解析。综合上述因素,同时考虑本设计方案的过程定义文档自身特点:文档规模相对都不是很大,过程定义解析后,对各个元素的操作要求高,必须知道每个元素之间的关系,所以决定采用针对编程人员操作比较方便的DOM4J和JAXP相结合的方式,对过程定义解析提供接口。
在具有的实现过程中,设计方案对过程定义中包含在XPDL_Schema规范中的元素通过DOM4J进行解析,采用名称空间识别的办法(父元素和子元素必须在同一名称空间中,才可以通过父元素查找相应的子元素),完成对内存树的操作,而针对定义在‘工作流过程’元素的扩展属性内的‘事务’元素,方案采用JAXP进行解析,直接通过元素的标记进行判断,不考虑名称空间。通过两个接口的结合调用,实现定义的工作流过程解析工作。
本方案的过程定义解析实现的部分代码如下:
(1)用DOM4J接口完成过程定义中主要元素的解析:
public DefPackage parse(InputStream in)
throws XPDLParserException, IOException
{ //通过过程定义文件的一个输入流完成XPDL的过程定义到Java Object Model的转化
SAXReader reader = new SAXReader();
Document document = reader.read(in);
//将树结构的文档的头元素值赋给packageElement元素
Element packageElement = document.getRootElement();
//创建包
Package HeaderpackageHeade=createPackageHeader
(Util.child(packageElement, "PackageHeader"));
………………………………………..
//转化包中的‘WorkflowProcesses’元素结构=>java中的WorkflowProcesses类实例
loadWorkflowProcesses(pkg.getWorkflowProcesses(), Util.child(packageElement, "WorkflowProcesses"));
}
public static Element child(Element element, String name){
//返回父元素‘element’下名称为‘name’的子元素
注:父元素和子元素的名称空间必须相同
return element.element(new QName(name, element.getNamespace()));
}
(2)用JAXP完成不同名称空间下‘事务’(transactions)元素的过程解析:
public ArrayList parse(InputStream in){
Document doc = this.getDocumentByStream(in);
NodeList workflowProcesses= root.getElementsByTagName
("xpdl:WorkflowProcesses");
Element workflowProcess = (Element)workflowProcesses.item(0);
//得到‘workflowProcess’下名称为‘Transactions’的子元素
注:JAXP支持与父元素不同名称空间的子元素的查找
NodeList transactionsList= workflowProcess.getElementsByTagName
(""xsd:Transactions");………………… …………………………………….
}
5 流程版本的解决方案
5.1 实现的功能
(1)运行过程中对过程定义的改变,确保原流程继续正常运行。
(2)流程管理员指定过程定义的版本,引擎根据版本号,调用、解析相应的过程定义,实现流程实例的运行。
5.2 实现的思路
(1)过程定义的改变,使得过程定义文件中的元素的属性或其子元素的个数改变,不同版本过程定义的保存形式一般有两种情况:a、修改原来的过程定义文件。b、另外生成一个xpdl过程定义文件保存改变后的过程定义。对于第一种情况,经营流程的改变可以在过程定义的WorkflowProcesses的元素中添加不同的子元素“WorkflowProcess”,根据“WorkflowProcess”中属性‘Id’和‘Name’定义不同版本的流程。采用这种方式保存,在过程定义加载时就有两种方法,一种是每次都加载所有版本的过程定义,这种情况下,如果有的版本的流程很少使用,就会占用不必要的内存;另一种情况是通过版本的判断进行指定加载,这种情况下,如果一个经营流程下面本来就有多个“WorkflowProcess”,这样在就会给版本判断带来困难。对于第二种情况,由于不同版本的过程定义保存在不同的文件中,在进行过程定义加载时,就可以通过过程定义的文件名字来快速加载不同版本地过程定义,从而避免了第一种保存方式的弊端。通过以上比较,本设计方案采用第二种方法来保存不同版本的流程。
(2)关于部分执行的过程实例,如果流程版本更改,其继续运行时采用哪种版本,设计的想法是如果创建新的流程的时候,使用更改后新的版本,以前没有执行完成的过程实例还是按照原来的版本执行。根据这种思路,主要需要解决的问题就是:如果流程没有中断,即package包没有重新载入,package中的内容是不会改变的,这样原流程可以继续进行,关键是如果原流程挂起,引擎重新启动,两个流程采用的package包就不是同一个,如何判断应采用哪个就是关键。其解决思路:a、把每个流程实例的packageId作为不同版本的xpdl过程定义文件唯一标志。b、在引擎启动后,将每个流程实例的packageId保存到数据库中,并且把流程的最新版本的packageId作为引擎启动后默认加载包的Id。c、引擎启动后,首先加载默认的Package,然后到数据库中查找没有完成的流程实例,如果其packageId的值不是默认的packageId值,加载其相应的Package,这样原流程恢复后就可以根据packageId采用原来的package包。d、新的流程也可以采用人工指定的方式加载指定的package包。
5.3 开发约定、应用接口
(1)xpdl过程文件的命名规则,每个过程文件名字为其过程定义中“Package Id”的值,扩展名称为xml,例如Package Id="dreambike_300",其相应的过程文件名称为:“dreambike_300.xml”;其中‘300’为版本号,版本号用3位数字表示,针对与同一个流程,新的版本号必须比旧的版本号大。
(2)过程文件的加载。第一部分是com.rongji.workflow.demo.dreambike.BikeOrderManager中相应函数的解释:a、public String getLastestxml(String files[]):得到最新版本的xpdl过程文件的名称。b、private void addLastestPackge():加载最新版本的xpdl过程文件。c、private void addPackgebyId(String pkgId):加载指定版本的xpdl过程文件。第二部分是com.rongji.workflow.engine. WfEngine中相应函数的解释:a、public ArrayList getPackageIdAndProcId():得到数据库中没有完成的过程实例的PackageId和ProcessId,b、public void recorverFormerProcessInstanse(String packageId,String procId):恢复指定packageId和processId的过程实例。c、public void addRepositoryManager(WfRepositoryManager ar,String processName):加载流程的应用到应用仓库中,同时加载多个过程定义时,应用仓库中针对不同的应用只保存一份。
参考文献
[1]河江斌.工作流管理系统研究及其在土地登记中的应用[D].河海大学,2004,11:19.
[2]WFMC.Workflow Management Coalition Specification: Applications Invocation Interface. Document Number WFMC-TC-1014 v0.9,Jul96.
作者单位
福州大学数学与计算机科学学院福建省福州市350116