随笔 - 0  文章 - 12  trackbacks - 0
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

留言簿

文章分类

文章档案

搜索

  •  

最新评论

OSWorkflow解读之九

Filed under: — site admin @ 9:08 am

UserManager>>

OSWorkflow在用户管理方面所提供的功能,主要包括用户的创建、群组的定义、用户验证、以及对step执行人的跟踪记录和执行权限的判断等等。

用户/群组的管理是由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>

11/29/2004

OSWorkflow解读之八

Filed under: — site admin @ 10:21 am

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的源代码。通过这样的方式,可以满足任意复杂的流程查询条件指定。

11/28/2004

OSWorkflow解读之七

Filed under: — site admin @ 4:32 pm

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>");

实际上,这是典型的Interpreter Pattern的运用,下面是类图总结。

11/27/2004

OSWorkflow解读之六

Filed under: — site admin @ 10:53 am

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中,以备随时取用。以下便是一个典型的使用场景:

<registers>
 <register type="class” variable-name="log">
  <arg name="class.name">com.opensymphony.workflow.util.LogRegister</arg>
  <arg name="addInstanceId">true</arg>
 </register>
</registers>
<function type="beanshell” name="bsh.function">
 <arg name="script">transientVars.get("log").info("function called");</arg>
</function>

此外,为了方便使用,osworkflow的util包中还预定义了大量的Condition和FunctionProvider,以及其他的一些辅助类,比如:StatusCondition、AllowOwnerOnlyCondition、BeanShellCondition、Caller、EJBInvoker、ScheduleJob。

借助这些设施,osworkflow的扩展性、灵活性、易用性,得到了极大的体现。

11/26/2004

OSWorkflow解读之五

Filed under: — site admin @ 12:00 am

Schedule>>

在osworkflow中提供了定时执行某项任务的功能,这主要得益于opensymphony的另一个项目——Quatz,该项目为工作的定期执行提供了底层设施支持。为此,我们需要引用quatrz.jar包,好在osworkflow的下载包中已经包含了该文件。

为了实现任务定时,我们需要在流程定义文件中做类似如下的配置:

<function type="class">
 <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>

11/25/2004

osworkflow解读之四

Filed under: — site admin @ 9:38 am

流程启动>>

前面分析流程流转的执行逻辑时,并没有讲到流程的启动。实际上,有了前面的基础,再加上对流程定义文件加载的过程有清晰认识之后,流程启动逻辑是很容易理解的。大致情况如下:
- 调用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达到目的的。

11/24/2004

OSWorkflow解读之三

Filed under: — site admin @ 9:48 am

初始配置加载及工作流定义文件加载>>

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。至此,流程定义文件加载完毕,整个初始化过程也就基本完成了。

11/23/2004

OSWorkflow解读之二

Filed under: — site admin @ 9:09 am

多种方式定制逻辑>>

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)。这一模式屡试不爽。

11/22/2004

OSWorkflow解读之一

Filed under: — site admin @ 9:35 am

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定义文件中做文章,流程执行逻辑无需做特殊处理;

11/17/2004

OSWorkflow开篇

Filed under: — site admin @ 11:04 pm

刚刚下载了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

posted on 2007-08-01 11:19 Eric huang 阅读(423) 评论(0)  编辑  收藏 所属分类: Osworkflow

只有注册用户登录后才能发表评论。


网站导航: