OSWorkflow之三——workflow的推动者Action

一个workflow有许许多多的step组成,而一个step到另一个step的流转是通过action来完成的。

 

我们先来看看actions的DTD声明

<!--
A list of zero or more common-actions and a list of zero or more actions for the enclosing step.
Note that you must define one or the other, an actions element with neither will be considered
invalid.
Used in: step
-->
<!ELEMENT actions (common-action*, action*)>

 

actions的父节点是step,从声明中可以看出,我们可以在他的下面定制各种各样的common-action和action

 

从action说起

下面是action的定义

<!ELEMENT action (meta*, restrict-to? , validators?, pre-functions?, results, post-functions?)>
<!ATTLIST action
	id CDATA #REQUIRED
	name CDATA #REQUIRED
  view CDATA #IMPLIED
  auto (TRUE | FALSE | true | false) #IMPLIED
  finish (TRUE | FALSE | true | false) #IMPLIED
>
 

Attributes:


id: 标识符,在整个流程定义中,他必须唯一

name: 名称

view: a view name for the action

auto: 可选值(true|false),如果为true的话,这个action将在条件满足后会被自动执行,默认值false

finish: 可选值(true|false),默认值false

 

Action执行的Condition

Condition接口只有一个passesCondition方法,返回值是boolean,只有返回true的时候,我们的Action才可能被后续执行。下面是passesCondition的方法签名:

 

public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException;
 

passesCondition参数:

A、transientVars

作为一个Map类型的参数,他内部保存了一些内置的key-value,同时他也能保存着我们希望他保存的东西(我们可以通过在调用OSWorkflow的API的时候作为参数传递给他,稍后介绍)。

transientVars中的内置变量:

1、createdStep:新建Step的相关信息,保存的是Step接口的实现
2、currentSteps:保存当前的steps
3、descriptor:Workflow的Descriptor信息,保存的是WorkflowDescriptor实例
4、context:Workflow的上下文信息,保存的是WorkflowContext接口实现
5、entry:保存当前WorkflowEntry的实例
6、store:保存当前存储方式
7、configuration:保存Configuration接口的实现——DefaultConfiguration实例
8、actionId:保存当前actionId
9、jn:保存JoinNodes信息

他承载着以上的这些内置变量以及doAction方法的Map类型参数的值,并作为以下这些方法的参数在整个流程中共享和传递。
1、Condition接口的passesCondition方法,我们可以实现Condition接口来自定义条件,并配置到流程文件中的各种condition元素中去
2、Validator接口的validate方法,我们可以实现Validator接口来自定义验证器,并配置到流程文件中的各种validator元素中去
3、FunctionProvider接口的execute方法,我们可以实现FunctionProvider接口来自定义各种方法,并配置到流程文件中的各种function元素中去
这三个方法作为OSWorkflow流程最常见的扩展点,我们可以在自己实现相关接口的同时获取到这些变量信息,配合我们完成相关需求的实现。

 

B、args

我们先看一个流程定制文件的片段

<condition type="class">
	<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
	<arg name="status">Queued</arg>
</condition>

 args参数保存的就是在以上代码片段中的两个关于arg元素的配置部分的信息,我们可以在passesCondition方法中通过

 args的get("status")方法或者相关参数的值。

 

C、ps

该参数对应的参数类型为com.opensymphony.module.propertyset.PropertySet,他可以将一些类型的变量以key-value的方式持久化到存储设备中去。我们以MYSQL数据库为例,看看他的庐山真面目

 

图1:

前三列为联合主键,entity_key是我们的key名称,对应的值则保存在key_type以后的那些列中,根据value值的类型不同来确定保存在哪一列中。

 

我们需要引入下面jar来支持PropertySet

propertyset-1.5.jar:基础包

propertyset-hibernate3-1.5.jar:通过hibernate的方式来持久化数据,这个包里有一个PropertySetItemImpl.hbm.xml的映射文件,可以根据情况使用。

 

内置的Condition

OSWorkflow给我们提供了一些Condition可以供我们直接使用。

 

图2:

其中AllowOwnerOfStepCondition、AllowOwnerOnlyCondition、DenyOwnerCondition已经被deprecated掉了,不推荐使用,而是用IsUserOwnerCondition替代啦。

 

IsUserOwnerCondition

IsUserOwnerCondition作为一个内置的Condition,他返回当前workflow实例的caller和owner的匹配结果。如果相等则返回true,否则为false。

 

