OSWORKFLOW工作流的概念

来源:互联网 发布:金融企业财务快报软件 编辑:程序博客网 时间:2024/04/30 05:35

1. 步骤、状态和动作

工作流要描述步骤(step)、步骤的状态(status)、各个步骤之间的关系以及执行各个步骤的条件和权限,每个步骤中可以含有一个或多个动作(action),动作将会使一个步骤的状态发生改变。

对于一个执行的工作流来讲,步骤的切换是不可避免的。一个工作流在某一时刻会有一个或多个当前步骤,每个当前步骤都有一个状态值,当前步骤的状态值组成了工作流实例的状态值。一旦完成了一个步骤,那么这个步骤将不再是当前步骤(而是切换到一个新的步骤),通常一个新的当前步骤将随之建立起来,以保证工作流继续执行。完成了的步骤的最终状态值是用old-status属性指定的,这个状态值的设定将发生在切换到其他步骤之前。old-status的值可以是任意的,但在一般情况下,我们设置为Finished

切换本身是一个动作(action)的执行结果。每个步骤可以含有多个动作,究竟要载入哪个动作是由最终用户、外部事件或者triggerd的自动调用决定的。随着动作的完成,一个特定的步骤切换也将发生。动作可以被限制在用户、用户组或当前状态。每一个动作都必须包含一个unconditional result0个或多个conditional results

所以,总体来说,一个工作流由多个步骤组成。每个步骤有一个当前状态(例如:Queued, Underway, or Finished),一个步骤包含多个动作。每个步骤含有多个可以执行的动作。每个动作都有执行的条件,也有要执行的函数。动作包含有可以改变状态和当前工作流步骤的results

2. Results, Joins, and Splits

(1) Unconditional Result

对于每一个动作来讲,必须存在一个Unconditional Result。一个result是一系列指令,这些指令将告诉OSWorkFlow下一个任务要做什么。这包括使工作流从一个状态切换到另一个状态。

(2) conditional Result

conditional Resultunconditional Result的一个扩展。它需要一个或多个condition子标签。第一个为trueconditional(使用ANDOR类型),会指明发生切换的步骤,这个切换步骤的发生是由于某个用户执行了某个动作的结果导致的。

(3) 三种不同的Resultsconditional or unconditional

 --一个新的、单一的步骤和状态的组合。

 --一个分裂成两个或多个步骤和状态的组合。

 --将这个和其他的切换组合成一个新的单一的步骤和状态的组合。

 每种不同的result对应了不同的xml描述,你可以阅读http://www.opensymphony.com/osworkflow/workflow_2_6.dtd,获取更多的信息。

 注意:通常,一个split或一个join不会再导致一个split join的发生。

单一步骤和状态的结果可以这样描述:

如果状态不是Queued的话,那么第三个必要条件就是新步骤的所有者(owner)。除了可以指明下一个状态的信息之外,result也可以指定validators post-functions,这将在下面讨论。

从一个状态分裂成多个状态可以这样描述:

...

 

   

                          status="Underway" owner="${someOwner}"/>

   

                          status="Underway" owner="${someOtherOwner}"/>

 

将多个状态合并为一个状态可以这样描述:

9. Permissions and Restrictions

Permissions can be assigned to users and/or groups based on the state of the workflow instance. These permissions are unrelated to the functionality of the workflow engine, but they are useful to have for applications that implement OSWorkflow. For example, a document management system might have the permission name "file-write-permission" enabled for a particular group only during the "Document Edit" stage of the workflow. That way your application can use the API to determine if files can be modified or not. This is useful as there could be a number of states within the workflow where the "file-write-permission" is applicable, so instead of checking for specific steps or conditions, the check can simply be made for a particular permission.

 

10. Auto actions

