使用jBpm支持高级用户交互模式

许多通用业务流程都包含人类参与者。人类活动,从简单场景(如人工批准)到复杂场景(涉及复杂的数据输入),在流程实现中引入了新的方面,如人类交互模式。人类交互模式的一个典型集合1 包括:

\
  • 四眼原则(The 4-eyes principle),通常又被称为“职责分离”,它是决策由多人彼此独立作出时的一个常见场景。在很多情况下,很容易就能得到另一个观点/签名。\
  • 任命(Nomination)是指上级根据团队成员的任务安排、工作负荷或经验人工地将任务分配给他的情形。\
  • 任务通常被建模来表达一种预期:它们将在确定时间段内完成。如果任务没有按预期地进展,就需要一种上报(escalation)机制。两种典型的上报实现是:重新分配任务,并常常附带一个上报已经发生的通知;或任务未按时完成的通知(通常发给经理)。\
  • 链状执行(Chained execution)是一个流程(片断),其中的一系列步骤是由同一个人来完成。\

在本文中,我将讨论如何使用JBoss jBPM来实现这些高级的交互模式。

\

jBPM中的任务管理

\

jBPM的一个核心功能2是为人类管理任务和任务列表。jBPM允许将任务和任务节点作为整个流程设计的一部分使用。

\

任务一般在jBPM中定义成任务节点。单个任务节点可以包含一个或多个任务。包含任务节点的jBPM流程的一个公共行为就是等待任务节点中的全部任务完成,然后继续执行。某个任务可被分配3 给个人、用户组或泳道:

\
  • 假如任务被分配给某个特定用户,那么就只有这个使用者可以执行它。\
  • 假如任务被分配给某个用户组,那么这个组内的任何参与者都能执行这个任务。jBPM使用的是参与者池(pooled actors)符号(它可以包含组名、组名列表和参与者个人列表等),而不是组ID。如果用户开始执行在他们组任务列表中的任务,最终可能会引起冲突4——可能有多人开始执行相同的任务。为了避免这种情况,在开始执行任务之前,用户应该将任务从组任务列表移动到他们自己的任务列表中。 \
  • 泳道代表一个流程角色,它通常被分配给一个用户组。它是一种指定流程中的多个任务要由同一参与者完成的机制5。因此,在第一个任务被分配给某个泳道之后,流程就会记住所有在相同泳道内的后续任务都将由同一参与者完成。\

jBPM提供了两种定义任务分配的基本方法:作为流程定义的一部分或通过编程实现。如果是作为流程定义的一部分,分配可以通过指定具体用户、用户组或泳道 完成。此外,可以使用表达式根据流程变量动态确定某个具体用户。完整的编程实现是基于分配处理器(assignment handler)的6,它允许任务根据任意的计算规则去查找用户ID。

\

流程定义描述流程实例的方式类似任务描述任务实例的方式。当流程执行时,一个流程实例——流程的运行时表示——就会被创建。类似,一个任务实例——任务的运行时表示——就会被创建。根据任务定义,任务实例被分配给一个参与者/参与者组。

\

任务实例的一个作用就是支持用户交互——把数据显示给用户并从用户那里收集数据。一个jBPM任务实例拥有访问流程(令牌)变量7的全部权限,而且还可以有自己的变量。任务能够拥有自己的变量对于以下场景非常有用:

\
  • 在任务实例中创建流程变量的副本,这样对任务实例变量的即时更新只有在该任务完成且这些副本被提交给流程变量时才会影响流程变量。\
  • 创建更好支持用户活动的“派生(计算)”变量。\

任务自己的变量在jBPM中是通过任务控制器处理器(task controller handler)支持的8,它可以在任务实例创建时生成任务实例数据(从流程数据),并在任务实例完成时将任务实例数据提交给流程变量。

\

实现四眼原则

\

我们上面已经说过,实现四眼原则意味着要允许多人同时干一个活。它的实现有以下几种可能方法:

\
  • 在任务外解决:需要大量时间的任务并行循环(parallel looping)9 。\
  • 使用动作处理器(Action handler):附加到任务节点的进入事件(enter event),基于流程实例变量创建多个节点实例10。\
  • 在任务内解决:引入“任务接受量(task take)”(类似jPDL 4)并允许某个任务实例可被接受多次。\