BeanShellCondition

http://www.beanshell.org/

beanshell可以在嵌入到系统中,就像在执行脚本语言一样,在系统运行时动态的执行JAVA代码。我们也可以通过BeanShell调用我们的应用程序及其对象,它可以让JAVA对象和API动态运行。BeanShell是用JAVA写的,所以它可以和你的应用程序运行在同一个JVM空间内,我们也可以自由的传递实时对象的References到脚本代码中,调用相关方法并且作为结果返回。

以下是一个简单的配置例子,直接返回true

 

<condition type="beanshell">
	<arg name="script">true</arg>
</condition>

注意:

<arg name="script">我们的代码在这里</arg>

 

BSFCondition

Bean Scripting Framework(BSF)是一个支持在Java应用程序内调用脚本语言 (Script),并且支持脚本语言直接访问Java对象和方法的一个开源项目。有了它 , 你就能在java application中使用javascript, Python, XSLT, Perl, tcl, ……等一大堆scripting language。反过来也可以,就是在这些scripting language中调用任何已经注册过了的JavaBean、java object。

BSF最初是IBM的Alpha工作组的项目后来贡献给了Apache。以下是BSF支持的一些Script以及对应的处理Engine:

 

    Languages.properties:

javascript = org.apache.bsf.engines.javascript.JavaScriptEngine, js
jacl = org.apache.bsf.engines.jacl.JaclEngine, jacl
netrexx = org.apache.bsf.engines.netrexx.NetRexxEngine, nrx
java = org.apache.bsf.engines.java.JavaEngine, java
javaclass = org.apache.bsf.engines.javaclass.JavaClassEngine, class
bml = org.apache.bml.ext.BMLEngine, bml
vbscript = org.apache.bsf.engines.activescript.ActiveScriptEngine, vbs
jscript = org.apache.bsf.engines.activescript.ActiveScriptEngine, jss
perlscript = org.apache.bsf.engines.activescript.ActiveScriptEngine, pls
perl = org.apache.bsf.engines.perl.PerlEngine, pl
jpython = org.apache.bsf.engines.jpython.JPythonEngine, py
jython = org.apache.bsf.engines.jython.JythonEngine, py
lotusscript = org.apache.bsf.engines.lotusscript.LsEngine, lss
xslt = org.apache.bsf.engines.xslt.XSLTEngine, xslt
pnuts = pnuts.ext.PnutsBSFEngine, pnut
beanbasic = org.apache.bsf.engines.beanbasic.BeanBasicEngine, bb
beanshell = bsh.util.BeanShellBSFEngine, bsh
ruby = org.jruby.javasupport.bsf.JRubyEngine, rb
judoscript = com.judoscript.BSFJudoEngine, judo|jud
groovy = org.codehaus.groovy.bsf.GroovyEngine, groovy|gy
objectscript = oscript.bsf.ObjectScriptEngine, os
prolog = ubc.cs.JLog.Extras.BSF.JLogBSFEngine, plog|prolog
rexx = org.rexxla.bsf.engines.rexx.RexxEngine, rex | rexx | cls | rxj | rxs
 

虽然BSF提供以上如此多Script的支持,但我们在使用的时候还得参考我们使用的BSF版本,并不是所有版本都支持以上这些Script Engine的。

以下是一个简单配置例子:

<condition type="bsf">
	<arg name="language">java</arg>
	<arg name="source"></arg>
	<arg name="row">0</arg>
	<arg name="col">0</arg>
	<arg name="script">true</arg>
</condition>

 

language参数的值则对应Languages.properties中的key,script则是需要执行的脚本或者代码的内容。

OSWorkflow根据language参数的值来映射由哪一种Engine来负责处理我们的script中的代码,所有的一切都是通过用BSFEngine的eval方法来完成的。以下是方法签名:

 

/**
	 * This is used by an application to evaluate an expression. The
	 * expression may be string or some other type, depending on the
	 * language. (For example, for BML it'll be an org.w3c.dom.Element
	 * object.)
	 *
	 * @param source   (context info) the source of this expression
	 *                 (e.g., filename)
	 * @param row   (context info) the line number in source for expr
	 * @param col (context info) the column number in source for expr
	 * @param script     the expression to evaluate
	 *
	 * @exception BSFException if anything goes wrong while eval'ing a
	 *            BSFException is thrown. The reason indicates the problem.
	 */
	public Object eval(String source, int row, int col, Object script)
		throws BSFException;

 source、row、col三个参数都是context info,并不是所有的BSFEngine都会用到这三个参数,目前只发现JavaScriptEngine有使用到。因为JavaScriptEngine最终是调用org.mozilla.javascript.Context的evaluateString方法来执行JS代码,这个方法里有用到了source和row两个参数,关于org.mozilla.javascript.Context的使用请大家自己参考API吧。所以说大多数时候,我们都只需要language和script两个参数。

 