有的时候,我们需要一些动作可以基于一些条件自动地执行。为了达到这个目的,你可以在action中加入auto="true"属性。流程将考察这个动作的条件和限制,如果条件符合,那么将执行这个动作。 Auto action是由当前的调用者执行的,所以将对该动作的调用者执行权限检查。

11. Integrating with Abstract Entities

建议在你的核心实体中,例如"Document" "Order",在内部创建一个新的属性:workflowId。这样,当新的"Document" "Order"被创建的时候,它能够和一个workflow实例关联起来。那么,你的代码可以通过OSWorkflow API查找到这个workflow实例并且得到这个workflow的信息和动作。

12. Workflow Instance State

有的时候,为整个workflow实例指定一个状态是很有帮助的,它独立于流程的执行步骤。OSWorkflow提供一些workflow实例中可以包含的"meta-states"。这些"meta-states"可以是CREATED, ACTIVATED, SUSPENDED, KILLED COMPLETED。当一个工作流实例被创建的时候,它将处于CREATED状态。然后,只要一个动作被执行,它就会自动的变成ACTIVATED状态。如果调用者没有明确地改变实例的状态,工作流将一直保持这个状态直到工作流结束。当工作流不可能再执行任何其他的动作的时候,工作流将自动的变成COMPLETED状态。

 

然而,当工作流处于ACTIVATED状态的时候,调用者可以终止或挂起这个工作流(设置工作流的状态为KILLED SUSPENDED)。一个终止了的工作流将不能再执行任何动作,而且将永远保持着终止状态。一个被挂起了的工作流会被冻结,他也不能执行任何的动作,除非它的状态再变成ACTIVATED

 

二、工作流中的函数

OSWorkflow支持下面的函数:

1. Java-based Functions(基于Java的函数)

基于Java的函数必须要实现com.opensymphony.workflow.FunctionProvider接口。这个接口只有一个函数——execute,这个函数有三个参数

--transientVars Map

transientVars Map:是客户端代码调用Workflow.doAction()时传进来的。这个参数可以基于用户的不同的输入使函数有不同的行为。

这个参数也包含了一些特别变量,这些变量对于访问workflow的信息是很有帮助的。它也包含了所有的在Registers中配置的变量和下面两种特别的变量:entry (com.opensymphony.workflow.spi.WorkflowEntry) and context (com.opensymphony.workflow.WorkflowContext)

--The args Map

args Map是一个包含在所有的标签中的标签的Map。这些参数都是String类型的。这意味着this is ${someVar} 标签中定义的参数将在arg Map中通过"foo",可以映射到"this is [contents of someVar]"字串。

--propertySet

propertySet包含所有的在workflow实例中持久化的变量。

Java-based的函数适用于以下类型:

(1) class

对于一个类的函数,类加载器一定得知道你的函数所属的类的名字。这可以通过class.name参数来完成:

      

              com.acme.FooFunction

              The message is ${message}

      

(2) jndi

JNDI函数就象类函数一样,唯一的不同是JNDI函数的对象一定是已经在JNDI树中存在了,这里需要jndi.location参数:

      

              java:/FooFunction

              The message is ${message}

      

(3) remote-ejb   

远程EJBs可以在OSWorkflow中作为一个函数使用。EJB的远程接口一定要扩展com.opensymphony.workflow.FunctionProviderRemote,这里需要ejb.location参数:

      

              java:/comp/env/FooEJB

              The message is ${message}

      

(4) local-ejb

本地EJBs与远程EJBs的不同在于,本地EJBs要扩展com.opensymphony.workflow.FunctionProvider接口,例如:

      

              java:/comp/env/FooEJB

              The message is ${message}

      

 

2. BeanShell Functions

OSWorkflow支持BeanShell作为一个scripting语言。到http://www.beanshell.org/上,你可以获得更多的BeanShell的信息。

BeanShell函数的类型一定要指定为beanshell,还需要一个参数,名字叫script。这个参数的值实际上就是要执行的script,例如:

             

              System.out.println("Hello, World!");

      

