2008年10月28日星期二

jBPM用户指南-CH06


第六章 配置

配置jBPM的最简单的方式是将jbpm.cfg.xml配置文件放到classpath的根目录中。如果那个文件不能作为一个资源被找到,包含在 jbpm库中的默认的最小化配置将被使用。记住最小化的配置没有任何持久化的配置信息。
jBPM配置通过org.jbpm.JbpmConfiguration类来描述。最简单的获取JbpmConfiguration的方式是使用单态实例方法JbpmContext.getInstance();
如果你想从另一个来源装载配置信息,你可以使用JbpmConfiguration。parseXxxx方法。

static JbpmConfinguration jbpmConfiguration = JbpmConfinguration.getInstance();
JbpmConfiguration是线程安全的,因此可以是一个静态成员。所有的线程都可以用JbpmConfiguration作为JbpmContext对象的一个工厂。一个JbpmContext典型地代表了一个事务。JbpmContext使得在一个context块中的服务可用。一个context块看起来就像这样:
try {
// This is what we call a context block.
// Here you can perform workflow operations

} finally {
jbpmContext.close();
}
JbpmContext使得一系列的服务和配置对于JBPM可用。这些服务在jbpm.cfg.xml配置文件中配置,并使得jBPM可以运行在任何Java环境且在那个环境中无论使用哪个服务都可用。
这是一个JbpmContext典型的配置,你可以在src/config.files/jbpm.cfg.xml中找到它:
<jbpm-configuration>

<jbpm-context>
<service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
<service name='message' factory='org.jbpm.msg.db.DbMessageServiceFactory' />
<service name='scheduler' factory='org.jbpm.scheduler.db.DbSchedulerServiceFactory' />
<service name='logging' factory='org.jbpm.logging.db.DbLoggingServiceFactory' />
<service name='authentication' factory='org.jbpm.security.authentication.DefaultAuthenticationServiceFactory' />
</jbpm-context>

<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name='resource.hibernate.cfg.xml' value='hibernate.cfg.xml' />
<!-- <string name='resource.hibernate.properties' value='hibernate.properties' /> -->
<string name='resource.business.calendar' value='org/jbpm/calendar/jbpm.business.calendar.properties' />
<string name='resource.default.modules' value='org/jbpm/graph/def/jbpm.default.modules.properties' />
<string name='resource.converter' value='org/jbpm/db/hibernate/jbpm.converter.properties' />
<string name='resource.action.types' value='org/jbpm/graph/action/action.types.xml' />
<string name='resource.node.types' value='org/jbpm/graph/node/node.types.xml' />
<string name='resource.parsers' value='org/jbpm/jpdl/par/jbpm.parsers.xml' />
<string name='resource.varmapping' value='org/jbpm/context/exe/jbpm.varmapping.xml' />

<int name='jbpm.byte.block.size' value="1024" singleton="true" />
<bean name='jbpm.task.instance.factory' class='org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl' singleton='true' />
<bean name='jbpm.variable.resolver' class='org.jbpm.jpdl.el.impl.JbpmVariableResolver' singleton='true' />
<long name='jbpm.msg.wait.timout' value='5000' singleton='true' />

</jbpm-configuration>
在文件中你可以看到3个部分:
第一个片段用一些列服务的实现配置了jbpm上下文,可能的配置选项在涵盖特定服务实现的章节中有介绍。
第二部分是对配置资源的所有的引用映射。如果你想定制其中一个配置文件,那么资源引用可以被更新。典型的,你制作默认配置的一个拷贝,这个默认配置是在jbpm-3.x.jar中,切放置在classpath的某个地方。那么你在这个文件更新了引用,那么jbpm将使用你定制的那个配置文件的版本。
第三部分是在jbpm使用的一些别称(miscallanious)。这些配置选项在涵盖了指定主题的章节有描述。

服务的默认配置集合的目标定位是最小的web应用环境和最小的依赖。持久化服务将获取一个jdbc连接,且所有其他的服务将使用相同的连接来完成他们的服务。所以你的所有工作流操作将被集中到1个JDBC连接事务内,而不再需要一个事务管理器。

JbpmContext包含了大多数流程操作的方便的方法:
public void deployProcessDefinition(ProcessDefinition processDefinition) {...}//流程部署
public List getTaskList() {...}//获取任务列表
public List getTaskList(String actorId) {...}//通过actorId获取任务列表
public List getGroupTaskList(List actorIds) {...}//通过一个actorId列表来获取一组工作列表
public TaskInstance loadTaskInstance(long taskInstanceId) {...}//装载任务实例
public TaskInstance loadTaskInstanceForUpdate(long taskInstanceId) {...}//装载任务实例以更新
public Token loadToken(long tokenId) {...}//装载令牌
public Token loadTokenForUpdate(long tokenId) {...}//装载令牌以更新
public ProcessInstance loadProcessInstance(long processInstanceId) {...}//装载流程实例
public ProcessInstance loadProcessInstanceForUpdate(long processInstanceId) {...}//装载流程实例以更新
public ProcessInstance newProcessInstance(String processDefinitionName) {...}//产生新的流程实例
public void save(ProcessInstance processInstance) {...}//保存流程实例
public void save(Token token) {...}//保存令牌
public void save(TaskInstance taskInstance) {...}//保存任务实例
public void setRollbackOnly() {...}//设置回滚

注意XxxForUpdate方法将注册装载的对象以自动保存,所以你不必再明确地调用一个save方法。

可以指定多个jbpm-context,但另一方面你必须确保每一个jbpm-context必须拥有一个唯一的name属性。命名的上下文可以用JbpmConfiguration.createContext(String name)来获取。
一个service项指定了一个服务的名字和服务的service factory。这个服务将只在用JbpmContext.getServices().getService(String name)方法要求创建时被创建。
工厂也可以被指定为一个element元素来替代attribute属性,那可能需要在factory对象注入一些配置信息。负责解析XML,创建和写入对象的组件成为对象工厂(the object factory)。

6.1. Configuration properties

jbpm.byte.block.size: 文件附件和二进制变量存储在数据库中。不是作为二进制大对象(blob)存储,而是作为一些固定大小的二进制对象列表。这是为了达到在不同的数据库中提高移植性和提高jBPM的总体嵌入性。这个参数控制了这个固定长度块的大小。

jbpm.task.instance.factory: 为了定制任务实例的创建方式,在这个属性中指定了完全限定类名。当你想要定制TaskInstance bean且要添加一个新的properties到TaskIntance Bean时可能需要这么做。(请看Section 11.10,“定制任务实例”),指定的类需要实现org.jbpm.taskmgmt.TaskIntanceFactory.

jbpm.variable.resolver: 为了定制jBPM将要查找的在JSF形式的表达式第一项的方法。

jbpm.msg.wait.timout: To customize the time inbetween polls for messages.

6.2. Configuration 文件
这是在jBPM可定制的所有配置文件的简短描述。

6.2.1. Hibernate cfg xml 文件
这个文件包含hibernate配置和hibernate映射资源文件的引用。
位置:hibernate.cfg.xml除非特别指定,否则在jbpm.properties文件的jbpm.hibernate.cfg.xml属性指定。在jbpm工程,默认的hibernate.cfg.xml文件处在src/config.files/hibernate.cfg.xml目录下。


6.2.2. Hibernate 查询配置文件
该文件包含hibernate查询,这些查询在jBPM 会话 org.jbpm.db.*Session中使用。
位置:org/jbpm/db/hibernate.queries.hbm.xml

6.2.3. 节点类型配置文件
该文件XML节点元素到节点实现类的映射。
位置:org/jbpm/graph/node/node.types.xml


6.2.4. Action 类型配置文件
该文件包含了XML action元素到Action实现类的映射
位置:org/jbpm/graph/action/action.types.xml


6.2.5. 业务日历配置文件
包含了业务时间和自由时间的配置
位置:org/jbpm/calendar/jbpm.business.calendar.properties
Location: org/jbpm/calendar/jbpm.business.calendar.properties