JNDICondition

JNDICondition会接收一个name为jndi.location的参数,用来从JNDI中获得Condition的实例,并返回这个Condition实例的匹配结果。

 

<condition type="jndi">
	<arg name="jndi.location">java:comp/env/myCondition</arg>
</condition>

 

OSUserGroupCondition

OSUserGroupCondition接收一个name为group的参数,并与当前workflow的caller所属的group比较,如果caller在group中,则返回true,否则返回false。OSUserGroupCondition的使用需要依赖OSUser,OSUser也是opensymphony下一个项目,如果我们用OSUser来管理我们的用户,可以考虑使用该Condition。

 

Condition也可以是EJB,可根据情况选择LocalEJBCondition和RemoteEJBCondition。

 

StatusCondition

StatusCondition接收一个name为status的参数,并与当前workflow实例step的status属性作匹配。如果相等则返回true,否则为false。

 

<condition type="class">
	<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
	<arg name="status">Underway</arg>
</condition>

 

自定义Condition

我们可以自已实现com.opensymphony.workflow.Condition接口,来定制我们自己的条件判断,配置如下:

<condition type="class">
	<arg name="class.name">packagename.MyCondition</arg>
	......other args
</condition>
 

 

多个Condition可以组合使用,通过AND和OR来完成更加复杂的逻辑判断:

 

<conditions type="AND">
	<condition type="beanshell">
		<arg name="script">true</arg>
	</condition>
	<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>

 

conditions下面还可以有更多的conditions,以下是conditions的定义:

 

<!ELEMENT conditions (conditions | condition)*>
<!ATTLIST conditions
	type (AND | OR) #IMPLIED
>
 

让action使用Condition

<action id="1" name="Finish First Draft">
	<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.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>
	.....
</action>
 

Function

OSWorkflow中的com.opensymphony.workflow.FunctionProvider接口定义了所有function的统一行为,该接口只有一个execute方法。

 

public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException;

 

该方法的参数和Condition的参数是一样的,可参考Condition接口参数的介绍。

 


OSWorkflow提供了很多内置的FunctionProvider实现,其中有一些function和condition的实现比较类似,如:BeanShellFunctionProvider、BSFFunctionProvider、JNDIFunctionProvider。

同时还提供了发送JMS消息的JMSMessage,用于发送邮件的SendEmail,以及用于处理Quartz的ScheduleJob和UnscheduleJob。

 

自定义Function

我们可以自己实现com.opensymphony.workflow.FunctionProvider接口,定制我们自己的Function。

function 可用于pre-functions, post-functions的XML定义中。

 

Action的validators

和Condition和Function一样,Validator也有一个接口定义——com.opensymphony.workflow.Validator

 

public void validate(Map transientVars, Map args, PropertySet ps) throws InvalidInputException, WorkflowException;

 

使用方法和Function一样没有区别。OSWorkflow给出了几个默认实现,和之前Funtion、Condition的相关实现类似,他们分别是BeanShellValidator、BSFValidator、JNDIValidator、LocalEJBValidator、RemoteEJBValidator等,具体使用方法可以参考之前Funtion、Condition部分的介绍。

和Function一样,我们可以自己实现com.opensymphony.workflow.Validator接口,来定制自己的Validator。

以下是一个配置示例片段:

 

<validators>
	<validator type="class">
		<arg name="class.name">com.opensymphony.workflow.util.jndi.JNDIValidator</arg>
		<arg name="jndi.location">java:comp/env/MyValidator</arg>
	</validator>
</validators>
 

TypeResolver

不论是Condition还是Function还是Validator,都有一个"type"属性作为#REQUIRED在ATTLIST的定义中,在我们之前的示例中有看到关于type的配置部分,有beanshell、bsf、jndi、class等等等等。我们到底有多少种选择,而OSWorkflow又是如何根据我们提供的type来定位最终的处理者呢——TypeResolver都帮我们搞定啦。

 

