今天我们来讨论一下怎么将Activiti BPMN 2.0工作流部署到Alfresco Share上。
先简单介绍一下理论知识。在Alfresco中添加工作流需要三个必要文件,分别是:
BPMN 2.0 定义文件:定义了整个数据流的流程,已经描述了数据流中所使用的节点。节点描述包括节点类型,如用户任务节点,流程分支节点,起始节点,结束节点等等。
数据模型定义文件:每个用户任务节点都需要用户参与。在Alfresco中,我们是通过表单来允许用户输入数据,与任务进行互动。那么表单的数 据保存在什么地方呢?就是这些数据模型。因此,在Alfresco中,我们需要定义数据模型,即元数据,来与工作流任务进行绑定。
Share表单描述文件:这个猜也猜得到,是用来告诉share,怎样显示工作流中的表单。
好了,有了以上的理论知识,我们开始创建我们的工作流。这里我打算用一个创建ticket的简单例子给大家作演示。整个过程就是 business启动工作流,把工作流指派给support队伍。support队伍创建了一个ticket,并指派technical队伍去解决这个 ticket。工作流本身并没有太大意义,只是起到演示作用。我们首先会使用Activiti Workflow Deisgner来创建工作流定义文件。
如果没有安装Designer,请参看Activiti Workflow HelloWorld示例与测试环境搭建。
具体步骤如下:
1.新建一个普通Java项目
在根目录下创建一个”alfresco”文件夹,并在此文件夹内创建三个文件夹,分别是”extension”, “web-extension”, “workflow”。
- extension用来存放spring加载文件,例如如何加载工作流文件。
- web-extension用来存放share form配置文件。
- workflow用来存放工作流定义文件和数据模型文件。
项目结构如下图所示:
2、 在workflow文件夹中创建Activiti工作图,如下图所示,创建文件,选择Activiti Diagram。
点击下一步
我们会得到一个干净的设计面板。按照下图红色的序号顺序,依次拖动工作流元素,创建如图所示工作流。
点击保存,注意Activiti Designer会生成相应的bpmn20.xml文件,并默认保存到”recources.diagrams”目录下,这不是我们想要得。我们手动 把”CreateTicket.bpmn20.xml”文件移动到workflow目录下。删除其它两个文件。
3. 双击打开” CreateTicket.bpmn20.xml”文件,其内容大致如下,非常简单的定义:
现在我们开始修改这个文件,我个人喜欢直接手动修改xml文件,不大习惯用设计面板,感觉比较直接,呵呵。工作流定义部分内容修改大致如下:
<?xml version=“1.0″ encoding=“UTF-8″?> <definitions xmlns=“http://www.omg.org/spec/BPMN/20100524/MODEL” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:activiti=“http://activiti.org/bpmn” xmlns:bpmndi=“http://www.omg.org/spec/BPMN/20100524/DI” xmlns:omgdc=“http://www.omg.org/spec/DD/20100524/DC” xmlns:omgdi=“http://www.omg.org/spec/DD/20100524/DI” xmlns:ot=“http://www.alfresco.org/model/ticketcreate3_workflow/1.0″ typeLanguage=“http://www.w3.org/2001/XMLSchema” expressionLanguage=“http://www.w3.org/1999/XPath” targetNamespace=“http://www.activiti.org/test”> <process id=“CreateTicket” name=“CreateTicket”> <documentation>Place documentation for the ‘CreateTicket’ process here.</documentation> <startEvent id=“workstart” name=“Start” activiti:formKey=“ot:CreateTicket”></startEvent> <userTask id=“createTicket” name=“Create Ticket” activiti:formKey=“ot:supportTicket”> <extensionElements> <activiti:taskListener event=“complete” class=“org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener”> <activiti:field name=“script”> <activiti:string> execution.setVariable(‘ot_reviewresult’, task.getVariableLocal(‘ot_reviewOutcome’)); </activiti:string> </activiti:field> </activiti:taskListener> </extensionElements> <humanPerformer> <resourceAssignmentExpression> <formalExpression>${reviewAssignee}</formalExpression> </resourceAssignmentExpression> </humanPerformer> <!– For each assignee, task is created –> <multiInstanceLoopCharacteristics isSequential=“false”> <loopDataInputRef>wf_supportTeam</loopDataInputRef> <inputDataItem name=“reviewAssignee” /> <completionCondition>${ot_reviewresult == ‘Complete’}</completionCondition> </multiInstanceLoopCharacteristics> </userTask> <userTask id=“resolveTicket” name=“Resolve Ticket” activiti:formKey=“ot:resolveTicket”> <extensionElements> <activiti:taskListener event=“complete” class=“org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener”> <activiti:field name=“script”> <activiti:string> execution.setVariable(‘ot_ticketresult’, task.getVariableLocal(‘ot_ticketstatus’)); </activiti:string> </activiti:field> </activiti:taskListener> </extensionElements> <humanPerformer> <resourceAssignmentExpression> <formalExpression>${reviewAssignee}</formalExpression> </resourceAssignmentExpression> </humanPerformer> <!– For each assignee, task is created –> <multiInstanceLoopCharacteristics isSequential=“false”> <loopDataInputRef>wf_technicalTeam</loopDataInputRef> <inputDataItem name=“reviewAssignee” /> <completionCondition>${ot_ticketresult == ‘Complete’}</completionCondition> </multiInstanceLoopCharacteristics> </userTask> <endEvent id=“endevent1″ name=“End”></endEvent> <sequenceFlow id=“flow4″ name=“” sourceRef=“workstart” targetRef=“createTicket”> <extensionElements> <activiti:executionListener event=“start” class=“org.alfresco.repo.workflow.activiti.listener.ScriptExecutionListener”> <activiti:field name=“script”> <activiti:string> if (logger.isLoggingEnabled()) { logger.log(“Please log something for create ticket…”); } logger.log(“people : ” + people); logger.log(“bpm_groupAssignee : ” + bpm_groupAssignee); var members = people.getMembers(bpm_groupAssignee); logger.log(“members : ” + members); var memberNames = new java.util.ArrayList(); logger.log(“memberNames : ” + memberNames); for(var i in members) { logger.log(“i : ” + i); memberNames.add(members[i].properties.userName); logger.log(“members[i].properties : ” + members[i].properties); logger.log(“members[i].properties.userName : ” + members[i].properties.userName); } execution.setVariable(‘wf_supportTeam’, memberNames); </activiti:string> </activiti:field> </activiti:executionListener> </extensionElements> </sequenceFlow> <sequenceFlow id=“flow5″ name=“” sourceRef=“createTicket” targetRef=“resolveTicket”> <extensionElements> <activiti:executionListener event=“start” class=“org.alfresco.repo.workflow.activiti.listener.ScriptExecutionListener”> <activiti:field name=“script”> <activiti:string> var members = people.getMembers(bpm_groupAssignee); var memberNames = new java.util.ArrayList(); for(var i in members) { memberNames.add(members[i].properties.userName); } execution.setVariable(‘wf_technicalTeam’, memberNames); </activiti:string> </activiti:field> </activiti:executionListener> </extensionElements> </sequenceFlow> <sequenceFlow id=“flow6″ name=“” sourceRef=“resolveTicket” targetRef=“endevent1″></sequenceFlow> </process> <bpmndi:BPMNDiagram id=“BPMNDiagram_CreateTicket”> <bpmndi:BPMNPlane bpmnElement=“CreateTicket” id=“BPMNPlane_CreateTicket”> <bpmndi:BPMNShape bpmnElement=“createTicket” id=“BPMNShape_workstart”> <omgdc:Bounds height=“55″ width=“105″ x=“250″ y=“250″></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement=“resolveTicket” id=“BPMNShape_alfrescoUsertask2″> <omgdc:Bounds height=“55″ width=“105″ x=“430″ y=“250″></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement=“endevent1″ id=“BPMNShape_endevent1″> <omgdc:Bounds height=“35″ width=“35″ x=“640″ y=“260″></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement=“workstart” id=“BPMNShape_alfrescoStartevent1″> <omgdc:Bounds height=“35″ width=“35″ x=“140″ y=“260″></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement=“flow5″ id=“BPMNEdge_flow2″> <omgdi:waypoint x=“355″ y=“277″></omgdi:waypoint> <omgdi:waypoint x=“430″ y=“277″></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement=“flow6″ id=“BPMNEdge_flow3″> <omgdi:waypoint x=“535″ y=“277″></omgdi:waypoint> <omgdi:waypoint x=“640″ y=“277″></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement=“flow4″ id=“BPMNEdge_flow4″> <omgdi:waypoint x=“175″ y=“277″></omgdi:waypoint> <omgdi:waypoint x=“250″ y=“277″></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions>
这里需要注意的地方:
Alfresco workflow使用扩展script listener,listener使用javascript来操作工作流数据。如createTicket 任务中使用javascript设置“ot_reviewresult”工作流变量。这里先取得用户任务中的本地变量 “ot_reviewOutcome”,再设置“ot_reviewresult”,后续的节点中都可以应用这个变量。
这个工作流中,我们使用了“multiInstanceLoopCharacteristics”,表示我们创建了多个用户任务实例。由于我们 的任务是指派给整个工作小组的,所以小组中每个成员都会接到一个任务实例。小组中的任何一人完成了任务,即判断条件为 “${ot_reviewresult == ‘Complete’}”,小组中其它成员的工作流实例便会消失,工作流便会被向前推动一个节点。
在script listener中,我们可以用javascript log打印信息,帮助我们调试。记得要启动javascript logger,具体方法为在TOMCAT_HOME/webapps/alfresco/WEB-INF/log4j.properties中修改 log4j.logger.org.alfresco.repo.jscript.ScriptLogger=debug
activiti:formKey这个很重要,他对应了我们将要定义的元数据中某一个具体的类型。这个类型是用来保存与其所关联的用户任务的输入信息。同时这个属性也表示了将用share中哪个form来渲染这个用户任务。
4. 创建我们的元数据模型,用来保存用户任务数据。
在workflow目录下创建CreateTicketModel-custom.xml文件,其内如如下:
<?xml version=“1.0″ encoding=“UTF-8″?> <model name=“ot:createticketmodel” xmlns=“http://www.alfresco.org/model/dictionary/1.0″> <imports> <import uri=“http://www.alfresco.org/model/dictionary/1.0″ prefix=“d” /> <import uri=“http://www.alfresco.org/model/bpm/1.0″ prefix=“bpm” /> </imports> <namespaces> <namespace uri=“http://www.alfresco.org/model/ticketcreate3_workflow/1.0″ prefix=“ot” /> </namespaces> <types> <type name=“ot:supportTicket”> <parent>bpm:workflowTask</parent> <properties> <property name=“ot:reviewOutcome”> <type>d:text</type> <default>Reject</default> <constraints> <constraint name=“ot:reviewOutcomeOptions” type=“LIST”> <parameter name=“allowedValues”> <list> <value>Complete</value> <value>Wait</value> </list> </parameter> </constraint> </constraints> </property> </properties> <mandatory-aspects> <aspect>bpm:groupAssignee</aspect> </mandatory-aspects> </type> <type name=“ot:CreateTicket”> <parent>bpm:startTask</parent> <mandatory-aspects> <aspect>bpm:groupAssignee</aspect> </mandatory-aspects> </type> <type name=“ot:resolveTicket”> <parent>bpm:workflowTask</parent> <properties> <property name=“ot:result”> <type>d:text</type> </property> <property name=“ot:ticketstatus”> <type>d:text</type> <default>Reject</default> <constraints> <constraint name=“ot:reviewOutcomeOptions2″ type=“LIST”> <parameter name=“allowedValues”> <list> <value>Complete</value> <value>Wait</value> </list> </parameter> </constraint> </constraints> </property> </properties> </type> </types> </model>
这个数据模型比较简单,创建了3个 type,分别对应了上面我们所定义的工作流中的一个开始节点和两个用户任务。注意每个type都继承了bpm数据模型中的某个type。这些type的 名字要与工作流中节点的activiti:formKey属性对应起来。
5. 创建Share用户任务表单配置。
在web-extension目录下创建share-config-custom.xml文件,其内容如下:
<alfresco-config> <config evaluator=“string-compare” condition=“activiti$CreateTicket”> <forms> <form> <field-visibility> <show id=“bpm:groupAssignee” /> <show id=“bpm:workflowPriority” /> </field-visibility> <appearance> <field id=“bpm:groupAssignee” label-id=“workflow.field.review_group” /> <field id=“bpm:workflowPriority” label-id=“workflow.field.priority”> <control template=“/org/alfresco/components/form/controls/workflow/priority.ftl” /> </field> </appearance> </form> </forms> </config> <config evaluator=“task-type” condition=“ot:supportTicket”> <forms> <form> <field-visibility> <show id=“bpm:groupAssignee” /> <show id=“ot:reviewOutcome” /> </field-visibility> <appearance> <field id=“bpm:groupAssignee” label-id=“workflow.field.review_group” /> <field id=“ot:reviewOutcome” label-id=“workflow.field.outcome”> <control template=“/org/alfresco/components/form/controls/workflow/activiti-transitions.ftl” /> </field> </appearance> </form> </forms> </config> <config evaluator=“task-type” condition=“ot:resolveTicket”> <forms> <form> <field-visibility> <show id=“ot:result” /> <show id=“ot:ticketstatus” /> </field-visibility> <appearance> <field id=“ot:result” label-id=“result” /> <field id=“ot:ticketstatus” label-id=“workflow.field.outcome”> <control template=“/org/alfresco/components/form/controls/workflow/activiti-transitions.ftl” /> </field> </appearance> </form> </forms> </config> </alfresco-config>
这里我们分别为一个开始节点和两个用户任务定义了显示界面。注意start节点的evaluator使用” string-compare”,用户任务节点使用” task-type”。
(未完)查看剩余章节