6.2.6. 变量映射配置文件
指定流程变量(java对象)的值如何被转化为实例变量来存储到jbpm数据库中。
位置:org/jbpm/context/exe/jbpm.varmapping.xml


6.2.7. Converter 配置文件
指定id-to-classname映射。id存储在数据库中。org.jbpm.db.hibernate.ConverterEnumType用来映射id到单独的对象。
位置:org/jbpm/db/hibernate/jbpm.converter.properties


6.2.8. 默认 modules 配置文件
指定那个modules被默认添加到一个新的流程定义中。
位置: org/jbpm/graph/def/jbpm.default.modules.properties

6.2.9. 流程归档解析配置文件
指定流程归档解析安排。
位置: org/jbpm/jpdl/par/jbpm.parsers.xml

6.3.对象工厂(Object factory)
对象工厂能通过beans-like XML配置文件来创建对象。配置文件指定对象应该如何被创建、配置和连接在一起来形成一个完整的对象图。对象工厂可以注入配置和其他bean到一个bean中。
在最简单的形式下,对象工厂能通过如下的配置来创建基本类型和java beans。

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance"/>
<string name="greeting">hello world</string>
<int name="answer">42</int>
<boolean name="javaisold">true</boolean>
<float name="percentage">10.2</float>
<double name="salary">100000000.32</double>
<char name="java">j</char>
<null name="dusttodust" />
</beans>

---------------------------------------------------------

ObjectFactory of = ObjectFactory.parseXmlFromAbove();
assertEquals(TaskInstance.class, of.getNewObject("task").getClass());
assertEquals("hello world", of.getNewObject("greeting"));
assertEquals(new Integer(42), of.getNewObject("answer"));
assertEquals(Boolean.TRUE, of.getNewObject("javaisold"));
assertEquals(new Float(10.2), of.getNewObject("percentage"));
assertEquals(new Double(100000000.32), of.getNewObject("salary"));
assertEquals(new Character('j'), of.getNewObject("java"));
assertNull(of.getNewObject("dusttodust"));
Also you can configure lists:

<beans>
<list name="numbers">
<string>one</string>
<string>two</string>
<string>three</string>
</list>
</beans>
和 maps

<beans>
<map name="numbers">
<entry><key><int>1</int></key><value><string>one</string></value></entry>
<entry><key><int>2</int></key><value><string>two</string></value></entry>
<entry><key><int>3</int></key><value><string>three</string></value></entry>
</map>
</beans>
Beans可以用直接的域注入和通过property的setter被配置。

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<field name="name"><string>do dishes</string></field>
<property name="actorId"><string>theotherguy</string></property>
</bean>
</beans>
Beans可以被引用。引用它的对象不必是一个bean,它也可以是一个字符串,整数或任何其它的对象。

<beans>
<bean name="a" class="org.jbpm.A" />
<ref name="b" bean="a" />
</beans>
Beans可以用任何构造器进行构造。

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor>
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
...或通过bean上的一个对象方法

<beans>
<bean name="taskFactory"
class="org.jbpm.UnexistingTaskInstanceFactory"
singleton="true"/>

<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor factory="taskFactory" method="createTask" >
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
...或用一个类上的静态工厂方法...

<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor factory-class="org.jbpm.UnexistingTaskInstanceFactory" method="createTask" >
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
每一个命名的对象可以用属性siglenton="true"来标记为单态。那意味着一个给定的对象工厂对于每一个请求将总是返回同一个对象。注意,单台在两个不同的对象工厂中不被共享。

单态的特征带来了getObject和getNewObject方法的区别。典型的对象工厂用户将使用getNewObject.这意味着第首先对象工厂的对象缓存在一个新的对象图构造之前被清空,存储在对象工厂缓存的非单态对象允许被一个对象共享引用。单态对象缓存不同于普通的对象缓存。单台缓存从不被清空,而普通对象缓存在每一个getNewObject方法启动时被清空。


jBPM用户指南-CH05


第五章 部署


jBPM是一个可嵌入的BPM引擎,意思是你可以把jPBM嵌入到你自己的java工程,而不是安装一个独立的产品并与它集成。最关键的一方面是它使最小化依赖变为可能。本章讨论jbpm库和他们的依赖关系。
5.1.Java运行时环境
jPBM3要求J2SE1.4.2及以上版本。

5.2.jBPM库
jbpm-[version].jar是带有核心jbpm功能的库。

jbpm-identity-[version].jar是包含身份验证组件可选库,在11.11"身份验证组件"描述。

5.3.第三方库
在一个最小的部署中,你可以只把commons-logging和dom4j库放到你的classpath中就可以用jBPM创建并运行流程。但持久化流程到数据库中是不被支持的。如果你不使用流程xml解析,dom4j库是可以移除的,但你必须用编程的方式来构建你的对象图。


















LibraryUsageDescriptionDirectory
commons-logging.jarlogging in jbpm and hibernateThe jBPM code logs to commons logging. The commons logging library can be configured to dispatch the logs to e.g. java 1.4 logging, log4j, ... See the apache commons user guide for more information on how to configure commons logging. if you're used to log4j, the easiest way is to put the log4j lib and a log4j.properties in the classpath. commons logging will automatically detect this and use that configuration. lib/jboss (from jboss 4.0.3)
dom4j-1.6.1.jarprocess definitions and hibernate persistencexml parsinglib/jboss (from jboss 4.0.3)


一个jBPM的典型部署将包含流程定义和流程执行的持久化存储。在那种情况下,jBPM在hibernate之外不必依赖任何依赖库。
当然,hibernate要求的苦取决于你所用的环境和特性。更多的细节请参考hibernate文档。下一个表给出了普通的独立POJO开发环境的指示。
jBPM最终和hibernate3.1一起发布。但它可以和3.0.x.一起工作。在那种情况下,你必须在hibernate.queries.hbm.xml配置文件中更新一点hibernate查询。关于定制查询的更多信息,请看7.6节“定制查询”。
表 5.2.
































































LibraryUsageDescriptionDirectory
hibernate3.jarhibernate persistencethe best O/R mapperlib/hibernate (hibernate 3.1 final)
antlr-2.7.5H3.jarused in query parsing by hibernate persistenceparser librarylib/jboss (from jboss 4.0.3)
cglib-2.1_2jboss.jarhibernate persistencereflection library used for hibernate proxieslib/jboss (from jboss 4.0.3)
commons-collections.jarhibernate persistencelib/jboss (from jboss 4.0.3)
ehcache-1.1.jar hibernate persistence (in the default configuration)second level cache implementation. When configuring a different cache provider for hibernate, this library is not required.lib/hibernate
jaxen-1.1-beta-4.jarprocess definitions and hibernate persistenceXPath library (used by dom4j)lib/hibernate
jdbc2_0-stdext.jar hibernate persistencelib/hibernate
asm.jarhibernate persistenceasm byte code librarylib/hibernate
asm-attrs.jarhibernate persistenceasm byte code librarylib/hibernate



beanshell库是可选的。如果你不包含它,你将不能集成beanshell到你的jbpm流程语言中,且你将会得到一条消息说jbpm不能装载Script类,同时,script元素将不可用。

Table 5.3.













LibraryUsageDescriptionDirectory
bsh-1.3.0.jarbeanshell script interpreterOnly used in the script's and decision's. When you don't use these process elements, the beanshell lib can be removed, but then you have to comment out the Script.hbm.xml mapping line in the hibernate.cfg.xmllib/jboss

jBPM用户指南-CH04



第四章 面向图形的编程

4.1. 介绍
本章可以看作是JBoss jBPM的表现。它给出了当前策略背后的思想和蓝图以及JBoss jBPM工程未来方向的完整概览。这个蓝图和传统的途径有很大的不同。
首先,我们相信很多流程语言。有很多不同的环境和不同的目的要求有他们自己指定的流程语言。
其次,面向图形编程是一种新的实现技术为所有的机遇图形的流程语言提供服务。
我们进步的最有利的地方在于他定义了所有类型的流程语言的一种基本技术。
当前软件开发依赖于各领域指定的语言。一个典型的Java开发者将使用相当一部分领域制定的语言。各种框架里面的工程中的XML文件可以被认为是特定领域语言。




图4.1. 基于图形语言的位置

用于工作流、BPM、流程编制和页面流的特定领域语言是基于有向图的执行。其他的诸如Hibernate映射文件,ioc-configuration就不是。 面向图形编程是所有基于执行图的特定领域语言的基础。
面向图形编程是一种非常简单的技术,它描述了在平面的面向对象编程语言之上图形如何被定义以和执行。
在4.5节,“应用领域”,我们见爱你个覆盖大部分常用的能被使用工作流、BPM、流程编制和页面流实现的流程语言。

4.1.1. 特定领域语言
每种流程语言都可以被认为是特定领域语言(DSL)。DSL透视图在流程语言如何与普通的OOP关联上给了开发者很好的洞察力。
这一节我们将单纯地集中注意力在编程环境上以有个印象。面向图形编程包含了连续整套的BPM产品,从API库到完整丰富的BPM套件产品。BPM套件产品是以业务流程为中心的完整的软件开发环境。在这种类型的产品中,用编程语言编写代码能够被尽量地避免。
特定领域语言的一个重要的方面是每种语言有一个特定的文法。该文法能够被表达为一个域模型。在java中,这些可以是类,方法,字段域,构造器...在jPDL中,可以是节点,转换,动作(Action)...在Rules中,可以是条件,concequence...
DSL的主要想法是,开发者用一种指定的语言创造东西。IDE是围绕一种语言的语法构建的。因此,可以有很多不同的编辑器来进行创作。比如jPDL了流程有一个图形的编辑器和一个XML源代码视图编辑器。同样的,也有不同的方法来存储相同的作品:比如,对于jPDL,可以是一个流程XML文件或是一个节点和转换对象图的序列化对象。其他的(理论上的)例子是java:你可以在系统中使用java类文件格式。当用户打开编辑器,源代码就产生好了。当用保存时,编译的类也同时被保存。
10年前,最大一部分的开发人员用在编写代码。现在随着特定领域语言的学习和使用,这一切改变了。这一趋势将持续下去,其结果是开发人员在同一个宿主平台上将在框架和编写软件之间有更大的选择余地。JBoss SEAM在这个方向上迈出了一大步。
这些语言中有些是基于图形执行的。比如:用于java工作流的jPDL,用于流程编制服务的BPEL,SEAM页面流…等等。面向图形编程是所有这些特定领域语言类型的公共基础。
以后,对于每种语言,开发人员将可以选择最适合他/她的编辑器。比如:核心程序员可能因为工作起来更快而喜欢在源文件格式下编辑Java代码。但缺乏经验的java程序员可能选择一个点击的编辑器来组合一个最终形成一个java类的功能。java源代码编辑可能显得更加复杂。

从其他方式来看这些特定领域语言可以从架构软件的角度。面向对象编程通过组合方法和数据来增加结构。面向关联编程 (AOP)增加了提取交错关联的方法。依赖注入(DI) 和反向控制 (IoC) 框架增加了图形对象的连线的简便性。 同样的基于图形的执行语言(这里介绍的)能通过围绕图行的执行架构你的部分软件项目的方式来有效的解决复杂性。
关于特定领域元的最初解释可以在Martin Fowler的bliki找到。但其背后更细致的阐述请看Martin关于Language Workbenches的论文。

4.1.2. 基于图形语言的特性
有很多的机遇图形的流程语言。在环境和侧重点上有很大的区别。例如,BPEL用来作为建立在企业服务总线(ESB)之上的基于XML的服务编制组件。而页面流流程语言则定也了在一个web应用中页面是如何导航的。这些是两种完全不同的环境。
尽管所有的这些都有区别,但你将发现在几乎每个流程语言中都有两个特性:支持等待状态和图形化表示。这一点没有一致性性,因为它的两个特性在普通的OOP编程语言(如Java)中不能得到充分支持。
面向图形编程是用面向对象编程语言实现这两个特性的一种技术。面向图形编程在面向对象编程的依赖性意味着所有具体的实现于面向图形编程之上的流程语言,必须用OOP开发。但这并不意味着流程语言本身暴露了底层OO的实现。比如:BPEL和OO编程没有任何关联,并且它能够在面向图形编程上实现。

4.1.2.1. 支持等待状态
像Java这样的强制性语言用来表达有一个系统执行的一系列指令。没有等待指令。强制性语言可以完美地描述诸如在服务器中的一个请求响应周期。系统连续地执行指令序列直到请求被处理并且响应结束。
但一个这样的请求往往是一个比想象中更大的一部分。比如:客户端提交了一个订单,该订单要被订单管理员验证。正式批准后,消息必须进入ERP系统。很多发到服务器的请求也是想象中一样大的一部分。
所以流程语言是用来描述更大场景的语言。我们这里必须区分的一个重要的区别是一个系统内部的执行和多个系统的协议。面向图表语言的实现技术仅仅用于在一台机器上执行的程序语言。
所以一个编制流程按照一个系统描述所有的场景。比如:当客户端提交订单时,一个流程开始。流程的下一步是订单管理员的批准。所以系统必须在订单管理员的任务列表中增加一个入口,并等待直到订单管理员提供请求输入。当输入被接受,流程继续执行。现在消息被发送到ERP系统,系统将再次等待直到响应发回。
所以,为了为一个系统描述全部的场景,我们需要一种机制来处理等待状态。
在大多数的应用领域,在等待状态期间执行必须被持久化。这就是为什么阻塞线程不够充分。聪明的Java程序员会想到Object.wait()和Object.notify()方法。这些将用来模拟等待状态但问题是线程不是持久化的。、

Continuations是一种使得线程(及上下文变量)可持久化的技术。这足够可以用来解决等待状态问题。但和我们在下节要讨论的一样,图形化表示在很多应用领域也一样重要。Countinuation是一种基于命令编程的技术,所以它不适合图形化表示。

所以支持等待状态的一个重要方面是执行需要被持续化。不同的应用领域可能有持久化这么一个执行的不同需求。对于大部分工作流、BPM和编排应用,执行需要被持久化到一个关系数据库中。典型的,一个流程执行中的状态转换将和数据库的一个转换相一致。

4.1.2.2. 图形化表示
软件开发的一些方面可以从基于图形的进步受益。业务流程管理是基于图形语言最明显的一个应用。在那个例子中,业务分析和开发人员的沟通把基于图形的业务流程图表作为通用语言进行改良。请看4.5.1节“业务流程管理(BPM)”
另一个可以从图形化表示受益的方面是页面流。在这种情况下,页面、导航和动作命令被展示并用图形化表示链接到一起。
在面向图表编程中,我们的目标是使用图表来代表一些格式的业务程序的执行。那与UML类图实例有明显的区别,UML类图用来表示OO数据结构的静态模型。

同时图形化表示可以被看作是OO编程的一个缺失的特性。没有一种好的方法能图形化地表示OO程序的执行。因此在OO程序和图形化视图之间没有直接的联系。

在面向图形的编程中,图形的描述是中心,而且它是像描述流程图的XML文件的一个实际的软件作品。由于图形化视图实质上是软件的一部分,因此它往往是同步的。从图形化需求到软件设计并不需要手册翻译。软件是围绕着图形构造的。


4.2. 面向图形编程
我们这里介绍的是基于图形的执行语言的一种实现技术。该技术是基于图形的runtime解释。其他的图形执行技术是基于消息队列和代码生成的。
本节将解释关于流程执行如何在OO编程语言上实现的策略。对于那些熟悉设计模式的人来说,这是命令模式和职责链模式的一种结合。

我们将尽量从最简单的模型开始,并一点点地扩展。

4.2.1. 图形化结构
首先,图形的结构由Node和Transition类来描绘。transition有一个方向,故节点(nodes)有离开和到达转换的行为。



Figure 4.2. Node and Transition classes

