创建描述符
首先,让我们来定义工作流。你可以使用任何你想要的名字来命名工作流。工作流的定义在一个xml文件中被详细说明,每个工作流对应一个XML文件。让我们以新建一个名称为“myworkflow.xml”的文件开始,这个文件的样板文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC
"-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
"http://www.opensymphony.com/osworkflow/workflow_2_7.dtd">
<workflow>
<initial-actions>
...
</initial-actions>
<steps>
...
</steps>
</workflow>
首先是标准的详细的XML报头(xml header),注意的是OSWorkflow将会通过这些指定的DTD来验证XML文件的正确性,因此工作流的定义必须正确,你可以使用绝大多数可以高亮显示相应的错误的工具来编辑它。
actions and steps (步骤和动作)
下面我们来定义初始化动作(initial-actions)和步骤(steps)。第一个需要理解的重要概念是osworkflow的steps(步骤)和actions(动作)。steps(步骤)只是工作流所处的位置,如一个简单的工作流过程中,它从一个步骤(step)流转到另外一个步骤(有时会停留在同一个步骤)。举例来说,在一个文档管理系统中,步骤名称可能是“First Draft – 草案初稿”,“Edit Stage -编辑阶段”,“At publisher - 出版商”等。
动作指定了可能在一个特殊步骤内发生的转变,动作的结果常常是步骤的一次变更。在我们的公文管理系统中,动作的例子是,在上面我们定义的“草案初稿”这个步骤中可能有“start first draft - 开始草案初稿”和“complete first draft - 完成草案初稿”两个动作。
简单一点,步骤是“在哪里”('where it is'),动作是“可以去哪里”('where it can go')。
初始化步骤(initial-actions)是一种特殊类型的步骤,它被用来启动工作流。在一个工作流恰好开始的时候,没有状态(no state),不处在任何一个步骤之中,用户必须使用某些动作开始这个流程。这些可能开始工作流的步骤在 <initial-actions>中被定义。
在我们的例子中,我们假定只有一个简单的初始化动作'Start Workflow',在<initial-actions>里面添加如下动作定义:
<action id="1" name="Start Workflow">
<results>
<unconditional-result old-status="Finished" status="Queued" step="1"/>
</results>
</action>
这可能是最简单的一个动作类型,它仅仅定义了我们要去的步骤以及设置状态(status)的值。
工作流状态(Workflow status)
工作流状态是用来描述工作流中一个具体步骤的状态的字符串。在我们的文档管理系统中,“First Draft”(草案初稿)这一步中可能有'Underway(进行中)'和'Queued(队列中)两个状态需要照料。
我们用'Queued'表示这一条目已经排入First Draft步骤中,比如说有人已经请求编写一篇详细文档,但是还没有指定作者,那么这篇文档在'First Draft'步骤中当前的状态就是'Queued','Underway'可以被用来表示一个作者已经从队列中挑选了这篇文档来写,而且可能已经锁定了它,表明他正工作在first draft步骤中。
第一个步骤(The first step)
让我们来查看在<steps>元素(element)中怎样定义第一个步骤。我们知道有两个actions(动作),第一个动作(start first draft)是保持当前步骤不变,只是把状态改变到了'Underway';第二个action(动作)使我们移动到工作流中的下一个step(步骤),也就是finished' step(步骤)。现在让我们在<steps> element(元素)中添加如下内容:
<step id="1" name="First Draft">
<actions>
<action id="1" name="Start First Draft">
<results>
<unconditional-result old-status="Finished" status="Underway" step="1"/>
</results>
</action>
<action id="2" name="Finish First Draft">
<results>
<unconditional-result old-status="Finished" status="Queued" step="2"/>
</results>
</action>
</actions>
</step>
<step id="2" name="finished" />
上面我们看到了两个action(动作)是怎样被定义的。old-status attribute(属性)被用来表示当前状态完成之后在history table(历史表格)中是什么,在大多数的案例中,通常都是"Finished"。
上面我们定义的两个action(动作)用途比较狭窄,比如说,一个用户在调用action 1(动作1)之前就调用了action 2,很明显,草稿尚未开始,更谈不上完成草稿了。同样的,“first draft”步骤也可能被开始好多次,那是没有意义的。最后,我们也没有采取适当的措施阻止第二个用户完成第一个用户的草稿,这也是我们应该避免的。
让我们一个个来解决这些问题。首先,我们想指定当工作流处于'Queued' state(状态)的时候,一个调用者只能开始一个草稿,这能够阻止用户多次启动first draft步骤。为实现这一目的,我们需要在action(动作)中指定一个restriction(约束),restriction由一个condition(条件)组成。
Conditions(条件)
OSWorkflow有许多有用的内置条件可供使用,在这个例子中相关的条件是StatusCondition(状态条件)。Conditions也可以接受参数,通常在java文档里面有一个详细定义(如果条件使用java类实现的话)。在例子里面,status condition(状态条件)接受一个名为“'status'”的参数,它指定了以便condition(条件)通过的检查状态。如果我么查看定义这些条件所必需的的xml,这将变得更加清楚:
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Underway" step="1"/>
</results>
</action>
希望condition(条件)的概念现在是清晰的。以上条件保证了action1(动作1)只有当前状态为'Queued'的时候才能被调用,它仅仅在initial action(初始化动作)被条用之后才是正确的。
Functions(函数)
接下来,我们想定义当一个用户开始first draft(草稿初稿)之后,把它设置为'owner'。为做到这一点,需要两件事情:
1) 一个在当前环境中设置'caller'变量的函数
2) 设置'owner'属性到'caller'变量
函数是OSWorkflow的重要特色。函数基本上是在一个工作流变化中能够被完成的工作单位,那不会给工作流本身带来影响。例如,你可能有一个SendEmail函数,它可以用来在一些特定的变化发生的时候可靠地发出一个email通知。
函数也可以把变量添加到当前环境中。变量是一个指定名称的、对工作流有用的、可以被以后的函数或脚本调用的对象。
OSWorkflow提供了一些内置的有用的函数,其中一个是'Caller'函数,这个函数寻找当前调用工作流的用户,并把它放置到一个名称为'caller'的string型变量中。
既然我们想知道开始了first draft(草稿初稿)的用户,我们可以作如下修改来使用这个函数:
<action id="1" name="Start First Draft">
<pre-functions>
<function type="class">
<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished" status="Underway"
step="1" owner="${caller}"/>
</results>
</action>
综合
综合以上所有想法,现在我们有了如下定义的action(动作)1:
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished" status="Underway"
step="1" owner="${caller}"/>
</results>
</action>
Action(动作)2作同样定义:
<action id="2" name="Finish First Draft">
<restrict-to>
<conditions type="AND">
<condition type="class">
<arg
name="class.name">com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Queued" step="2"/>
</results>
</action>
这里我们制定了一个新的条件'allow ownly only',这保证了只有开始了first draft的用户才能完成它,这个状态变量也保证了只有当状态为'Underway'时,'finish first draft'才能被执行,这只有当一个用户开始了first draft(草稿初稿)之后才会发生。
把所有的放在一起,我们就有了下面完整的工作流定义:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC
"-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
"http://www.opensymphony.com/osworkflow/workflow_2_7.dtd">
<workflow>
<initial-actions>
<action id="1" name="Start Workflow">
<results>
<unconditional-result old-status="Finished" status="Queued" step="1"/>
</results>
</action>
</initial-actions>
<steps>
<step id="1" name="First Draft">
<actions>
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished" status="Underway"
step="1" owner="${caller}"/>
</results>
</action>
<action id="2" name="Finish First Draft">
<restrict-to>
<conditions type="AND">
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Queued" step="2"/>
</results>
</action>
</actions>
</step>
<step id="2" name="finished" />
</steps>
</workflow>
现在,工作流已经完整的定义完了,到了测试和验证它的时候了。