type的选择

TypeResolver的构造方法给出了所有支持的type方案,如下:

 

validators.put("remote-ejb", "com.opensymphony.workflow.util.ejb.remote.RemoteEJBValidator");
validators.put("local-ejb", "com.opensymphony.workflow.util.ejb.local.LocalEJBValidator");
validators.put("jndi", "com.opensymphony.workflow.util.jndi.JNDIValidator");
validators.put("beanshell", "com.opensymphony.workflow.util.beanshell.BeanShellValidator");
validators.put("bsf", "com.opensymphony.workflow.util.bsf.BSFValidator");
conditions.put("remote-ejb", "com.opensymphony.workflow.util.ejb.remote.RemoteEJBCondition");
conditions.put("local-ejb", "com.opensymphony.workflow.util.ejb.local.LocalEJBCondition");
conditions.put("jndi", "com.opensymphony.workflow.util.jndi.JNDICondition");
conditions.put("beanshell", "com.opensymphony.workflow.util.beanshell.BeanShellCondition");
conditions.put("bsf", "com.opensymphony.workflow.util.bsf.BSFCondition");
registers.put("remote-ejb", "com.opensymphony.workflow.util.ejb.remote.RemoteEJBRegister");
registers.put("local-ejb", "com.opensymphony.workflow.util.ejb.local.LocalEJBRegister");
registers.put("jndi", "com.opensymphony.workflow.util.jndi.JNDIRegister");
registers.put("beanshell", "com.opensymphony.workflow.util.beanshell.BeanShellRegister");
registers.put("bsf", "com.opensymphony.workflow.util.bsf.BSFRegister");
functions.put("remote-ejb", "com.opensymphony.workflow.util.ejb.remote.RemoteEJBFunctionProvider");
functions.put("local-ejb", "com.opensymphony.workflow.util.ejb.local.LocalEJBFunctionProvider");
functions.put("jndi", "com.opensymphony.workflow.util.jndi.JNDIFunctionProvider");
functions.put("beanshell", "com.opensymphony.workflow.util.beanshell.BeanShellFunctionProvider");
functions.put("bsf", "com.opensymphony.workflow.util.bsf.BSFFunctionProvider"); 

 

我们可以清晰看到他们各自都支持哪些type,以及他们各自的HandlerMapping。但是我们并没有看到type="class"这样的定义,TypeResolver又是如何处理的呢?我们看一段原型代码:

 

String className = (String) functions.get(type);
    	
if (className == null) {
	className = (String) args.get("class.name");
}

if (className == null) {
	throw new WorkflowException("No type or class.name argument specified to TypeResolver");
}

try {
    return (FunctionProvider)ClassLoaderUtil.loadClass(className.trim(), getClass()).newInstance();
} catch (Exception e) {
    log.error("Could not load class '" + className + "'", e);

    return null;
}
 

真相大白,如果没有Resolver到对应的type,那么会参照class.name参数,并创建一个相应的实例。这种情况,多用于我们自己实现接口的场景。

 

SpringTypeResolver

SpringTypeResolver继承了TypeResolver,他提供了另外一种支持。

 

<function type="spring">
	<arg name="bean.name">myFunction</arg>
</function>

当type="spring"时, SpringTypeResolver就出场了,他会从spring的上下文中,返回bean.name中对应的Bean实例。

 

在上下文中使用SpringTypeResolver:

 

<bean id="workflowTypeResolver" class="com.opensymphony.workflow.util.SpringTypeResolver"/>

<bean id="basicWorkflow" class="com.opensymphony.workflow.basic.BasicWorkflow">
	<constructor-arg>
		<value>test</value>
	</constructor-arg>
	<property name="configuration">
		<ref local="osworkflowConfiguration" />
	</property>
	<property name="resolver">
		<ref local="workflowTypeResolver" />
	</property>
</bean>

 

Action的Results

 

<!ELEMENT results (result*, unconditional-result)>

Action必须有与之对应的results才有意义,他表示这个流程处理的结果。从results的定义来看,他包含了0到多个result和一个unconditional-result。

 

unconditional-result

当不需要任何condition判断或者不满足任何condition判断的场景下,我们需要一个unconditional-result,来告诉OSWorkflow流程下一步要做什么,这时候他更像是一个default result.

 