节点是一个命令,并有一个执行方法。Node的子类支持覆盖execute方法来实现那个节点类型的特定行为。

4.2.2. 执行
我们定义在图形结构上的执行模型看起来与有限状态机或UML状态图类似。实际上面向图形编程可以被用来实现那些类型的行为,但它还可以做得更多。
执行(亦即token)由叫做Executeion的类来描绘。执行有一个指向当前节点的引用。




Figure 4.3. The Execution class

转换可以通过take方法把执行从一个源节点传递到目标节点。

Figure 4.4. The Transition take method

当执行到达一个节点,那个节点就被执行。Node的execute方法同样负责传递执行。传递执行意味着一个节点可以传递抵达某个节点的执行越过一个即将离开的转换到下一个节点。


Figure 4.5. The Node execute method

当一个节点的execute方法不传递执行,它就成了等待状态。同样的,当一个新的执行被创建,它将被初始化在某个起始状态并等待一个事件。
事件是用来发给执行的,并触发执行开始移动。当事件发给一个当前节点要离开的转换相关的执行,该执行就会带起转换。然后执行将继续传递直到它进入另一个处于等待状态的节点。


Figure 4.6. The Execution event method


4.2.3. 流程语言
现在我们已经可以看到两种被支持的特性:等待状态和图形化表示。在等待状态期间,Execution只是指向图形中的一个节点。流程图和Execution两者都能被持久化:比如使用如Hibernate的O/R映射保存到数据库或序列化图形对象到一个文件中。同样的你可以看到图形中的节点和转换在图形化表示中有直接的关联。你也能看到一个图表/“业务程序定义”的节点和转向。因此,这也是一个与可视化表示的直接结合。 Also you can see that the nodes and transitions form a graph and hence there is a direct coupling with a graphical representation.
流程语言只不过是节点-实现的一个集合。 每个节点-实现与一个流程结构相一致。流程结构的准确的行为是通过覆盖execute方法来实现的。
这里我们展示一个具有4个流程结构的流程语言的例子:一个起始状态,一个决定,一个任务和结束状态。这个例子与jPDL流程语言无关。



Figure 4.7. An example process language

具体的节点对象现在可以用来在我们流程语言的示例中创建流程图了。



Figure 4.8. An example process


当为该流程创建了一个新的执行,我们把执行定位到起始状态开始。如果执行没有收到任何事件,那么执行将保持定位在起始状态。



Figure 4.9. A new execution

现在我们看看当一个事件被启动时会发生什么。在这个初始的情形,我们启动将与默认转换保持一致的默认事件。
这可以通过调用executeion对象的event方法完成。event方法将开始查找默认的离开转换并通过调用转换之上的take方法吧执行传递到转换,并把自身作为参数传递。
转换将传递执行到decision节点并调用execute方法。我们假设decision的execute实现履行计算功能并且决定通过发送‘yes’事件给执行来传递执行。那将导致执行结束‘yes’转换而继续并且执行将到达‘doubleCheck’任务。
什么假设doubleCheck(二次核对)的任务节点的执行实现增加了一个条目到核对者的任务列表并在以后不传递执行来等待核对者的输入。
现在,执行将仍旧定位在doubleCheck任务节点。所有的嵌套呼叫将在直到原始的event方法返回时开始返回。



Figure 4.10. An execution in the 'doubleCheck' wait state

4.2.4. Actions

在某些应用领域中,必须有一种方法来包含程序逻辑的执行而不用为它引进一个节点。比如在业务流程管理中这就是非常重要的一方面。业务分析管理图形化表示而开发人员负责使之执行。如果开发人员必须改变图表来包含业务分析员不感兴趣的技术细节,那将是难以接受的。
Action还是一个带有execute方法的命令。Action可以和事件绑定。
在执行的运行过程中有2个基本的事件由Node类启动:node-leave和node-enter。随着导致转换被带起的事件,这已经给了往图形的执行注入程序逻辑很大的自由。


Figure 4.11. Actions that are normally hidden from the graphical view

每个时间可以与一个actions列表相结合。随着事件的触发,所有的action都将被执行。

4.2.5. 代码示例
为了让人们学习到面向图形编程的规则,我们用少于130行的代码开发了这4个类。你可以阅读代码来获得一个认识或你可以进行改造并实现你自己的节点类型。

这是实例代码:

* Execution.java
* Node.java
* Transition.java
* Action.java

你也可以下载全部的(297KB)源代码项目并自己改造。它包含一个eclipse工程,你只要导入已有工程便可以使用。也有一系列的展示基本流程执行的测试,高级的图表执行概念在下一节介绍。

4.3. 高级面向图形编程扩展
前一节用最简单的形式介绍了普通的面向图形编程模型。本周将讨论基于图形语言的各种概念以及面向图形编程如何被使用或扩展以符合这些需求。
4.3.1. Process variables
4.3.1. 流程变量

流程变量保存一个流程执行的上下文数据。在一个保险索赔流程,“索赔总额”、“核准总额”和“拨付标志”就是很好的流程变量的例子。在很多情况下,它们与类的成员(域)相似。

面向图形编程能通过联合一个 与流程结合的键-值对集合,随着流程变量支持 简单地扩展,并发的执行路径和流程的组合将使事情变得有点复杂。万一在并发路径或子流程的情况下,范围规则将会定义流程变量的可见性。

工作流数据模式是一个关于各种范围的广泛的研究报告,这些范围可以应用于在子流程上下文和并发执行的流程变量。

4.3.2. 并发执行

假设你用基于图形的流程处理语言在为工作流程开发一个“sale”流程。在客户端提交订单之后,将有一系列的活动在为客户端付现金,同时也有一系列活动在为客户端运送商品项。正如你能想象的,付款活动和运送活动能够并行完成。

在这种情况下,一条执行路线将不足以跟踪整个流程状态。让我们一步步扩展面向图的编程模型并增加对并发执行的支持。

首先,我们对执行重新命名为一个执行路径,然后我们可以引进一个新的概念叫做流程执行(a process execution).流程执行描绘了一个流程的完整执行,并包含了很多执行路线。

执行路径可以被分级的排序。这意味着当一个流程执行被实例化的时候一个跟执行被创建。当这个根执行路径被分解到多个并发执行路径时,根就成了父亲,而新创建的执行路径则是根的所有的孩子。这样,一个join执行就变得相当易懂:一个join执行只要检查是否所以的兄弟执行路线已经全部指向了join节点。如果是这种情况,那么复制性路径可以回复执行,离开join节点。

尽管分层次的执行路径和join执行基于兄弟执行路径覆盖了该用例的一大部分,其他的并发行为应该在具体的环境中进行描述。比如当多条合并与一个分叉关联。在这种情形下,其他运行时数据和混合执行的组合是必须的。动作通常情况下都从图形视图中隐藏。


图 4.12. Actions that are normally hidden from the graphical view


执行的多条并发路径经常和多线程编程混淆在一起,尤其在工作流和BPM上下文中,他们有很大的不同。一个流程指定一个状态机。考虑一下,一个状态机通常总是处于稳定状态的,而状态转换是瞬时的。你可以通过查看状态转换引起的事件来解释执行的并发路径。并发执行意味着可以被处理的事件与两个并发的执行路径没有关联。现在,我们假设流程执行中的状态转换与一个数据库转换相关联,(as explained in Section 4.3.6, “Persistence and Transactions”),那么你可以看到多线程编程实际上没有被要求来支持执行的并发路径。

4.3.3. 流程的制作

流程创造可以包含一个子流程作为一个超流程(上级流程、父流程)的一部分。这个高级特性使得增加流程建模的抽取成为可能。对于业务分析来说,这个特性对于将模型分解为子模块的解决办法是很重要的。
主要的是路是超流程在图中有一个节点描绘了一个子流程的完整的执行。当一个执行进入超流程的子流程节点,有很多事情要考虑:
*首先,一个子流程的执行被创建。
*一些存储在超流程的流程变量的信息可以可选地从超流程的执行中注入到子流程执行。最为简单的方式是子流程节点被配置为:将带有从超流程的流程变量拷贝过来的一个变量集合复制到子流程变量中。
*子流程的开始节点必须只能有一个离开的转换。支持多个离开转换的流程语言必须有一种机制来从那些基于超流程变量的转换中选择一个转换。
*子流程的执行通过发送一个相当于它的开始状态的默认离开转换的事件来被启动。