在表达式中始终存在三个变量,entrycontextstoreentry变量是一个实现了com.opensymphony.workflow.spi.WorkflowEntry接口的对象,它代表workflow实例。context变量是com.opensymphony.workflow.WorkflowContext类型的对象,它允许BeanShell函数回滚事务或决定调用者的名字。store变量是com.opensymphony.workflow.WorkflowStore类型的对象,它允许函数访问workflow的持久化存储区。

和上面Java-based函数一样,也要使用三个变量transientVars, argspropertySet,例如:

             

              propertySet.setString("world", "Earth");

      

             

              System.out.println("Hello,"+propertySet.getString("world"));

      

这两个scripts的输出是"Hello,Earth"。这是因为任何存储在propertySet中的变量都会被持久化,以便在后面的该workflow中的函数使用。

3. BSF Functions (perlscript, vbscript, javascript)

OSWorkflow还支持一种Bean Scripting Framework的函数。BSFIBM AlphaWorks小组的项目,它允许通用的Script语言,如VBScript, Perlscript, Python, and JavaScript在通用的环境中运行。这意味着,在OSWorkflow中你可以使用BSF支持的任何一种语言来编写你的函数:

       foo.pl

       0

       0

      

              print $bsf->lookupBean("propertySet").getString("foo");

      

上面的代码将获得propertySet,然后打印出keyfoo的对象的值。在BeanShell函数中的缺省范围的相同的变量,可以在你的BSF script代码中获得。关于如何在你所选择的语言中获取变量,请阅读BSF用户手册。

4. Utility Functions

OSWorkflow本身附带一些很实用的工具函数,这些函数都实现了com.opensymphony.workflow.FunctionProvider接口。要获取更详细的信息,请阅读这些工具函数的javadoc文档。下面是每个函数的简单描述,你在com.opensymphony.workflow.util包中可以找到所有的类。

(1) Caller

用当前动作的执行者名字设置持久化变量caller

(2) WebWorkExecutor

执行一个WebWork函数并且在动作结束时存储旧的ActionContext

(3) EJBInvoker

调用一个EJBsession bean方法。

(4) JMSMessage

发送一个TextMessage给一个JMStopic queue

(5) MostRecentOwner

用最近指定的步骤的所有者的名字来设置持久化变量mostRecentOwner。可选特性可以在有所有者时将变量设置为nothing,或者返回一个内部错误。

(6) ScheduleJob

可以调度一个Trigger函数在某时执行。同时支持cron表达式和简单的repeat/delay counts

(7) UnschduleJob

删除一个ScheduleJob和所有与之联系的triggers函数。在workflow的状态发生变化而且你不再希望ScheduleJob再执行的时候是很有用的。

(8) SendEmail

给一个或多个用户发送邮件。

三、OSWorkflowValidators

OSWorkflow的函数一样,OSWorkflowvalidators也有三种不同的形式:Java-based, BeanShellBSFJava-basedValidators必须实现com.opensymphony.workflow.Validator接口(或者如果通过远程EJB的话,要实现com.opensymphony.workflow.ValidatorRemote接口)。在所有的Java-based validators中,都要抛出InvalidInputException异常。

但是在BeanShell BSF的实现中,情况稍微有点不同,因为在scripts中抛出的异常无法在Java运行时环境中传播出去。为了达到这个目的,BeanShellBSF所返回的值要作为错误消息返回,逻辑是这样的:

--如果返回的是一个InvalidInputException对象,那么这个对象会立即被抛出到客户端。

--如果返回的是一个Map,那么这个Map被用于InvalidInputException中的 错误/错误消息对。

--如果返回的是一个String[],那么偶数值被用做关键字(key),奇数值被用做所对应的值,这样就可以构造出上面的所述的Map

--如果不是上面的三种情况,返回值将被转换为一个String对象,并且作为一个错误消息。

四、OSWorkflowRegisters