根据jBPM最佳实践11 ——“扩展jBPM API而不是去搞复杂的流程建模”12 ,我决定采用任务内解决的方法。这就要求修改jBPM提供的任务和任务实例类。

\

扩展Task类

\

jBPM任务的定义被包含在org.jbpm.taskmgmt.def.Task类中。为了支持四眼原则,我们需要给类增加以下的字段/方法(清单1):

\
\  protected int numSignatures = 1;\\\  public int getNumSignatures(){\\t  return numSignatures;\  }\  public void setNumSignatures(int numSignatures){\\t  this.numSignatures = numSignatures;\  }
\

清单1 给Task类增加字段和方法

\

这个新的参数允许指定任务完成所需的任务处理人数量。缺省值为1,这意味着,只有1个用户应该/可以处理这个任务。

\

jBPM使用Hibernate来向数据库保存和读取数据。为了让我们新加的变量持久化,我们需要更新Task类的Hibernate配置文件(Task.hbm.xml),它在org.jbpm.taskmgmt.def文件夹中,增加代码如下(清单2)

\
\    \u0026lt;property name=\"numSignatures\" column=\"NUMSIGNATURES_\" /\u0026gt;
\

清单2 在Task映射文件中指定新增域

\

为了让我们新加的属性能被流程定义和数据库正确读取,我们需要修改org.jbpm.jpdl.xml.JpdlXmlReader类以正确地读取我们的新属性(清单3)