在子流程进入一个等待状态后,超流程执行将被指向子流程节点而子流程执行将被指向某个等待状态。
当子流程执行完成,超流程执行可以继续。那是一下几个方面必须被考虑:
*流程变量信息必须从子流程执行被拷贝回来,放到超流程执行中。
*超流程执行将继续。典型的,流程语言允许在子流程节点只有一个离开转换。这样,超流程执行会在默认单独的离开转换上被传播。
*当一个子流程节点被允许多于一个离开转换时,必须引入一种机制开选择一个离开转换。这个选择可以给予子流程执行的变量,或是子流程的结束状态(一个典型的状态机可以有多个结束状态)。

未完成:bpel有一个未明确的子处理概念,没有明确指出...

4.3.4. 同步执行
4.3.5. 异步执行
4.3.6. 持久化与事务
4.3.7. 服务和环境


2008年10月13日星期一

jBPM用户指南-CH03


第三章 指南

本指南将向你展示用JPDL构造的基本流程和管理运行时执行的API的用法。
指南的格式是解释一组的例子。这些例子将集中讲解一个详细的主题并包含一些扩展的内容。这些例子也可以在jBPM下载包中的src/java.examples目录找到。
最好的学习方式是创建一个工程,并在给出的例子的基础上改造,进行试验。

Eclipse用户入门:下载jbpm-3.0-[version].zip包并解压到你的系统中。然后按照Eclipse菜单的"File" --> "Import..." --> "Existing Project into Workspace"顺序进行操作。点击“下一步”(Next),然后浏览jBPM根目录并点击“完成”(Finish)按钮。现在在你的工作区就有了jbpm.3工程了。现在你可以在src/java.examples/...目录下发现本指南的示例。当你打开这些示例,你可以通过菜单"Run" --> "Run As..." --> "JUnit Test"来运行它们。
jBPM包括一个图形设计工具来制作例子中所示的XML文件。你可以在2.1节“Downloadables Overview”(下载概览)中找到图形设计器的下载指示。
学习本指南,你不需要具备图形设计工具。


3.1. Hello World 的例子
流程定义是由节点和转换构成的有向图。本Hello world流程由有3个节点。为了看看这些小单位如何组合在一起,我们将不利用设计器工具从一个简单的流程开始。下图展示了hello world流程的图形化表示。


图3.1. hello world 流程图

public void testHelloWorldProcess() {
// 该方法展示了一个流程定义和一个流程定义执行。
// 流程定义有3个节点:一个未命名的开始状态,一个‘s’状态,一个命名为‘end’的结束状态。
// 下一行将一个xml文本片段解析为一个ProcessDefinition(流程定义)。
// ProcessDefinition是一个被表现为java对象的流程的正式描述。

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

//下一行创建了一个流程定义的执行,构造之后,流程执行就有一个定位到开始状态的执行主路径(=root token)
ProcessInstance processInstance = new ProcessInstance(processDefinition);

//构造之后,流程执行就有一个定位到开始状态的执行主路径(=root token)
Token token = processInstance.getRootToken();//获取执行主路径

// 同样的,在构造完之后,执行主路径定位到流程定义的开始状态。
assertSame(processDefinition.getStartState(), token.getNode());
// 现在开始流程的执行,离开开始状态,结束默认的转换。
// 现在开始流程的执行,留下开始状态作为默认的转换。
// Let's start the process execution, leaving the start-state
// over its default transition.
token.signal();//发信号
// signal方法将阻塞直到流程执行进入一个等待(wait)状态。

// 流程执行将进入第一个等待状态‘s’。所以执行的主路径现在定位到状态‘s’。
assertSame(processDefinition.getNode("s"), token.getNode());

// 我们再送另外一个信号signal. 这将继续执行离开状态's' 结束默认的转换(transition)
// 现在我们发另一个信号。这将通过留下状态‘s’作为默认转换而挂起执行。
// Let's send another signal. This will resume execution by
// leaving the state 's' over its default transition.
token.signal();//发另一个信号

// 现在signal方法返回,因为流程实例已经到达结束状态。
assertSame(processDefinition.getNode("end"), token.getNode());
}


3.2. 数据库的例子
jBPM的一个基本特性是它能够在当流程处于等待状态时,将流程执行持久化存储到数据库中。下一个例子将向你展示如何在jBPM数据库中存储一个流程实例。例子给出一个将会发生的上下文.分开的方法因不同的用户代码块而创建。比如:一个web应用中的用户代码段开始了一个流程并将其执行持久化存储到数据库中。然后,一个消息驱动bean从数据库中装载了这个流程实例并挂起该流程的执行。
更多的jBPM持久化内容将在 第七章:持久化 找得到。

public class HelloWorldDbTest extends TestCase {

static JbpmConfiguration jbpmConfiguration = null;

static {
// 和这个例子相同的配置文件可以在‘src/config.files’目录找到。典型的,配置信息保存在jbpm.cfg.xml资源文件中,
// 但在这里我们将配置信息当成一个XML字符串来传递。
// 首先,我们静态地创建一个JbpmConfiguration。JbpmConfiguration能被用在系统的所有线程中,
// 这就是为什么我们能够安全地使之成为静态的。

jbpmConfiguration = JbpmConfiguration.parseXmlString(
"<jbpm-configuration>" +
// jbpm-context机制将jbpm核心引擎从环境中与jbpm使用的服务分开。
" <jbpm-context>" +
" <service name='persistence' " +
" factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
" </jbpm-context>" +
//同样的,所有jbpm使用的资源文件都从jbpm.cfg.xml引用。


" <string name='resource.hibernate.cfg.xml' " +
" value='hibernate.cfg.xml' />" +
" <string name='resource.business.calendar' " +
" value='org/jbpm/calendar/jbpm.business.calendar.properties' />" +
" <string name='resource.default.modules' " +
" value='org/jbpm/graph/def/jbpm.default.modules.properties' />" +
" <string name='resource.converter' " +
" value='org/jbpm/db/hibernate/jbpm.converter.properties' />" +
" <string name='resource.action.types' " +
" value='org/jbpm/graph/action/action.types.xml' />" +
" <string name='resource.node.types' " +
" value='org/jbpm/graph/node/node.types.xml' />" +
" <string name='resource.varmapping' " +
" value='org/jbpm/context/exe/jbpm.varmapping.xml' />" +
"</jbpm-configuration>"
);
}

//JUnit的setUp()方法
public void setUp() {
jbpmConfiguration.createSchema();
}
//JUnit的tearDown()方法
public void tearDown() {
jbpmConfiguration.dropSchema();
}

public void testSimplePersistence() {
// 在下面调用的3个方法之间,所有的数据都通过数据库进行传输。在这个单元测试中,
// 因为我们想测试一个完整的想定的流程,所以这3个方法依次执行。但在实际情况中,
// 这些方法的展示的是对一个服务的不同请求。
// 由于我们以一个干净的,空的内存数据库开始,我们需要首先部署流程。在实际情况中,
// 这一步将由流程开发者来完成。
deployProcessDefinition();//部署流程定义ProcessDefinition

// 假设当用户在web应用中提交了一个表单时我们想启动一个流程实例(=process execution) ...
processInstanceIsCreatedWhenUserSubmitsWebappForm();
// 然后,等下异步消息到来时,执行必须继续。
theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();//当收到异步消息时流程实例继续
}

//部署流程定义ProcessDefinition
public void deployProcessDefinition() {
// 该测试展示了流程定义以及一个流程定义的执行。流程定义有3个节点:一个未命名的起始状态,一个状态‘s’
// 以及一个名为‘end’的结束状态。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition name='hello world'>" +
" <start-state name='start'>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 查找上面配置的pojo持久化上下文构造器
// Lookup the pojo persistence context-builder that is configured above
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
// 在数据库中部署流程定义
jbpmContext.deployProcessDefinition(processDefinition);

} finally {
// 销毁pojo持久化上下文。这包括刷新插入流程定义到数据库的SQL语句。(flush后事务将提交)
jbpmContext.close();
}
}