OSWorkflow中的Register是一个运行时的变量,它能够动态的注册到workflow的定义文件中。Registers在很多场合都是很有用的。例如:你想提供让workflow在它的描述符文件中就能访问一个实体的方法。这时,你就可以定义一个Register来封装这个实体。如果这个实体是一个本地的session EJB的话,你就要使用com.opensymphony.workflow.util.ejb.local.LocalEJBRegister注册类。例如,在后面的post-function中,你就可以访问这个实体,并且可以通过beanshell script来调用这个实体中的方法。

Registers也有三种实现方式:Java-based, BeanShellBSF.

1. Java-based

Java-based registers 必须实现com.opensymphony.workflow.Register接口(或者如果是远程ejb的话,你要实现com.opensymphony.workflow.RegisterRemote接口)

2. BeanShell and BSF registers

通过script返回的值或对象将是你注册的对象。

注:在registers的接口中,你只需要一个args Map参数就行了,这是因为registers的调用根本不管用户的输入。

3. 例子:

下面的例子将说明register的功能和用途。在这里register被用于一个简单的日志register,它有一个可访问的变量"log",这个log变量可以在可以在整个工作流的生命期内被访问。日志记录器可以做很多有用的工作,比如说将工作流的实例id加入到日志记录中。我们在workflow的描述符文件的顶层定义register

   

      com.opensymphony.workflow.util.LogRegister

      true

   

从代码中可以看到,我们创建了一个名为logLogRegister,还指定了一个值为true的参数addInstanceId

我们可以在workflow描述符文件中的任何地方使用这个变量。例如:

  transientVars.get("log").info("function called");

上面的函数将输出"function called",同时在输出前附加了workflow的实例id

五、Conditions

BSFBeanshellscript中,有一个特别的对象叫做"jn"。这个变量是com.opensymphony.workflow.JoinNodes类的一个实例,被用于join-conditions中。除此之外,conditionsfunctions的不同在于,conditions必须返回一个truefalse的值。这个值可以是一个"true""false"的字串,也可以是一个"true""false"的布尔值,甚至是一个含有toString()的函数,其返回值为"true""false"的对象。

每个condition一定是作为一个conditions的子标签被定义的。当你使用"AND"类型,所有的condition标签的值必须都是"true",整个conditions才能是true。否则,整个conditions将返回"false"。如果你使用"OR"类型,那么只要有一个condition标签的值为"true",整个conditions就为true,而只有当所有的condition标签的值必须都是"false",整个conditions才能是false。如果你想要更复杂的逻辑判断,那么你就要自己考虑使用ConditionConditionRemote接口、BeanShell或者是BSF来实现。注意:如果在conditions标签中只含有一个condition的话,类型可以省略。

下面是OSWorkflow自带的一些标准的conditions

  --OSUserGroupCondition - 使用OSUser来判断调用者是否在参数"group"中。

  --StatusCondition - 判断当前步骤的状态是否与参数"status"相同。

  --AllowOwnerOnlyCondition - 如果调用者是指定的步骤的所有者的话,那么只返回true,如果没有指明步骤的话,就返回当前步骤。

  --DenyOwnerCondition - AllowOwnerOnlyCondition功能相反。

六、使用OSWorkflowAPI

1. 接口选择

OSWorkflow提供了com.opensymphony.workflow.Workflow接口的多个实现类,你可以在你的程序中直接使用。

(1) BasicWorkflow

BasicWorkflow不支持事务处理,但是可以通过外覆BasicWorkflow的方式来支持事务,这要以来于你的持久化的实现。BasicWorkflow通过下面的方式创建:

              Workflow wf = new BasicWorkflow(username);

username是当前请求的用户名。

(2) EJBWorkflow

EJBWorkflow使用EJB容器来管理事务。这是在ejb-jar.xml文件中配置的。它是这样建立的:

              Workflow wf = new EJBWorkflow();

