用户/群组的管理是由UserManager来完成的,它包含于一个单独的jar包内。我们可以这样使用UserManager:
UserManager um = UserManager.getInstance();
User test = um.createUser("test");
test.setPassword("test");
Group foos = um.createGroup("foos");
test.addToGroup(foos);
利用UserManager也可以实现用户验证功能:
UserManager um = UserManager.getInstance();
boolean authenticated = false;
authenticated = um.getUser(username).authenticate(password);
if (authenticated) {
session.setAttribute("username");
……
} else {
……
}
关于step执行人的跟踪,首先我们可以在创建流程的时候传入调用者(caller)名称,比如:
Workflow wf = new BasicWorkflow((String) session.getAttribute("username"));
BasicWorkflow会负责创建一个实现了WorkflowContext接口的实例,其中记录了caller的信息。利用com.opensymphony.workflow.util.Caller,可以将WorkflowContext中的caller随时植入transientVars中,以供后续的条件判断。为此,我们需要在流程定义文件中的适当位置加入如下定义(比如initial-actions中的pre-functions节点处):
<pre-functions>
<function type="class">
<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
</function>
</pre-functions>
Caller是一个FunctionProvider,其excute方法中包含了如下代码:
WorkflowContext context = (WorkflowContext) transientVars.get("context");
transientVars.put("caller", context.getCaller());
同时,我们还可以指定流程中某个step的执行人(owner),只需要在action的results节点处为其指定owner属性:
<step id="2″ name="Edit Doc">
<actions>
<action id="2″ name="Sign Up For Editing">
……
<results>
<unconditional-result old-status="Finished” status="Underway” step="2″ owner="${caller}"/>
</results>
利用caller和owner信息,我们可以在流程定义文件的condition节点中以多种形式指定限定条件,比如,利用脚本限定只允许caller为test的用户触发某结果:
<result old-status="Finished">
<condition type="beanshell">
<arg name="script">
propertySet.getString("caller").equals("test")
</arg>
</condition>
……
</result>
又比如,利用util包中的OSUserGroupCondition限定仅当caller为foos群组中的用户时,才触发action:
<action id="1″ name="Start Workflow">
……
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
<arg name="group">foos</arg>
</condition>
再比如:利用util包中的AllowOwnerOnlyCondition限定仅当caller等于owner时,才触发action:
<action id="1″ name="Start Workflow">
……
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
</condition>
OSWorkflow解读之八
WorkflowQuery及其有关查询类>>
我们知道,通常人们总是希望了解流程当前的运行状况,因此就需要工作流引擎在提供流程流转的基本功能的同时,还需要提供查询功能。在osworkflow中,查询功能是由WorkflowQuery及其相关类提供的。
WorkflowQuery提供了两种类型的构造函数:
public WorkflowQuery(int field, int type, int operator, Object value)
public WorkflowQuery(WorkflowQuery left, int operator, WorkflowQuery right)
我们可以利用第一个构造函数创建基本的WorkflowQuery实例,然后利用第二个构造函数组织装配。以查询执行者是“test”且状态是“Underway”的step实例为例:
WorkflowQuery queryLeft = new WorkflowQuery(
WorkflowQuery.OWNER, WorkflowQuery.CURRENT, WorkflowQuery.EQUALS, “test");
WorkflowQuery queryRight = new WorkflowQuery(
WorkflowQuery.STATUS, WorkflowQuery.CURRENT, WorkflowQuery.EQUALS, “Underway");
WorkflowQuery query = new WorkflowQuery(
queryLeft, WorkflowQuery.AND, queryRight);
List workflows = wf.query(query);
for (Iterator iterator = workflows.iterator(); iterator.hasNext();)
Long wfId = (Long) iterator.next();
}
装配好的查询条件,将会传入AbstractoWorkflow的query方法中,然后再由WorkflowStore的query方法执行具体查询操作。不同的WorkflowStore实例其查询方式不尽相同,以MemoryWorkflowStore为例,它将遍历所有位于cache中的流程,然后将满足条件的流程ID放入一个ArrayList中返回,查询的核心代码采用了递归调用的形式:
if (query.getLeft() == null) {
return queryBasic(entryId, query);
} else {
int operator = query.getOperator();
WorkflowQuery left = query.getLeft();
WorkflowQuery right = query.getRight();
switch (operator) {
case WorkflowQuery.AND:
return query(entryId, left) && query(entryId, right);
case WorkflowQuery.OR:
return query(entryId, left) || query(entryId, right);
case WorkflowQuery.XOR:
return query(entryId, left) ^ query(entryId, right);
}
}
这里的queryBasic再次使用了递归调用的方式,详细情况可以查看osworkflow的源代码。通过这样的方式,可以满足任意复杂的流程查询条件指定。
OSWorkflow解读之七
Descriptors>>
在osworkflow的很多地方都会看到Descriptor的使用,最重要的一个是前面提到的WorkflowDescriptor。除此以外还有,ActionDescriptor、ConditionsDescriptor、ConditionDescriptor、FunctionDescriptor、PermissionDescriptor等等。这些类均位于com.opensymphony.workflow.loader包中。它们的作用,除了提供getter方法外(类似model的角色),还负责xml文件的读取与写入。WorkflowDescriptor在执行xml文件的读写时,如果涉及具体的流程定义元素,将会交由对应的Descriptor类来完成。比如,在WorkflowDescriptor的writeXML方法中,对initial action的序列化是这么实现的:
XMLUtil.printIndent(out, indent++);
out.println("<initial-actions>");
for (int i = 0; i < initialActions.size(); i++) {
ActionDescriptor action = (ActionDescriptor) initialActions.get(i);
action.writeXML(out, indent);
}
XMLUtil.printIndent(out, –indent);
out.println("</initial-actions>");
OSWorkflow解读之六
pre function和post function>>
pre function和post function是osworkflow提供的又一特色,它为某项执行逻辑提供了前驱和后继处理,运用十分灵活。并且,osworkflow为许多元件的执行逻辑都配备了pre function和post function的调用时机。这一点也可以从AbstractWorkflow.doAction的执行逻辑中看到。可以使用和pre function和post function的元件包括:action,result/unconditional result,step,split,join。
ScriptVariableParser>>
作为osworkflow的一个util class,ScriptVariableParser的主要功能是将给定字串中的${var}替换成相应的value。这意味着我们可以在许多地方使用类似于Ant中引用property的语法,来进一步提高灵活性。比如:
<results>
<unconditional -result old-status="Finished” status="Underway” step="1″ owner="${caller}"/>
</results>
在这里,unconditional result的owner属性将被caller的实际值所替代。
TransientVars和PropertySet>>
在osworkflow的流程流转过程中,时常会用到Transient Vars和Property Set。这两个工具用来暂存一些临时信息或者在step间传递一些共享信息,比如:context信息,workflow entry信息,以及上面提到的${var}的value,等等。
Transient Vars实际上就是一个普通的Map,至于Property Set,则是opensymphony的另一个独立模块,需要单独下载jar包。与Transient Vars将信息暂存与内存不同,Property Set还支持数据库存储(JDBCPropertySet)
Register>>
为了更进一步提高灵活性,osworkflow还提供了Register功能。我们可以定义自己的Register,以执行特殊任务,并在流程定义文件中注明,该Register便会被动态注册到Transient Vars中,以备随时取用。以下便是一个典型的使用场景:
<register type="class” variable-name="log">
<arg name="class.name">com.opensymphony.workflow.util.LogRegister</arg>
<arg name="addInstanceId">true</arg>
</register>
</registers>
<arg name="script">transientVars.get("log").info("function called");</arg>
</function>
此外,为了方便使用,osworkflow的util包中还预定义了大量的Condition和FunctionProvider,以及其他的一些辅助类,比如:StatusCondition、AllowOwnerOnlyCondition、BeanShellCondition、Caller、EJBInvoker、ScheduleJob。
借助这些设施,osworkflow的扩展性、灵活性、易用性,得到了极大的体现。
OSWorkflow解读之五
Schedule>>
在osworkflow中提供了定时执行某项任务的功能,这主要得益于opensymphony的另一个项目——Quatz,该项目为工作的定期执行提供了底层设施支持。为此,我们需要引用quatrz.jar包,好在osworkflow的下载包中已经包含了该文件。
为了实现任务定时,我们需要在流程定义文件中做类似如下的配置:
<arg name="class.name">com.opensymphony.workflow.util.ScheduleJob</arg>
<arg name="triggerId">1</arg>
<arg name="jobName">testJob</arg>
<arg name="triggerName">testTrigger</arg>
<arg name="groupName">test</arg>
<arg name="repeat">10</arg>
<arg name="repeatDelay">2000</arg>
<arg name="cronExpression">0,5,10,15,20,25,30,35,40,45,50,55 * * * * ?</arg>
<arg name="username">test</arg>
<arg name="password">test</arg>
<arg name="local">true</arg>
<arg name="schedulerStart">true</arg>
</function>
ScheduleJob是一个FunctionProiver,因此具有execute方法。在该方法执行期间,ScheduleJob将会读取这些配置参数,创建好job实例(实际上是一个JobDetail实例)和trigger实例,然后启动schedule。大致流程如下:
- 根据传入的shedulerName参数,利用org.quartz.impl.StdSchedulerFactory的getScheduler方法创建sheduler实例,该实例实现了org.quartz.Scheduler接口;
- 根据传入的jobClass参数,决定创建何种Job实例,osworkflow自身提供了两种选择:WorkflowJob和LocalWorkflowJob。前者支持SOAP协议,后者则是本地调用,它们都实现了org.quartz.Job接口。
- 创建一个描述Job信息的JobDetail实例,并做好初始设置;
- 若传入参数中未指定cronExpression,则创建SimpleTrigger,并设置好startDate、endDate、repeat,否则创建CronTrigger
- 在jobDetail和trigger准备完毕后,就可以启动schedule了:
s.addJob(jobDetail, true);
s.scheduleJob(trigger);
s.start();
scheduler中应该可以同时维护多个job和trigger,当trigger的触发条件满足后,将会激活真正的job实例。由于,scheduler中只保存了jobDetail的实例,因此我猜想,job实例的真正创建是由jobDetail完成的。job实例(WorkflowJob、LocalWorkflowJob或者是其他自定义扩展类)激活后,其excute方法将会被执行。其内部的执行逻辑大体是获得指定的Workflow实例,然后执行该实例的executeTriggerFunction方法。trigger function的执行与先前在流程定义文件中所出现过的普通function大同小异。当然,我们还需要在流程定义文件中加入对trigger function的描述,大致格式如下:
<trigger-functions>
<trigger-function id="1″ >
<function>
…
</function>
</trigger-functions>
osworkflow解读之四
流程启动>>
前面分析流程流转的执行逻辑时,并没有讲到流程的启动。实际上,有了前面的基础,再加上对流程定义文件加载的过程有清晰认识之后,流程启动逻辑是很容易理解的。大致情况如下:
- 调用DefaultConfiguration的getWorkflow,传入流程名称,然后返回一个WorkflowDescriptor实例,流程定义文件的加载就是在这个时候完成的;
- 然后做一些准备工作,比如:获取WorkflowStore实例、准备好transientVars和propertySet等等;
- 调用WorkflowDescriptor的getInitialAction方法,获取initial action(如果有的话),注意此前的第一步中,流程定义文件已经成功加载;
- 调用transitionWorkflow,执行流程的流转(就是前面提到doAction执行逻辑时调用的那个重要的方法);
在transitionWorkflow方法中,与流程启动后step间的流转稍有不同的是:当发现action为initial action时,将会把流程设置为activated状态,表示流程启动。就是下面这段代码:
if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) {
changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED);
}
WorkflowStore和WorkflowEntry>>
前面提到过,在AbstractWorkflow中,包含流程流转关键逻辑的transitionWorkflow方法,会创建新的step。而这一创建工作是通过调用另一个member method实现的,也就是createNewCurrentStep。其执行逻辑大致如下:
int nextStep = theResult.getStep();
if (nextStep == -1) {
if (currentStep != null) {
nextStep = currentStep.getStepId();
}
}
…
if (currentStep != null) {
store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller());
store.moveToHistory(currentStep);
}
…
Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds);
从这段代码中,我们首先可以看到,osworkflow可以支持step节点自身再次被“激活”的行为,也就是重复执行某个step。而另一方面,随后的创建工作则是调用了store.createCurrentStep。
这个store变量就是一个实现了WorkflowStore接口的实例变量,缺省是MemoryWorkflowStore。WorkflowStore除了创建step之外,还提供了一系列find和query方法。在其内部分别保存着step的历史记录(history steps)以及当前处于“激活”状态的step(current steps),以MemoryWorkflowStore为例,分别对应了两个HashMap,而JDBCWorkflowStore则利用数据库表来记录这些信息。
实际上,WorkflowStore可以同时保存多个流程的记录,这样就可以满足同时存在多个流程的情况。为此,osworkflow提供了一个WorkflowEntry接口用来描述流程信息,其中包含了流程名称、流程ID、当前状态等等。WorkflowEntry中定义了如下几个流程状态常量:
public static final int CREATED = 0; //创建
public static final int ACTIVATED = 1; // 激活
public static final int SUSPENDED = 2; // 挂起
public static final int KILLED = 3; // 异常终止
public static final int COMPLETED = 4; // 正常结束
public static final int UNKNOWN = -1;
在WorkflowStore提供的接口方法中有如下几个方法供维护WorkflowEntry使用:
public WorkflowEntry createEntry(String workflowName) throws StoreException;
public WorkflowEntry findEntry(long entryId) throws StoreException;
public void setEntryState(long entryId, int state) throws StoreException;
具体的方法实现,各个具现类有所不同。比如。在MemoryWorkflowStore中,是通过维护一个存储SimpleWorkflowEntry实例(实现了WorkflowEntry接口)的HashMap达到目的的。
OSWorkflow解读之三
初始配置加载及工作流定义文件加载>>
AbstractWorkflow首先会取得一个Configuration实例,缺省时为DefaultConfiguration(实现了Configuration接口),该实例负责系统配置的加载。AbstractWorkflow会调用其load方法,该方法内部会查找一个名为osworkflow.xml的配置文件,并对其解析。osworkflow.xml文件中一般会指定persistence class和factory class,比如:
<osworkflow>
<persistence class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>
<factory class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
<property key="resource” value="workflows.xml” />
</factory>
</osworkflow>
load方法会动态加载这些class,还可以指定一些参数,在它们初始化的时候传入。在这里,我们指定了XMLWorkflowFactory,实际上还有其他种类的WorkflowFactory,比如JDBCWorkflowFactory、URLWorkflowFactory,它们均派生自AbstractWorkflowFactory,其共同职责是通过某种媒介加载流程定义。
以XMLWorkflowFactory为例,其相应实例在load方法内部完成初始化的过程中,将会查找一个名为workflows.xml的文件,可以在该文件中定义多个流程,每个流程指明其对应的xml定义文件。比如:
<workflows>
<workflow name="example” type="resource” location="example.xml"/>
</workflows>
在XMLWorkflowFactory内部维护了一个Map,保存着流程名称与其对应的文件路径(实际上是一个WorkflowConfig实例,不过这只是细节)。然后,DefaultConfiguration调用XMLWorkflowFactory.getWorkflow方法,并传入流程名称。
getWorkflow方法内部又会将流程加载的具体执行逻辑转交给一个名为WorkflowLoader的类(调用WorkflowLoader.load方法),由WorkflowLoader实现流程定义文件读取。不过,真正的xml文件解析是由WorkflowDescriptor类完成的。它将平面的xml流转化为osworkflow内部所使用的具有真正意义的对象。此类有很多get方法,在osworkflow的许多地方都将会用到这个类的get方法,以获取具体的对象,比如:getAction,getJoin,getStep等等。
最终,代表example.xml流程定义文件的WorkflowDescriptor实例将会被逐层返回,直至AbstractWorkflow。至此,流程定义文件加载完毕,整个初始化过程也就基本完成了。
OSWorkflow解读之二
多种方式定制逻辑>>
osworkflow的几个基本元件都具有很好的扩展性,它们分别是:condition,function,register,validator。以condition为例,我们可以为触发action的condition定义任意复杂的逻辑,而这种逻辑可以包含在一个java class中,也可以采用bsf,或者bean shell,还有local ejb和remote ejb。只要流程定义文件做相应配置即可,以java class和bean shell为例:
<action id="1″ name="Start Workflow">
<restrict-to>
<conditions type="AND">
<condition type="beanshell">
<arg name="script">true</arg>
</condition>
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
<arg name="group">foos</arg>
</condition>
</conditions>
</restrict-to>
…
</action>
在osworkflow的许多地方都可以见到类似如下的代码(此处以function为例):
if ("remote-ejb".equals(type)) { clazz = RemoteEJBFunctionProvider.class.getName(); } else if ("local-ejb".equals(type)) { clazz = LocalEJBFunctionProvider.class.getName(); } else if ("jndi".equals(type)) { clazz = JNDIFunctionProvider.class.getName(); } else if ("bsf".equals(type)) { clazz = BSFFunctionProvider.class.getName(); } else if ("beanshell".equals(type)) { clazz = BeanShellFunctionProvider.class.getName(); } else { clazz = (String) args.get(CLASS_NAME); } FunctionProvider provider = (FunctionProvider) loadObject(clazz); provider.execute(transientVars, args, ps);
loadObject会动态加载相应的具现处理类(比如BSFFunctionProvider),并转换为基类类型(比如FunctionProvider),然后调用相应的执行逻辑(比如provider.execute)。这一模式屡试不爽。
OSWorkflow解读之一
AbstractWorkflow>>
osworkflow中有关工作流流转的所有核心代码都在AbstractWorkflow中,BasicWorkflow就是派生自它,不过这个BasicWorkflow基本上没做什么事情。也许我们还可以从AbstractWorkflow派生自己的Workflow类以加入扩展功能,大概这也算是osworkflow所体现的一种灵活性了,即:允许对工作流流转的执行逻辑进行修改。AbstractWorkflow实现了Workflow接口,该接口包含了有关工作流的核心方法,最重要的是doAction方法,AbstractWorkflow实现了该方法,后面会提及,其他还有一些getter和query method。
流程流转的执行逻辑>>
当流程执行到的某个step时,可能有一个或多个action可供用户选择执行。一旦确定执行某个action后,我们需要调用AbstractWorkflow.doAction,并传入流程id和action的id。以下是对doAction的执行逻辑的一个不太严紧的算法描述:
* * *
- 根据流程id,获得所有当前的step,这种情况往往发生在有split的时候,此时会有多个step等待执行;
- 根据传入的action的id,检查是否是global action;
- 若不是global action,则遍历所有当前的step,对每个step的每个action调用isActionAvailable方法,检查该action是否可用(记住step和action是一对多的关系);
- 所谓可用是指,通过执行passesConditions,逐个检查action的condition:若是OR的关系,则有一个condition为真即为可用,AND关系则类推;
- 若action可用,则调用transitionWorkflow,这是流程流转处理的关键部分;
执行transitionWorkflow时:
- 首先获取当前step,存在有多个当前step的情况,比如split,此时获取首个isAvailableAction为真的step;
- 调用verifyInputs验证输入(如果action有validator的话);
- 执行当前step的post function(因为该step即将结束);
- 执行action的pre function;
- 判断当前step所属的result中的所有condition是否满足要求,判断方法类似action的condition;
- 一旦满足,则获取result的pre function和post function;
- 否则即是unconditional result,获取相应的pre function和post function;
- 在没有split和join的情况下
- 会根据在result中指定的下一个step的id,创建一个新的step,作为当前的step;
- 从current steps中移除原来的当前step,并添加到history steps中;
- 如果新的step有pre function,则会马上执行;
- 执行result的post function;
- 执行action的post function;
- 若action是intial action,则将流程设置为activated状态;
- 若action是finish action,则将流程设置为completed状态,返回true;
- 寻找auto action,若有的话,则执行之,执行方法是调用doAction本身;
- 返回false;
- 根据transitionWorkflow的返回值判断流程是否结束;
- 若返回false,则调用checkImplicitFinish检查是否存在implicit finish,即:当前没有一个step的action可用时,就认为流程应该结束;
* * *
- 若存在split,则会创建多个新的step,并且在创建之前先执行split的pre function,在创建之后执行split的post function;
- 创建step的过程和上面描述的普通状况相同:维护好current steps和history steps,并执行新的step的pre function;
* * *
- 若存在join,先结束当前step,并将该step添加至history steps和join steps;
- 查找history steps,对每个已完成的step,查看是否在其result或unconditional result中有join一项,若有则加入join steps中;
- 检查join是否已经满足:可以使用Bean Shell,在xml定义文件的join节点中,通过引用一个名为“jn”的特殊变量来指定join的满足条件,jn记录了有关join的关键信息;
- 若条件满足,则执行join的pre function,维护好history steps,并创建下一个step,然后执行join的post function;
* * *
- 对于条件循环的情况,可以通过将result的某个action的下一个step指定为自身来加以实现,这只是在xml定义文件中做文章,流程执行逻辑无需做特殊处理;
time granulrity,issue tracking,tools vs. human
time granulrity:
作为时间的一种度量单位,或小时记,或天记,都是灵活的和因人而异的。比如你完全可以将8个小时理解为1天。但是,粒度的确定并非没有原则:为任务分配时间是一种预估,这样的估计如果易变而不准确,那么多半也就失去了意义。一般而言,只有在对所做的工作十分了解的前提下,才能够准确估计。对于一个易变的的环境,陌生的环境,时常被打断的环境,或者时常冒出些“障碍”的环境(比如某个艰深的bug fix),时间的粒度就不亦过细,至少在迭代的初期是如此。随着时间的推移和经验的积累,估计的粒度也就会相应改进。
issue tracking
issue tracking至关重要,否则事态就可能滑入不可控的危险,team member就会失去对项目状况的全景认识,这也是我的切身体会。实际上,不单是bug,feature,defact,……,很多东西都是需要有tracking的。比如某此会议中的一个idea,如果没有某种形式的tracking,很容易就会消失在大家的记忆中,直到哪次再被大家带着一种似曾相识的感情从被遗忘的角落里重新提起。
tools vs. human
我始终觉得,在一个开发团队中,对于工具的使用,总是二等考量,更为重要的是人的因素。因为,即使有趁手的工具,也依然有可能存在缺乏交流的team member,以及由这些team member参加的冗长的缺乏主题的会议。
OSWorkflow开篇
刚刚下载了OSWorkflow,这里摘录的是联机文档中的开篇部分,也就是OSWorkflow的核心概念了:
OSWorkflow is based heavily on the concept of the finite state machine. Each state is represented by the combination of a step ID and a status. A transition from one state to another cannot happen without an action occuring first. There are always at least one or more active states during the lifetime
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=447742
h3 定义条件和函数
你也许已经注意到,到目前为止,我们定义的条件和函数类型都是"class""""""""""""""""""""class.name"""""""""""FunctionProvider或Condition接口的完整类名。
在osworkflow里面也有一些其他内置的类型,包括beanshell,无状态的session bean,JNDI树上的函数等。我们在下面的例子里使用beanshell类型。
h3 Property sets
我们可能需要在工作流的任意步骤持久化一些少量数据。在osworkflow里,这是通过OpenSymphony的PropertySet library来实现。一个PropertySet基本上是一个可以持久化的类型安全map,你可以添加任意的数据到propertyset(一个工作流实例对应一个propertyset),并在以后的流程中再读取这些数据。除非你特别指定操作,否则propertyset中的数据不会被清空或者被删除。任意的函数和条件都可以和propertyset交互,以beanshell script来说,可以在脚本上下文中用"propertyset"""""""""""""""""""""""""""""""""""Start First Draft""""pre-functions里面:
这样我们就添加了一个持久化的属性"foo"""""""bar""""""""""""""""""""""""
h3 Transient Map 临时变量
另外一个和propertyset变量相对的概念是临时变量:"transientVars""""""""""""map,只是在当前的工作流调用的上下文内有效。它包括当前的工作流实例,工作流定义等对应值的引用。你可以通过FunctionProvider的javadoc来查看这个map有那些可用的key。
还记得我们在教程的第2部分传入的那个null吗?如果我们不传入null的话,那么这些输入数据将会被添加到临时变量的map里。
h3 inputs 输入
每次调用workflow的动作时可以输入一个可选的map,可以在这个map里面包含供函数和条件使用的任何数据,它不会被持久化,只是一个简单的数据传递。
h3 Validators 校验器
为了让工作流能够校验输入的数据,引入了校验器的概念。一个校验器和函数,条件的实现方式非常类似(比如,它可以是一个class,脚本,或者EJB)。在这个教程里面,我们将会定义一个校验器,在"finish first draft""""""""""""""""working.title"""""30个字符。这个校验器看起来是这样的:
然后通过在流程定义文件添加validators元素,就可以登记这个校验器了:
这样,当我们执行动作2的时候,这个校验器将会被调用,并且检验我们的输入。这样在测试代码里面,如果加上:
我们将会得到一个InvalidInputException,这个动作将不会被执行。减少输入的title字符,将会让这个动作成功执行。
我们已经介绍了输入和校验,下面来看看寄存器。
h3 Registers 寄存器
寄存器是一个工作流的全局变量。和propertyset类似,它可以在工作流实例的任意地方被获取。和propertyset不同的是,它不是一个持久化的数据,而是每次调用时都需要重新计算的数据。
它可以被用在什么地方呢?在我们的文档管理系统里面,如果定义了一个"document""""""""""""""""""""""""""""""""""""""""""""
寄存器地值会被放在临时变量(transientVars map)里,这样能够在任意地方获得它。
定义一个寄存器和函数、条件的一个重要区别是,它并不是依靠特定的调用(不用关心当前的步骤,或者是输入数据,它只是简单地暴露一些数据而已),所以它不用临时变量里的值。
寄存器必须实现Register接口,并且被定义在流程定义文件的头部,在初始化动作之前。
举例来说,我们将会使用一个osworkflow内置的寄存器:LogRegister。这个寄存器简单的添加一个"log""""""""""Jakarta的commons-logging输出日志信息。它的好处是会在每条信息前添加工作流实例的ID。
这样我们定义了一个可用的"log"""""""""""pre-function的脚本里面使用它:
日志输出将会在前面添加工作流实例的ID
h3 结论
这个教程的目的是希望可以阐明一些主要的osworkflow概念。你还可以通过API和流程定义格式去获取更多的信息。有一些更高级的特性没有在此提到,比如splits 分支、joins 连接, nested conditions 复合条件、auto stpes 自动步骤等等。你可以通过阅读手册来获得更进一步的理解。
如果你遇到任何的困难,可以在osworkflow的email list上询问。