// 当用户提交web应用Form时流程实例被创建
public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
// 本方法中的代码应该放在struts的action中或是JSF的managed bean中。
// 查找上面配置的pojo持久化上下文构造器(context-builder)
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

GraphSession graphSession = jbpmContext.getGraphSession();

ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("hello world");
// 当我们从数据库中获取了processDefinition后,我们可以像在hello world的例子(没有持久化存储)
// 一样创建这个流程定义的一个执行。
ProcessInstance processInstance = new ProcessInstance(processDefinition);

Token token = processInstance.getRootToken();
assertEquals("start", token.getNode().getName());
// 让我们来启动流程的执行
token.signal(); //发信号
// 现在流程处于状态‘s'。
assertEquals("s", token.getNode().getName());
// 现在流程实例已经被保存到数据库中。所以该流程的执行的当前状态已经被存储到数据库中
jbpmContext.save(processInstance);

// 下面的方法将从数据库取回流程并通过提供另外的信号来挂起执行。
// The method below will get the process instance back out
// of the database and resume execution by providing another
// external signal.

} finally {
// 拆卸pojo持久化上下文。
jbpmContext.close();
}
}

//当异步消息收到时流程实例继续
public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
// 该方法中的代码可以是消息驱动BEAN的内容
// 查找上面配置的pojo持久化上下文构造器
// Lookup the pojo persistence context-builder that is configured above
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

GraphSession graphSession = jbpmContext.getGraphSession();
// 首先,我们需要从数据库取回流程实例
// 这里有很多选项可以知道我们正在处理的是哪个流程。
// 这个简单测试用例最简单的地方是只要去查找完整的流程实例列表。
// 那将给我们唯一的一个结果。所以我们来查找流程定义。
ProcessDefinition processDefinition = graphSession.findLatestProcessDefinition("hello world");
// 现在我们搜索这个流程定义的所有流程实例
List processInstances = graphSession.findProcessInstances(processDefinition.getId());
// 因为我们知道该单元测试的上下文中,只有一个执行。(所以这里使用get(0))
// 在实际情况下,processInstanceId能够从到达的消息或用户作出的一个选择中抽取出来

ProcessInstance processInstance =
(ProcessInstance) processInstances.get(0);
// 现在我们可以继续执行。注意:流程实例processInstance委派信号到执行的主路径(= root token)。
processInstance.signal();//发信号
// 在发信号之后,我们知道流程的执行已到达结束状态
assertTrue(processInstance.hasEnded());
// 现在我们可以更新执行的状态到数据库中了。
jbpmContext.save(processInstance);

} finally {
// 拆卸pojo流程实例持久化上下文。
jbpmContext.close();
}
}
}




3.3. 上下文的例子:流程变量
当流程执行时,流程变量包含了上下文信息。流程变量与java.util.Map把变量名映射到值类似,都是java对象。流程变量作为流程实例的一部分持久保存。为了使事情更加简单,在这个例子中,我们将只展示操作变量的API,而不持久化保存。

更多关于变量的信息请看 第10章 Context

// 这个例子同样从hello world流程开始。这次甚至不用修改。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

ProcessInstance processInstance =
new ProcessInstance(processDefinition);
// 从流程实例中获取上下文实例来与流程变量共事

ContextInstance contextInstance =
processInstance.getContextInstance();
// 在流程离开开始状态之前,我们将在流程实例的上下文中设置一些流程变量。
contextInstance.setVariable("amount", new Integer(500));
contextInstance.setVariable("reason", "i met my deadline");
// 从现在开始,这些变量就与流程实例关联在一起了。
// 现在流程变量可以由这里展示的API通过用户代码来访问了,但同样在这个动作和节点执行当中。
// 流程变量同样作为流程实例的一部分存储到数据库中。
processInstance.signal();//发信号

// 变量可以通过上下文实例访问。
assertEquals(new Integer(500),
contextInstance.getVariable("amount"));
assertEquals("i met my deadline",
contextInstance.getVariable("reason"));


3.4. 任务分配的例子
下一个例子中,我们将展示你如何才能够分配一个任务给一个用户。由于jBPM工作流引擎和组织模型的分离,用来计算参与者的表达式语言将一直很受限制。因此,你必须指定AssignmentHandler的一个实现来包含任务参与者的计算。

//测试任务分配
public void testTaskAssignment() {
// 下面展示的流程基于hello world流程。状态节点被task-node(状态节点)替换了。任务节点是JPDL的一个节点,
// 它描述了在流程能够继续执行之前的一个等待状态和产生要被完成的(一个或多个)任务

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition name='the baby process'>" +
" <start-state>" +
" <transition name='baby cries' to='t' />" +
" </start-state>" +
" <task-node name='t'>" +
" <task name='change nappy'>" +
" <assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' />" +
" </task>" +
" <transition to='end' />" +
" </task-node>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 创建流程定义的一个执行。
ProcessInstance processInstance = new ProcessInstance(processDefinition);
Token token = processInstance.getRootToken();
// 现在开始流程的执行,离开起始状态,结束它的默认转换。
token.signal();//发信号
// signal()方法将阻塞直到流程执行进入一个等待状态。在这种情况下,指的是task-node。
assertSame(processDefinition.getNode("t"), token.getNode());

// 当执行到达task-node,任务“change nappy”被创建,同时NappyAssignmentHandler被调用,来决定任务该分配给谁。
// NappyAssignmentHandler返回“papa”

// 在实际的环境中,任务将用org.jbpm.db.TaskMgmtSession从数据库获取。
// 由于在这个例子中我们不想包含复杂的持久化过程,我们只是获取该流程实例的第一个任务实例。
//(我们知道在这个想定的测试中任务只有一个)

TaskInstance taskInstance = (TaskInstance)processInstance
.getTaskMgmtInstance()
.getTaskInstances()
.iterator().next();
// 现在,我们检查一下任务实例是否真的赋给了“papa”
assertEquals("papa", taskInstance.getActorId() );
// 现在,我们假设“papa”已经完成了他的职责并标记任务为完成。
taskInstance.end();
// 由于这是要做的最后(唯一)一个任务,该任务的完成触发了流程实例执行的继续。
assertSame(processDefinition.getNode("end"), token.getNode());
}

3.5. 定制 action 的例子
Actions 是绑定你的自定义java代码到jBPM流程的一种机制。Actions能够联合组建的节点(如果他们在流程的图形化表示中相关的话)。或者actions也可以被放在诸如带走一个转换、离开一个节点或进入一个节点之类的事件中。在这种情况下,actions不是图形化表示的一部分,但当在一个运行时流程执行触发了事件的时候actions会被执行。
在我们要开始使用我们的例子前先来看看action的实现:MyActionHandler。这个action处理器实现没有真正做很多事情,它只是将boolean类型的变量isExecuted设置为true。变量isExecuted是静态的,所以它不但不只在action handler可以访问,而且从要检查它的值的其他action也可以访问。
更多关于 actions 的内容可以从 9.5节 “Actions”找到。

// MyActionHandler 描绘了一个类,该类能在jBPM流程执行期间执行一些用户代码。
public class MyActionHandler implements ActionHandler {
// 在每个测试之前(在setUp方法中),isExecuted成员变量将被设置为false。
public static boolean isExecuted = false;

// action将设置isExecuted为true,故单元测试可以显示action在何时执行。
public void execute(ExecutionContext executionContext) {
isExecuted = true;
}
}

如前所述,在每个测试之前,我们设置静态域MyActionHandler.isExecuted为false;

// 每个测试将以设置MyActionHandler的静态成员isExecuted为false开始;
public void setUp() {
MyActionHandler.isExecuted = false;
}