\
\String numSignatureText = taskElement.attributeValue(\"numSignatures\");\if (numSignatureText != null) {\   \ttry{\    \ttask.setNumSignatures(Integer.parseInt(numSignatureText));\    }\    catch(Exception e){}\}
\

清单3 读取numSignature属性

\

最后,因为JpdlXmlReader根据模式来验证XML,因此我们需要在jpdl-3.2.xsd中增加一个属性定义(清单4):

\
\  \u0026lt;xs:element name=\"task\"\u0026gt;\\t………………….\      \u0026lt;xs:attribute name=\"numSignatures\" type=\"xs:string\" /\u0026gt;\
\

清单4 在jpdl-3.2.xsd中增加numSignatures属性

\

当完成这些工作,任务定义就被扩展可以使用numSignatures属性(清单5):

\
 \u0026lt;task name=\"task2\" numSignatures = \"2\"\u0026gt;
\t\u0026lt;assignment pooled-actors=\"Peter, John\"\u0026gt;\u0026lt;/assignment\u0026gt;
\u0026lt;/task\u0026gt;\
\

清单5 给任务定义增加numSignatures属性

\

扩展TaskInstance类

\

在扩展完任务类后,我们还需要创建一个自定义的任务实例类来跟踪分配给该任务实例13的参与者,并确保所有被分配的参与者完成类执行(清单6)。

\
package com.navteq.jbpm.extensions;\\\import java.util.Date;\import java.util.LinkedList;\import java.util.List;\\\import org.jbpm.JbpmException;\import org.jbpm.taskmgmt.exe.TaskInstance;\\\public class AssignableTaskInstance extends TaskInstance {\\\\tprivate static final long serialVersionUID = 1L;\\\\tprivate List\u0026lt;Assignee\u0026gt; assignees = new LinkedList\u0026lt;Assignee\u0026gt;();\\\\tprivate String getAssigneeIDs(){\\t\tStringBuffer sb = new StringBuffer();\\t\tboolean first = true;\\t\tfor(Assignee a : assignees){\\t\t\tif(!first)\\t\t\t\tsb.append(\" \");\\t\t\telse \\t\t\t\tfirst = false;\\t\t\tsb.append(a.getUserID());\\t\t}\\t\treturn sb.toString();\\t}\\\tpublic List\u0026lt;Assignee\u0026gt; getAssignees() {\\t\treturn assignees;\\t}\\\\tpublic void reserve(String userID) throws JbpmException{\\\t\tif(task == null)\\t\t    throw new JbpmException(\"can't reserve instance with no task\");\\\t\t// Duplicate assignment is ok\\t\tfor(Assignee a : assignees){\\t\t\tif(userID.equals(a.getUserID()))\\t\t\t\treturn;\\t\t}\\\\t\t// Can we add one more guy?\\\\t\tif(task.getNumSignatures() \u0026gt; assignees.size()){\\t\t\tassignees.add(new Assignee(userID));\\t\t\treturn;\\t\t}\\\\t    throw new JbpmException(\"task is already reserved by \" +\getAssigneeIDs());\\\\t}\\\\tpublic void unreserve(String userID){\\\\t\tfor(Assignee a : assignees){\\t\t\tif(userID.equals(a.getUserID())){\\t\t\t\tassignees.remove(a);\\t\t\t\treturn;\\t\t\t}\\t\t}\\t}\\\\tprivate void completeTask(Assignee assignee, String transition){\\\\t\tassignee.setEndDate(new Date());\\\\t\t// Calculate completed assignments\\t\tint completed = 0;\\t\tfor(Assignee a : assignees){\\t\t\tif(a.getEndDate() != null)\\t\t\t\tcompleted ++;\\t\t}\\t\tif(completed \u0026lt; task.getNumSignatures())\\t\t\treturn;\\t\tif(transition == null)\\t\t\tend();\\t\telse \\t\t\tend(transition);\\t}\\\\tpublic void complete(String userID, String transition) throws JbpmException{\\\\t\tif(task == null)\\t\t    throw new JbpmException(\"can't complete instance with no task\");\\\\t\t// make sure it was reserved\\t\tfor(Assignee a : assignees){\\t\t\tif(userID.equals(a.getUserID())){\\t\t\t\tcompleteTask(a, transition);\\t\t\t\treturn;\\t\t\t}\\t\t}\\t    throw new JbpmException(\"task was not reserved by \" + userID);\\t}\\\\tpublic boolean isCompleted(){\\\\t\treturn (end != null);\\\\t}\}
\

清单6 扩展TaskInstance类

\

这个实现扩展了jBPM提供的TaskInstance类,并跟踪完成该实例所需的参与者个数。它引入了几个新方法,允许参与者预留(reserve)/退还(unreserve)任务实例,以及让指定参与者完成任务执行。

\

清单6的实现依赖一个支持类Assignee(清单7)

\
package com.navteq.jbpm.extensions;\\import java.io.Serializable;\import java.text.DateFormat;\import java.text.SimpleDateFormat;\import java.util.Date;\\\public class Assignee implements Serializable{\\\\tprivate static final long serialVersionUID = 1L;\\tprivate static final DateFormat dateFormat = new \SimpleDateFormat(\"yyyy/MM/dd HH:mm:ss\");\\\\tlong id = 0;\\tprotected String startDate = null;\\tprotected String userID = null;\\tprotected String endDate = null;\\\\tpublic Assignee(){}\\\\tpublic Assignee(String uID){\\\\t\tuserID = uID;\        startDate = dateFormat.format(new Date());\\t}\\\\Setters and Getters ///\\\\tpublic long getId() {\\t\treturn id;\\t}\\tpublic void setId(long id) {\\t\tthis.id = id;\\t}\\tpublic String getStartDate() {\\t\treturn startDate;\\t}\\tpublic void setStartDate(String startDate) {\\t\tthis.startDate = startDate;\\t}\\tpublic String getUserID() {\\t\treturn userID;\\t}\\tpublic void setUserID(String id) {\\t\tuserID = id;\\t}\\tpublic String getEndDate() {\\t\treturn endDate;\\t}\\tpublic void setEndDate(String endDate) {\\t\tthis.endDate = endDate;\\t}\\\\tpublic void setEndDate(Date endDate) {\\t\tthis.endDate = dateFormat.format(endDate);\\t}\\\\tpublic void setEndDate() {\\t\tthis.endDate = dateFormat.format(new Date());\\t}\\\\tpublic String toString(){\\\\t\tStringBuffer bf = new StringBuffer();\\t\tbf.append(\" Assigned to \");\\t\tbf.append(userID);\\t\tbf.append(\" at \");\\t\tbf.append(startDate);\\t\tbf.append(\" completed at \");\\t\tbf.append(endDate);\\t\treturn bf.toString();\\t}\}
\

清单7 Assignee类

\

自定义的TaskInstance类和Assignee类都必须保存到数据库中。这意味着需要给这两个类实现Hibernate映射14 (清单8,9):

\
\u0026lt;?xml version=\"1.0\"?\u0026gt;
\u0026lt;!DOCTYPE hibernate-mapping PUBLIC
\"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"
\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\"\u0026gt;

\u0026lt;hibernate-mapping auto-import=\"false\" default-access=\"field\"\u0026gt;

\u0026lt;subclass namename=\"com.navteq.jbpm.extensions.AssignableTaskInstance\"


extends=\" org.jbpm.taskmgmt.exe.TaskInstance\"
discriminator-value=\"A\"\u0026gt;
\u0026lt;list name=\" assignees\" cascade=\" all\" \u0026gt;
\u0026lt;key column=\" TASKINSTANCE_\" /\u0026gt;
\u0026lt;index column=\" TASKINSTANCEINDEX_\"/\u0026gt;
\u0026lt;one-to-many class=\" com.navteq.jbpm.extensions.Assignee\" /\u0026gt;
\u0026lt;/list\u0026gt;

\u0026lt;/subclass\u0026gt;


\u0026lt;/hibernate-mapping\u0026gt;\

清单8 自定义任务实例的Hibernate映射文件

\
\u0026lt;?xml version=\"1.0\"?\u0026gt;
\u0026lt;!DOCTYPE hibernate-mapping PUBLIC
\"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"
\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\"\u0026gt;

\u0026lt;hibernate-mapping auto-import=\"false\" default-access=\"field\"\u0026gt;


\u0026lt;class name=\" com.navteq.jbpm.extensions.Assignee\"
table=\"JBPM_ASSIGNEE\"\u0026gt;
\u0026lt;cache usage=\" nonstrict-read-write\"/\u0026gt;
\u0026lt;id name=\" id\" column=\" ID_\"\u0026gt;\u0026lt;generator class=\" native\" /\u0026gt;\u0026lt;/id\u0026gt;
\u0026lt;!-- Content --\u0026gt;
\u0026lt;property name=\" startDate\" \t column=\" STARTDATE_\" /\u0026gt;
\u0026lt;property name=\" userID\" \t\t column=\" USERID_\" /\u0026gt;
\u0026lt;property name=\" endDate\" \t column=\" ENDDATE_\" /\u0026gt;
\u0026lt;/class\u0026gt;
\u0026lt;/hibernate-mapping\u0026gt;\

清单9 Assignee类的Hibernate映射文件

\

要让jBPM能够使用我们的自定义任务实例实现,我们还需要提供一个自定义的任务实例工厂(清单10)。

\
package com.navteq.jbpm.extensions;\\\import org.jbpm.graph.exe.ExecutionContext;\import org.jbpm.taskmgmt.TaskInstanceFactory;\import org.jbpm.taskmgmt.exe.TaskInstance;\\\public class AssignableTaskInstanceFactory implements TaskInstanceFactory {\\\\tprivate static final long serialVersionUID = 1L;\\\\t@Override\\tpublic TaskInstance createTaskInstance(ExecutionContext executionContext) {\\\\t\treturn new AssignableTaskInstance();\\t}\\\}
\

清单10 自定义的任务实例工厂

\

最后,为了让jBPM运行时使用正确的任务实例工厂(清单10),还必须创建一个新的jBPM配置(清单11)。

\
\u0026lt;jbpm-configuration\u0026gt;

\u0026lt;bean name=\"jbpm.task.instance.factory\"


class=\" com.navteq.jbpm.extensions.AssignableTaskInstanceFactory\" singleton=\" true\"
/\u0026gt;

\u0026lt;/jbpm-configuration\u0026gt;

\

清单11 jBPM配置

\

完成所有这些变更之后(清单1-11),一个典型的任务处理显示如下:

\
\List\u0026lt;String\u0026gt; actorIds = new LinkedList\u0026lt;String\u0026gt;();\actorIds.add(\"Peter\");\List\u0026lt;TaskInstance\u0026gt; cTasks = jbpmContext.getGroupTaskList(actorIds)\TaskInstance cTask = cTasks.get(0);\AssignableTaskInstance aTask = (AssignableTaskInstance)cTask;\try{\\taTask.reserve(\"Peter\");\\t// Save\\tjbpmContext.close();\}\catch(Exception e){\\tSystem.out.println(\"Task \" + cTask.getName() + \" is already reserved\");\\te.printStackTrace();\}
\

清单12 处理可分配任务实例

\

这里,在得到某个用户的任务实例并将其转变成可分配任务实例之后,我们将试着预留它15。一旦预留成功,我们将关闭jBPM运行时以提交事务。

\

实现任命

\

JBoss jBPM可以非常轻易的实现手动将任务分配给特定用户。根据jBPM提供的简单API,可以完成将任务实例从一个任务列表移动到另一个任务列表,因此给某个用户分配任务相当直接(清单13)

\
\List\u0026lt;String\u0026gt; actorIds = new LinkedList\u0026lt;String\u0026gt;();\actorIds.add(\"admins\");\String actorID = \"admin\";\List\u0026lt;TaskInstance\u0026gt; cTasks = jbpmContext.getGroupTaskList(actorIds);\TaskInstance cTask = cTasks.get(0);\cTask.setPooledActors((Set)null);\cTask.setActorId(actorID);\
\

清单13 将任务重新分配给指定用户

\

jBPM提供了2类不同的API来设置参与者池:一类接收字符串id数组,另一类则接收id集合。如果要清空一个池,就要使用那个接收集合的API(传入一个null集合)。

\

实现上报

\

前面已经说过,上报一般被实现为任务的重新分配,并常常附带一个上报已发生的通知;或是实现成一个任务未及时完成的通知。

\

实现为重新分配的上报

\\

尽管jBPM不直接支持上报,但它提供了2个基本的机制:超时和重新分配(参见上节)。粗一看,实现上报只需将这二者结合即可,但是仔细一想还是存在一些困难:

\
  • jBPM实现中的关系并不总是双向的。如,从一个任务节点我们可以找到所有这个节点定义的任务,但是从一个任务,并没有API可以完成找到包含它的任务节点的工作16;由某个任务实例,你可以得到一个任务,但是没有由某个任务得到所有实例的API,诸如此类。 \
  • 超时不是发生在任务自身,而是发生在任务节点上。由于某个节点可以关联多个任务,并且jBPM关系实现并不是双向的(见上),因此要跟踪当前任务实例就需要其他的支持手段。\

以重新分配实现的上报的整个实现17涉及3个处理器:

\
  • 负责给任务分配参与者的分配处理器。这个处理器跟踪它是一个首次任务调用还是一个上报任务调用。清单14给出了一个分配处理器的例子。\
package com.sample.action;\\import org.jbpm.graph.def.Node;\import org.jbpm.graph.exe.ExecutionContext;\import org.jbpm.taskmgmt.def.AssignmentHandler;\import org.jbpm.taskmgmt.exe.Assignable;\\\public class EscalationAssignmentHandler implements AssignmentHandler {\\\tprivate static final long serialVersionUID = 1L;\\\t@Override\\tpublic void assign(Assignable assignable, ExecutionContext context)\\tthrows Exception {\\\\t\tNode task = context.getToken().getNode();\\t\tif(task != null){\\t\t\tString tName = task.getName();\\\\t\t\tString vName = tName + \"escLevel\";\\t\t\tLong escLevel = (Long)context.getVariable(vName);\\t\t\tif(escLevel == null){\\t\t\t\t// First time through\\t\t\t\tassignable.setActorId(\"admin\");\\t\t\t}\\t\t\telse{\\t\t\t\t// Escalate\\t\t\t\tassignable.setActorId(\"bob\");\\t\t\t}\\t\t}\\t}\}
\

清单14 分配处理器示例

\

这里我们尝试得到一个包含了给定任务上报次数的流程变量。如果变量未定义,则就分配“admin”为任务拥有者,否则任务就被分配给“bob”。在这个处理器中可以使用任何其他的分配策略。

\
  • 任务实例创建动作处理器(清单15),它保存流程实例上下文的任务实例id\
package com.sample.action;\\\import org.jbpm.graph.def.ActionHandler;\import org.jbpm.graph.def.Node;\import org.jbpm.graph.exe.ExecutionContext;\import org.jbpm.taskmgmt.exe.TaskInstance;\\\public class TaskCreationActionHandler implements ActionHandler {\\\\tprivate static final long serialVersionUID = 1L;\\\\t@Override\\tpublic void execute(ExecutionContext context) throws Exception {\\\t\tNode task = context.getToken().getNode();\\t\tTaskInstance current = context.getTaskInstance();\\t\tif((task == null) || (current == null))\\t\t\treturn;\\\\t\tString tName = task.getName();\\t\tString iName = tName + \"instance\";\\\\t\tcontext.setVariable(iName, new Long(current.getId()));\\t}\\}
\

清单15 任务实例创建处理器

\
  • 任务节点计时器触发调用的超时处理器(清单16)。\
package com.sample.action;\\\import org.jbpm.graph.def.ActionHandler;\import org.jbpm.graph.def.GraphElement;\import org.jbpm.graph.exe.ExecutionContext;\import org.jbpm.taskmgmt.exe.TaskInstance;\\\public class EscalationActionHandler implements ActionHandler {\\\\tprivate static final long serialVersionUID = 1L;\\\\tprivate String escalation;\\\\t@Override\\tpublic void execute(ExecutionContext context) throws Exception {\\\\t\tGraphElement task = context.getTimer().getGraphElement();\\t\tif(task == null)\\t\t\treturn;\\\\t\tString tName = task.getName();\\t\tString vName = tName + \"escLevel\";\\t\tlong escLevel = (long)context.getVariable(vName);\\t\tif(escLevel == null)\\t\t\tescLevel = new long(1);\\t\telse\\t\t\tescLevel += 1;\\t\tcontext.setVariable(vName, escLevel);\\t\tString iName = tName + \"instance\";\\\\t\tlong taskInstanceId = (long)context.getVariable(iName);\\\\t\tTaskInstance current = \context.getJbpmContext().getTaskInstance(taskInstanceId);\\\\t\tif(current != null){\\t\t\tcurrent.end(escalation);\\t\t}\\t}\}
\

清单16 超时处理器

\

这个处理器首先记录上报计数器,接着完成此节点关联的任务实例。任务实例的完成伴随有一个变迁(一般是回到任务节点)。

\

使用以上描述的处理器实现上报的简单流程例子显示在清单17中。

\
\u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\"?\u0026gt;
\u0026lt;process-definition
xmlns=\"urn:jbpm.org:jpdl-3.2\"
name=\"escalationHumanTaskTest\"\u0026gt;
\u0026lt;start-state name=\"start\"\u0026gt;
\t\t\u0026lt;transition to=\"customTask\"\u0026gt;\u0026lt;/transition\u0026gt;
\t\u0026lt;/start-state\u0026gt;
\u0026lt;task-node name=\"customTask\"\u0026gt;
\t\t\u0026lt;task name=\"task2\"\u0026gt;
\t\t\t\u0026lt;assignment class=\"com.sample.action.EscalationAssignmentHandler\"\u0026gt;\u0026lt;
/assignment\u0026gt;
\t\t\u0026lt;/task\u0026gt;
\t\t\u0026lt;event type=\"task-create\"\u0026gt;
\t\t\t\u0026lt;action name=\"Instance Tracking\" class=\"com.sample.action.TaskCreationActionHandler\"\u0026gt;\u0026lt;/action\u0026gt;
\t\t\u0026lt;/event\u0026gt;
\t\t\u0026lt;timer duedate=\"10 second\" name=\"Escalation timeout\"\u0026gt;
\t\t\t\u0026lt;action class=\"com.sample.action.EscalationActionHandler\"\u0026gt;
\t\t\t\t\u0026lt;escalation\u0026gt;
\t\t\t\t\tescalation
\t\t\t\t\u0026lt;/escalation\u0026gt;
\t\t\t\u0026lt;/action\u0026gt;
\t\t\u0026lt;/timer\u0026gt;

\t\t\u0026lt;transition to=\"end\" name=\"to end\"\u0026gt;\u0026lt;/transition\u0026gt;


\t\t\u0026lt;transition to=\" customTask\" name=\" escalation\"\u0026gt;\u0026lt;/transition\u0026gt;
\t\u0026lt;/task-node\u0026gt;
\t\u0026lt;end-state name=\" end\"\u0026gt;\u0026lt;/end-state\u0026gt;
\u0026lt;/process-definition\u0026gt;\

清单17 简单流程的上报

\

实现成通知的上报

\

jBPM为邮件传递提供了强大支持18,这使得实现成通知的上报变得极其简单。邮件传递可由给节点附加定时器,然后触发,它使用已经写好的邮件动作来完成通知传递。

\

实现链状执行

\

链状执行直接由jBPM泳道支持,并不需要额外的开发。

\

总结

\

不管我们在自动化方面投入多少努力,面对复杂的业务流程,总免不了要有人工介入的可能。在这篇文章中,我给出了一系列已建立的高级人工交互模式,并展示了用jBPM完成它是多么轻而易举。

\\

查看英文原文:Supporting Advanced User Interaction Patterns in jBPM

\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值