忍不住又搞了搞jbpm,上学期初的时候搞了搞流程定义和简单的Task调度,这几天想了想,过去的想法是没错的,只是不够深入,在springmodules的激励下,想了想如果prp中使用的是jbpm,而不是osworkflow,应该如何设计。
先新建jbpm的工程,然后新建一个process definition,一步步建立了一个类似prp中需求的流程,下面是流程定义(processdefinition.xml):
<?xml version="1.0" encoding="UTF-8"?>
<process-definition
xmlns="urn:jbpm.org:jpdl-3.1"
name="ProjectWorkflow">
<swimlane name="applier"></swimlane>
<swimlane name="admin"></swimlane>
<start-state name="Apply Project">
<task name="request project" swimlane="applier">
<controller>
<variable name="project name" access="read,write,required"></variable>
<variable name="applier id" access="read,write,required"></variable>
</controller>
</task>
<transition name="ready for review " to="Review Project"></transition>
</start-state>
<task-node name="Review Project" signal="last">
<task name="request review">
<controller>
<variable name="project name" access="read"></variable>
<variable name="applier id" access="read"></variable>
</controller>
<assignment class="org.springmodules.workflow.jbpm31.JbpmHandlerProxy" config-type="bean">
<targetBean>adminAssignmentHandler</targetBean>
<factoryKey>jbpmConfiguration</factoryKey>
</assignment>
</task>
<transition name="review pass" to="Upload File"></transition>
<transition name="review fail" to="End"></transition>
</task-node>
<task-node name="Upload File" signal="last">
<task name="upload profile">
<controller>
<variable name="project name" access="read"></variable>
<variable name="applier id" access="read"></variable>
<variable name="project profile" access="read,write,required"></variable>
</controller>
<assignment class="org.springmodules.workflow.jbpm31.JbpmHandlerProxy" config-type="bean">
<targetBean>applierAssignmentHandler</targetBean>
<factoryKey>jbpmConfiguration</factoryKey>
</assignment>
</task>
<transition name="upload finish" to="Publish Project"></transition>
</task-node>
<task-node name="Publish Project" signal="last">
<task name="publish profile">
<controller>
<variable name="project name" access="read"></variable>
<variable name="applier id" access="read"></variable>
<variable name="project profile" access="read"></variable>
</controller>
<assignment class="org.springmodules.workflow.jbpm31.JbpmHandlerProxy" config-type="bean">
<targetBean>adminAssignmentHandler</targetBean>
<factoryKey>jbpmConfiguration</factoryKey>
</assignment>
</task>
<transition name="finish" to="End"></transition>
<transition name="upload again" to="Upload File"></transition>
</task-node>
<end-state name="End">
<event type="node-enter">
<script>
<expression>
System.out.println("Process End. ");
</expression>
</script>
</event>
</end-state>
</process-definition>
这次的定义强调了使用controller来管理task的变量,考虑到prp的需求,主要是人工任务,所以基本放弃swimlane,使用AssignmentHandler来push工作项,另外,也小试了jpdl中使用beanshell。值得一提的是signal="last"是规定流程中某一步的Task都完成了,流程才继续,还有其他几种形式,这里不多说了。
接着简单地照搬了springmodules中jbpm的配置,applicaitionContext-jbpm.xml和jbpm.cfg.xml:
其中applicationContext-jbpm.xml中配置了两个AssignmentHandler:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="jdbc.properties" />
</bean>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close" singleton="true">
<property name="driverClass"
value="${jbpm.jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jbpm.jdbc.url}" />
<property name="user" value="${jbpm.jdbc.username}" />
<property name="password" value="${jbpm.jdbc.password}" />
<property name="maxPoolSize" value="5" />
</bean>
<bean id="jbpmSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingLocations">
<list>
<value>classpath*:org/jbpm/**/*.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${hibernate.dialect}
</prop>
<prop key="hibernate.show_sql">
${hibernate.show_sql}
</prop>
<prop key="hibernate.cache.use_query_cache">
${hibernate.cache.use_query_cache}
</prop>
<prop key="hibernate.cache.provider_class">
${hibernate.cache.provider_class}
</prop>
</props>
</property>
</bean>
<!--Hibernate TransactionManager-->
<bean id="jbpmTransactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="jbpmSessionFactory" />
</bean>
<!-- Process Definition of simpleOrderFlow -->
<bean id="projectWorkflow"
class="org.springmodules.workflow.jbpm31.definition.ProcessDefinitionFactoryBean">
<property name="definitionLocation"
value="classpath:project.par/processdefinition.xml" />
</bean>
<!-- jBPM configuration -->
<bean id="jbpmConfiguration"
class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
<property name="sessionFactory" ref="jbpmSessionFactory" />
<property name="configuration" value="classpath:jbpm.cfg.xml" />
<property name="processDefinitions">
<list>
<ref local="projectWorkflow" />
</list>
</property>
</bean>
<!-- jBPM template -->
<bean id="jbpmTemplate"
class="org.springmodules.workflow.jbpm31.JbpmTemplate">
<constructor-arg index="0" ref="jbpmConfiguration" />
<constructor-arg index="1" ref="projectWorkflow" />
</bean>
<bean id="adminAssignmentHandler"
class="org.openepo.workflow.taskmgmt.AdminAssignmentHandler"
autowire="byName">
<property name="adminId" value="admin" />
</bean>
<bean id="applierAssignmentHandler"
class="org.openepo.workflow.taskmgmt.ApplierAssignmentHandler"
autowire="byName" />
</beans>
AssignmentHandler代码:
AdminAssignmentHandler.java:
package org.openepo.workflow.taskmgmt;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.def.AssignmentHandler;
import org.jbpm.taskmgmt.exe.Assignable;
public class AdminAssignmentHandler implements AssignmentHandler {
private static final long serialVersionUID = 1L;
private String adminId;
public String getAdminId() {
return adminId;
}
public void setAdminId(String adminId) {
this.adminId = adminId;
}
public void assign(Assignable assignable, ExecutionContext executionContext) throws Exception {
System.out.println("Assign task to " + adminId);
assignable.setActorId(adminId);
}
}
ApplierAssignmentHandler.java:
package org.openepo.workflow.taskmgmt;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.def.AssignmentHandler;
import org.jbpm.taskmgmt.exe.Assignable;
public class ApplierAssignmentHandler implements AssignmentHandler {
private static final long serialVersionUID = 1L;
public void assign(Assignable assignable, ExecutionContext executionContext) throws Exception {
String actorId = (String)executionContext.getVariable("applier id");
System.out.println("Assign task to " + actorId);
assignable.setActorId(actorId);
}
}
setActorId就是指定具体的人来完成任务.
根据jbpm自带的mysql的script来建表,需要的准备工作就做好了。
现在prp的工作流设计中,主要在oswrokflow的基础上二次开发了:1.工作项 2.工作流代理 3.必要的function和condition(使用了项目其它模块) 4.工作流监控(就是条件查询)。
1,2. 使用springmodules的话,首先工作项不用特别设计了,因为jbpm天生就有TaskInstance的概念。
自己写工作项,就可以在配置文件中就可以关联必要的url入口,来直接由工作项引导到action,action再和代理类Operator对应,从而屏蔽工作流的驱动细节。
但是如果使用jbpm+springmodules的话,这种思路无疑增加了复杂性,一层又包一层,干脆就放弃对工作流细节的屏蔽,放弃工作流代理,直接利用jbpmTemplate根据actorId来findTaskInstances,然后根据TaskInstance的一些信息来对应到不同的service层方法,在manager里直接使用jbpmTemplate回调来驱动工作流。
这里有个问题没解决,拿到TaskInstance也就是工作项列表后,某个具体工作项一定是要对应到一个Action的,现在prp的做法正如上面说的很好地解决了这个问题,但是如果jbpm+springmodules,可能就要在AssignmentHandler里set一个url的variable,前台freemarker通过TaskInstance直接$出来。
下面是具体代码,考虑到一切从简,只是用了一个Test文件,堆砌代码来说明一下如何用springmodules的jbpmTemplate。
public void createNewProcess() {
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext jbpmContext) throws JbpmException {
ProcessInstance processInstance = jbpmContext.newProcessInstance("ProjectWorkflow");
TaskInstance taskInstance = processInstance.getTaskMgmtInstance().createStartTaskInstance();
System.out.println("requesting...");
/* Data Input Code, Example: */
taskInstance.setActorId(applierId);
taskInstance.setVariable("project name", "SoftEden");
taskInstance.setVariable("applier id", applierId);
taskInstance.end();
jbpmContext.save(processInstance);
return null;
}
});
}
这个方法应该放在service层,作用是启动新的流程,这里只是简单地模拟变量输入。
public void adminActions() {
List taskInstances = jbpmTemplate.findTaskInstances(adminId);
System.out.println("Task instances size: " + taskInstances.size());
for(Iterator it = taskInstances.iterator(); it.hasNext();) {
TaskInstance currentTask = (TaskInstance)it.next();
String taskInstanceName = currentTask.getName();
System.out.println(taskInstanceName);
long taskInstanceId = currentTask.getId();
if(taskInstanceName.equals("request review"))
review(taskInstanceId);
else if(taskInstanceName.equals("publish profile"))
publish(taskInstanceId);
}
}
这个方法是管理员方法,涉及到两部分,一部分是findTaskInstances,这一步应该在Action层中调用service层方法,返回工作项列表到前台,另一部分就是处理具体的某个工作项,根据TaskInstance的name选择不同的service层方法。下面就是方法:
public void review(final long taskInstanceId) {
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext jbpmContext) throws JbpmException {
TaskInstance taskInstance = jbpmContext.loadTaskInstance(taskInstanceId);
String projectName = (String)taskInstance.getVariable("project name");
String actorId = (String)taskInstance.getVariable("applier id");
System.out.println("Reviewing...");
System.out.println("Project Name: " + projectName);
System.out.println("Actor Id: " + actorId);
/* Review Code, Example: */
if(evaluate(projectName,actorId))
taskInstance.end("review pass");
else
/* Other dispose work */
taskInstance.end("review fail");
return null;
}
});
}
public void publish(final long taskInstanceId) {
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext jbpmContext) throws JbpmException {
TaskInstance taskInstance = jbpmContext.loadTaskInstance(taskInstanceId);
String projectName = (String)taskInstance.getVariable("project name");
String actorId = (String)taskInstance.getVariable("applier id");
String projectProfile = (String)taskInstance.getVariable("project profile");
System.out.println("publishing...");
System.out.println("Project Name: " + projectName);
System.out.println("Actor Id: " + actorId);
System.out.println("Project Profile: " + projectProfile);
/* Publish Code, Example: */
if(checkProfile(projectProfile))
taskInstance.end("finish");
else
taskInstance.end("upload again");
return null;
}
});
}
这两个方法使用JbpmCallback来完成流程逻辑。值得一提的是2点:1.利用taskInstance的end(String)方法,来选择流程分支transition,代码非常清晰;2.findTaskInstances出来的TaskInstance是lazy的,所以应该传TaskInstanceId到doInJbpm方法里load完整的TaskInstance,不要觉得这不方便,其实这是很实用的。
最后是上传文件的代码示例,没有什么特别的,也是两部分组成:列出工作项,处理工作项。
public void upload() {
List taskInstances = jbpmTemplate.findTaskInstances(applierId);
System.out.println("Task instances size: " + taskInstances.size());
for(Iterator it = taskInstances.iterator(); it.hasNext();) {
TaskInstance currentTask = (TaskInstance)it.next();
String taskInstanceName = currentTask.getName();
System.out.println(taskInstanceName);
final long taskInstanceId = currentTask.getId();
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext jbpmContext) throws JbpmException {
TaskInstance taskInstance = jbpmContext.loadTaskInstance(taskInstanceId);
System.out.println("uploading...");
/* Data Input Code, Example: */
taskInstance.setVariable("project profile", "Apply open souce.");
taskInstance.end();
return null;
}
});
}
}
总而言之,prp中是在Action中调用WorkItemManager来返回工作项列表,现在可以直接用jbpmTemplate或者封装其到service层来返回工作项列表;另外,暴露工作流细节到service层,方法中利用回调中完成业务逻辑和工作流逻辑。
3. 必要的外设,如短消息,JMS,权限检查,这里没有demo,可以实现ActionHandler和DesicionHandler来添加,这里不赘述,只需利用prp里的相关模块,虽然jbpm已经包办了很多内容,比如jms,比如权限,但是毕竟已经把jbpm当作了插件来用,另外prp中各模块都已经很好地work了,没必要再花时间配置,以后再说了。
4. 工作流监控部分,prp中封装了osworkflow的Query,使用回调来进行具体查询,比如针对某个状态的流程实例,但是jbpm里居然没有这样的API(真是崩溃),所以监控部分只有JbpmTemplate提供的单薄的findProcessInstances和findProcessInstance(Long)了。
这里只是简单地说说如果prp中用jbpm替代方案是什么,jbpm中的另外一些特别的部分,比如Super State、Process Composite和Scheduler,也放在以后研究。