<!ELEMENT unconditional-result (validators?, pre-functions?, post-functions?)>
<!ATTLIST unconditional-result
	old-status CDATA #REQUIRED
	status CDATA #IMPLIED
	step CDATA #IMPLIED
	owner CDATA #IMPLIED
	split CDATA #IMPLIED
	join CDATA #IMPLIED
  due-date CDATA #IMPLIED
  id CDATA #IMPLIED
  display-name CDATA #IMPLIED
>

 

unconditional-result虽然没有自己的conditions,但是他也可以有自己的validators、pre-functions和post-functions

 

Attributes:

 

old-status:Action结束后,流程会结束或者流转到其他的step中去,此时当前的流程就成为历史,old-status则表示即将成为历史的流程的status。

 

status:下一个流程的状态,可选值。

 

step:下一个step的id

 

owner:下一个流程的owner

 

split:下一个流程如果需要有分路,则可选择split属性,他与steps元素下定义的split的id对应

 

join:如果当前流程作为一个分路,且下一个流程需要做分路合并,则可选择join属性,他与steps元素下定义的join的id对应

 

due-date:下一个流程的结束时间

 

conditional-result

conditional-result又称为result,因为他可以有自己的conditions,之所以这样称呼是相对于unconditional-result而言的。我们可以看到他的定义下有自己的validators、pre-functions和post-functions,这些和unconditional-result是一样的,除此之外,他还有自己的conditions定义。

 

<!ELEMENT result (conditions, validators?, pre-functions?, post-functions?)>
<!ATTLIST result
	old-status CDATA #REQUIRED
	status CDATA #IMPLIED
	step CDATA #IMPLIED
	owner CDATA #IMPLIED
	split CDATA #IMPLIED
  join CDATA #IMPLIED
  due-date CDATA #IMPLIED
  id CDATA #IMPLIED
  display-name CDATA #IMPLIED
>
 

attlist部分请参考之前unconditional-result的Attributes介绍,这些都是相同的。

 

关于splits和joins的使用,请参考附件中workflow_2_8.dtd中的定义。

 

至此,关于action的部分基本上就介绍完了,我们再来看一个完成的action的定义供大家参考

 

<action id="1" name="Finish First Draft">
	<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.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>
	<validators>
		<validator type="class">
			<arg name="class.name">com.opensymphony.workflow.util.jndi.JNDIValidator</arg>
			<arg name="jndi.location">java:comp/env/MyValidator</arg>
		</validator>
	</validators>
	<pre-functions>
		<function type="beanshell">
			<arg name="script">
				String caller = context.getCaller();
				propertySet.setString("caller", caller);
				boolean test = true;
				String yuck = null;
				String blah = "987654321";
				System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
			</arg>
		</function>
	</pre-functions>
	<results>
		<result old-status="Finished" split="1">
			<conditions type="AND">
				<condition type="beanshell">
					<arg name="script">
						propertySet.getString("caller").equals("test")
					</arg>
				</condition>
			</conditions>
			<post-functions>
				<function type="beanshell">
					<arg name="script">
					        System.out.println("11111111111111");
					        System.out.println("11111111111111");
					        System.out.println("11111111111111");
					        System.out.println("11111111111111");
					        System.out.println("11111111111111");
					        System.out.println("11111111111111");
					        System.out.println("11111111111111");
					    </arg>
				</function>
			</post-functions>
		</result>
		<unconditional-result old-status="Finished" split="2"/>
	</results>
	<post-functions>
		<function type="beanshell">
			<arg name="script">
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
				System.out.println("22222222222222");
			</arg>
		</function>
	</post-functions>
</action>
 

更高层次上的actions

除了之前介绍的action之外,还有一些更高层次上的actions,他们是initial-actions、global-actions和common-actions。

 

initial-actions

作为最早执行的action,他和普通的action没有什么不同。他是在流程initialize的时候被执行的。

 

Workflow wf = new BasicWorkflow("test");
long id = wf.initialize("example", 100, null);

 

wf.initialize方法的第二个参数则为我们定义好的initial-actions下的action id,如果这个id不存在,则会抛出InvalidActionException异常。

 

global-actions

A list of global actions that can be excuted on any step.

他会隐式的被每一个step调用。

 

common-actions

他与global-actions不同,他需要显示的调用才会被执行。

A common (shared) action reference that references an action defined in the 'common-actions' element

 

<common-action id="111" />
 

文中有一些element的定义并没有一一列出,请参考workflow_2_8.dtd(附件)

 

未完待续……

 

原文链接:加载失败,请重新获取