我们将在一个转换中开始一个action

// 测试转换Action
public void testTransitionAction() {
// 下一个流程是hello world流程的一个变量。
// 我们在从起始状态到结束状态的转换之上加了一个action。本测试的目的是展示集成java代码到jBPM流程中是多么的简单。

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </transition>" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

// 我们为流程定义启动一个新的执行
ProcessInstance processInstance = new ProcessInstance(processDefinition);

// 下一个信号将导致执行离开起始状态而进入状态‘s’
processInstance.signal();

// 这里表明MyActionHandler还没有被执行。
assertFalse(MyActionHandler.isExecuted);

// 故执行的主路径位于状态‘s’
assertSame(processDefinition.getNode("s"),
processInstance.getRootToken().getNode());

// 下一个信号将触发root token的执行。该token将带走转换和action,并且该action在signal方法调用期间被执行。
processInstance.signal();

// 这里我们可以看到在signal方法调用期间MyActionHandler被执行了。
assertTrue(MyActionHandler.isExecuted);
}

下一个例子展示同样的action,但现在这个action分别放于enter-node和leave-node事件中。注意:节点和转换相比有多于一个的事件类型,转换只有一个事件。因此放置于节点的action应该被放在一个事件元素中。

ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <event type='node-enter'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </event>" +
" <event type='node-leave'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </event>" +
" <transition to='end'/>" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);

ProcessInstance processInstance = new ProcessInstance(processDefinition);

assertFalse(MyActionHandler.isExecuted);
// 下一个信号将导致执行离开起始状态并进入状态‘s’。所以状态‘s’被进入因而action被执行。
processInstance.signal();
assertTrue(MyActionHandler.isExecuted);

// 现在重置MyActionHandler.isExecuted
MyActionHandler.isExecuted = false;

// 下一个信号将触发执行离开状态‘s’。所以action将再次被执行一次。
processInstance.signal();
// 看,结果就是这样.
assertTrue(MyActionHandler.isExecuted);

2008年10月10日星期五

jBPM用户指南-CH02


第二章 入门

本章将带你从第一步获取JBoss jBPM到提供立即启动运行JBoss jBPM的初始指示。

2.1.下载包概览
下面列出的现在可用的不同jBPM包。每个包包含了一个或多个可下载的文件。这些文件都伴有一份内容说明和相关的安装指令提示(如果文件可安装的话)。
下面描述的所有下载内容均可在sourceforge jbpm下载页面找到。

2.1.1. JBPM3
在sourceforge.net下载JBoss jBPM 3。这是包含了核心引擎和一些运行JBPM所需要的附加模块。

开发新手包: (jbpm-starters-kit-.zip): 如果你想对JBoss jBPM快速入门,这就是你所需要的文件。它在一个单独的下载中包含了这个包的所有其他模块并加上一个图形设计器。解开压缩包到指定的目录并阅读readme.html文件以获取更多信息和更多的安装指令。有了哥哥新手包你就可以立即从第三章开始。【指南】
核心引擎和身份组件(jbpm-.zip):该下载包含了jBPM核心引擎和用于参与者与组管理的身份组件。要开始使用它,解开压缩包到指定的目录,你可以发现指向用户指南的提示和在jbpm-文件夹的readme.html文件中描述的其他重要信息资源。
数据库扩展(jbpm-db-.zip):数据库扩展包包含了jBPM核心引擎和用于参与者与组管理的身份组件。要开始使用它,解开压缩包到指定的目录,你可以发现指向用户指南的提示和在jbpm-文件夹的readme.html文件中描述的其他重要信息资源。


2.1.2. jBPM 流程设计器
在sourceforge.net下载jBPM流程设计器。该设计器是一个Eclipse插件并允许你制作自己的流程定义并简单地进行部署。该插件可以作为一个压缩的Eclipse feature或压缩的Eclipse update site提供下载。内容上基本一致,唯一不同的地方是你的安装的方式。

Eclipse Update Site(jbpm-gpd-site-<version>.zip):如果你想确信设计器的安装能够顺利进行,我们推荐使用update site机制与一个新的Eclipse安装相结合。当然,Eclipse版本要跟下载的update site文档相匹配。要入手设计器插件,请按照文档根目录的readme.html文件的指示以成功安装GPD(图形设计器)。
Eclipse Feature (jbpm-gpd-feature-<version>.zip): 如果你厌烦了每次去刷新Eclipse安装,那么你将应付一些可能的问题,你可以尝试feature下载包。这种安装相当简单,只需要解压文档到你的Eclipse安装路径并覆盖可能先前存在的同名文件夹即可(确保plugins和features文件夹在你的Eclipse安装的同一个位置结束)。这种安装方式非常简单,但由于你已经安装了其他的features,覆盖先前存在的插件可能会让你陷入相互冲突的问题。尽管文件有相同的名字,但还可能配到这些冲突的插件版本不同,因此存在冲突的可能性。readme.html文件的安装指示中已经多次强调。
2.1.3. jBPM BPEL 扩展
在sourceforge.net下载JBoss jBPM BPEL扩展。它只包含一个文件:jbpm-bpel-.zip。要使用BPEL扩展,请参看顶层文件夹doc子目录下的用户指南。


2.2. JBoss jBPM 项目目录

专业支持: JBoss 是为该项目提供专业支持、培训和资讯服务的公司。
用户指南:你正在阅读的提供进入本项目主要切入点服务的文档。
论坛:与社区联系,提问和讨论jBPM。
wiki:额外信息,大部分由社区提供。
问题跟踪:提交BUG和特性要求。
下载:sourceforge提供的jBPM下载页。
邮件列表:用于公告的邮件列表。
javadocs:部分的下载在doc/javadoc目录。

2.3. CVS 访问
2.3.1. 匿名 CVS 访问

作为选择,根据下面的信息你可以从CVS得到JBoss jBPM。

* 连接类型: pserver
* 用户: anonymous
* 主机: anoncvs.forge.jboss.com
* 端口: 2401 (which is the default)
* 知识库路径: /cvsroot/jbpm
* 标签: :pserver:anonymous@anoncvs.forge.jboss.com:/cvsroot/jbpm

2.3.2. 开发人员 CVS 访问
为了能够进行开发者访问,你必须签署贡献者协议并且需要一个SSH KEY。更多两者的信息可以在JBoss cvs 知识库 wiki页找到。
* 连接类型: ext over ssh (extssh in eclipse)
* 用户: sf.net username or jboss username
* 主机: cvs.forge.jboss.com
* 端口: 2401 (which is the default)
* 知识库路径: /cvsroot/jbpm
* 标签: :pserver:anonymous@cvs.forge.jboss.com:/cvsroot/jbpm


jBPM用户指南-CH01


第一章:介绍

JBoss jBPM是一个灵活、可扩展的工作流管理系统。JBoss jBPM有直观的流程设计语言,可以根据任务、异步通信的等待状态、定时器、自动操作等来图形化地表示业务流程。为了将这些操作绑定在一起,JBoss jBPM拥有一套最强大的、可扩展的流程控制机制。
JBoss jBPM有着最小的依赖性,使用起来和使用普通的java库一样简单。当然也可以把它通过部署到J2EE 集群应用服务器,以用在吞吐量极为关键的环境中.
JBoss jBPM 可以同任何数据库配置,并且可以部署在任何应用服务器上.

1.1综述
核心的工作流和BPM(业务流程管理)功能被打包为一个简单的java库。这个库包含了一个用来从jBPM数据库中存储、更新和获取流程信息的服务。


图1.1 :JBPM组件概览图


1.2. The JBoss jBPM starter kit

本工具包包含了绑定到一个简单下载包的所有的jBPM组件。内容包括:
jbpm-server: 预先配置的jboss 应用服务器.
jbpm-designer: 用来图形化地创建jBPM流程的eclipse插件 .
jbpm-db: jBPM 数据库兼容包 (参看下面).
jbpm: 核心jbpm组件包括库和本手册.
jbpm-bpel: JBoss jBPM BPEL 扩展的参考实现.