这里不需要指定用户名,因为一旦用户被授权,它会从EJB容器中自动将用户名载入。

(3) OfbizWorkflow

OfbizWorkflowBasicWorkflow唯一不同的地方在于,通过ofbizTransactionUtil调用可以支持事务处理。

2. 创建一个新的工作流

下面简单介绍了如何使用OSWorkflowAPI来创建一个新的工作流。首先应该创建定义工作流的文件。然后,你的程序必须知道初始化步骤的值,以便进行流程实例的初始化。在你初始化一个工作流之前,你必须创建它,这样的话,你就可以得到这个工作流的引用。下面是例程代码:

       Workflow wf = new BasicWorkflow(username);

       HashMap inputs = new HashMap();

       inputs.put("docTitle", request.getParameter("title"));

       wf.initialize("workflowName", 1, inputs);

注意:一般情况下,你应该使用一个Workflow类型的引用,而不应该是BasicWorkflow的引用。

3. 执行动作

OSWorkflow中,执行一个动作非常简单:

       Workflow wf = new BasicWorkflow(username);

       HashMap inputs = new HashMap();

       inputs.put("docTitle", request.getParameter("title"));

       long id = Long.parseLong(request.getParameter("workflowId"));

       wf.doAction(id, 1, inputs);

4. 查询

OSWorkflow 2.6中,引入了新的ExpressionQuery API

注意:不是所有的workflow的存储都支持查询。目前,HibernateJDBC和内存存储都支持查询。然而,hibernate存储不支持混合类型的查询(例如:一个历史和当前步骤的查询信息的查询)。要执行查询,就要建立一个WorkflowExpressionQuery对象,然后在WorkflowExpressionQuery对象中调用查询方法。

下面是一个查询的例子:

//Get all workflow entry ID's for which the owner is 'testuser'

new WorkflowExpressionQuery(

  new FieldExpression(FieldExpression.OWNER,             //Check the OWNER field

  FieldExpression.CURRENT_STEPS,                             //Look in the current steps context

  FieldExpression.EQUALS,                                           //check equality

  "testuser"));                                                                //the equality value is 'testuser'

//Get all workflow entry ID's that have the name 'myworkflow'

new WorkflowExpressionQuery(

  new FieldExpression(FieldExpression.NAME,                //Check the NAME field

  FieldExpression.ENTRY,                                              //Look in the entries context

  FieldExpression.EQUALS,                                           //Check equality

  'myworkflow'))                                                          //equality value is 'myworkflow'

 

下面是一个嵌套查询的例子:

// Get all finished workflow entries where the current owner is 'testuser'

Expression queryLeft = new FieldExpression(

  FieldExpression.OWNER,

  FieldExpression.CURRENT_STEPS,

  FieldExpression.EQUALS, 'testuser');

Expression queryRight = new FieldExpression(

  FieldExpression.STATUS,

  FieldExpression.CURRENT_STEPS,

  FieldExpression.EQUALS,

  "Finished",

  true);

WorkflowExpressionQuery query = new WorkflowExpressionQuery(

  new NestedExpression(new Expression[] {queryLeft, queryRight},

  NestedExpression.AND));

Finally, here is an example of a mixed-context query. Note that this query is not supported by the Hibernate workflow store.

 

//Get all workflow entries that were finished in the past

//or are currently marked finished

Expression queryLeft = new FieldExpression(

  FieldExpression.FINISH_DATE,

  FieldExpression.HISTORY_STEPS,

  FieldExpression.LT, new Date());

Expression queryRight = new FieldExpression(

  FieldExpression.STATUS,

  FieldExpression.CURRENT_STEPS,

  FieldExpression.EQUALS, "Finished");

WorkflowExpressionQuery query = new WorkflowExpressionQuery(

  new NestedExpression(new Expression[] {queryLeft, queryRight},

  NestedExpression.OR));