预配置的JBoss应用服务器安装了以下的组件。
核心jBPM组件,打包为一个服务档案文件
集成了jBPM表的数据库:包含jBPM表和流程的默认hypersonic数据库
能由流程参与者与jBPM管理员使用的web控制台应用程序。
定时器执行的jBPM调度计划。调度程序在新手包中被配置为一个Servlet。这个servlet将产生一个线程来监控和执行定时器。
异步执行命理的jBPM命令执行器。该执行器同样配置为一个Servlet。该Servlet将产生一个线程来监视和执行命令。
一个示例流程以及部署到jBPM数据库中。

2008年10月9日星期四

jBPM用户指南-CH10

第十章 上下文


上下文与流程变量有关。流程变量是保存与流程实例相关信息的键-值对。由于上下文必须能被存储到数据库,将会稍会约束使用。

10.1.访问变量
org.jbpm.context.exe.ContextInstance作为为流程变量提供服务的中心接口。你可以像这样从流程实例获取上下文实例:
ProcessInstance processInstance = ...;
ContextInstance contextInstance = (ContextInstance) processInstance.getInstance(ContextInstance.class);
最基本的操作是:
void ContextInstance.setVariable(String variableName, Object value);
void ContextInstance.setVariable(String variableName, Object value, Token token);
Object ContextInstance.getVariable(String variableName);
Object ContextInstance.getVariable(String variableName, Token token);
变量名称为java.lang.String类型。默认情况下,jBPM支持下面几种值类型:
java.lang.String
java.lang.Boolean
java.lang.Character
java.lang.Float
java.lang.Double
java.lang.Long
java.lang.Byte
java.lang.Short
java.lang.Integer
java.util.Date
byte[]
java.io.Serializable
classes that are persistable with hibernate
空类型null值同样可以被持久化保存。
所有其他类型能正常地存储到流程变量中。但当你试图保存流程实例时将导致一个异常。
如何配置jBPM以能够在变量中存储HIBERNATE持久化对象,请参看Storing hibernate persistent objects。

10.2.变量的生命周期
变量不必再流程定义文件中(流程归档中)声明。在运行时,你可以随意放置任何对象到变量中。如果变量不存在,它将会被建立。这和普通的java.util.Map类似。
变量可以使用如下语句删除:
ContextInstance.deleteVariable(String variableName);
ContextInstance.deleteVariable(String variableName, Token token);
自动改变类型以及被支持。这意味着允许使用一个不同类型的值覆盖一个变量。当然,你应该尽力限制类型改变的数量,因为更新一个普通的列将带来更多的数据库消耗。

10.3.变量持久化
变量是流程实例的一部分。保存流程实例到数据库中,保持流程实例和数据库同步。变量由于流程实例在数据库中的保存(更新)操作而在数据在被创建、更新、和删除。更多信息请看Chapter 7, Persistence.

10.4.变量范围
每条执行路径(阅读:Token)都有它所属的流程变量集合。请求一个变量往往在一个Token(令牌)中完成。流程实例有一个令牌树(请看graph oriented programming)。当请求一个变量而没有指定一个令牌时,默认的令牌为根令牌。

变量查询递归到给定令牌的父母令牌.这种行为与编程语言的变量范围类似。

当不存在的变量被设置到令牌中,变量将被创建在根令牌。这意味着每个变量默认就是整个流程范围。为使得变量基于本令牌(局部变量),你可以用如下方法明确创建:
ContextInstance.createVariable(String name, Object value, Token token);

10.4.1. 变量重载
变量重载一为之每个执行路径能够用相同的名称拥有自己的一份变量的拷贝。他们能被独立的对待,因此可以是不同的类型。如果你在相同的转换上启动了多条并发的执行路径,变量重载将十分有趣。唯一的事情是区分哪些执行路径是它们各自的变量集合。

10.4.2.变量的覆盖
变量覆盖意味着执行的嵌套路径中的变量覆盖了执行中更为全局的变量。一般的,执行的嵌套路径与并发性有关:一般的,嵌套执行路线涉及到并发问题: 在form和join之间的执行路线是到达fork的执行路线的. 比如,如果你有变量 'contact'在流程实例范围, 你可以在嵌套的执行路线'shipping'和'billing'重写这个变量.

10.4.3.任务实例变量范围
更多关于任务实例变量的信息,请看 Section 11.4, “Task instance variables”.

10.5 瞬时变量
当流程实例被持久化到数据库中,普通变量可以同时作为流程的一部分被持久化。在某些情况下你可能需要在ige代理类中使用一个变量,但你不想把它存储到数据中。比如一个你想从jBPM外部传递数据库连接到一个代理类。这可以通过瞬时变量来完成。
瞬时变量的生命周期和流程实例的java对象相同。
由于他们的特性,瞬时变量不与令牌相关。所以对一个流程实例只有一个瞬时变量的Map。瞬时变量可以在上下文实例中通过它们自己的方法集合来访问,你不必在流程定义文件processdefinition.xml中声明。
Object ContextInstance.getTransientVariable(String name);
void ContextInstance.setTransientVariable(String name, Object value);

10.6.定制变量持久化
变量通过2个步骤被存储在数据库中:
user-java-object <---> converter <---> variable instance
变量存储在变量实例VariableInstances。VariableInstances的数量与Hibernate在数据库中的域映射。在jBPM默认的配置中,有6种VariableInstances被使用:
DateInstance (with one java.lang.Date field that is mapped to a Types.TIMESTAMP in the database)
DoubleInstance (with one java.lang.Double field that is mapped to a Types.DOUBLE in the database)
StringInstance (with one java.lang.String field that is mapped to a Types.VARCHAR in the database)
LongInstance (with one java.lang.Long field that is mapped to a Types.BIGINT in the database)
HibernateLongInstance (this is used for hibernatable types with a long id field. One java.lang.Object field is mapped as a reference to a hibernate entity in the database)
HibernateStringInstance (this is used for hibernatable types with a string id field. One java.lang.Object field is mapped as a reference to a hibernate entity in the database)

Converters在java-user-objects和能被VariableInstances存储的java对象之间进行转换。因此,当一个流程变量用:ContextInstance.setVariable(String variableName, Object value)进行设置,那么值将随意地通过Converter被转换。然后转换过的对象将被存储到VariableInstances。Converters实现了下面的接口:
public interface Converter extends Serializable {
boolean supports(Object value);
Object convert(Object o);
Object revert(Object o);
}

Converters是非强制的,Converters必须在jBPM class loader可用。
user-java-objects被转换并存储到变量实例的方式是在org/jbpm/context/exe/jbpm.varmapping.properties文件配置的。要定制这些属性文件,需要放一个修改版本在classpath的根路径中,如Section 6.2, “Configuration files” 中所述,属性文件的每行指定2或3个类名,通过空格隔开:user-java-object的类名,可选的转变器的类名和变量实例的名字。当你引用你定制的转换器,确保他们在jBPM class path中。当你引用定制的变量实例,他们必须在jBPM class path中,并且hibernate映射文件中的org/jbpm/context/exe/VariableInstance.hbm.xml必须被更新以包含VariableInstance定制的子类。
比如,看从org/jbpm/context/exe/jbpm.varmapping.xml文件截取的片段:
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.ClassNameMatcher">
<field name="className"><string value="java.lang.Boolean"></field>
</bean>
</matcher>
<converter class="org.jbpm.context.exe.converter.BooleanToStringConverter">
<variable-instance class="org.jbpm.context.exe.variableinstance.StringInstance">
</JBPM-TYPE>
该片段指定了所有java.lang.Boolean类型的对象必须使用BooleanToStringConverter转换器来转换,并且结果对象(是一个String值)将被存储到一个类型为StringInstance的变量实例对象。
如果没有指定Converter

<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.ClassNameMatcher">
<field name="className"><string value="java.lang.Long"></field>
</bean>
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.LongInstance">
</JBPM-TYPE>

则意味着放在变量中的Long对象只能被存储为一个类型为LongInstance变量实例而没有被转化。