Introduction

This post describes how to configure workflow in Alfresco framework using Activiti engine. Instructions on how to set up the development environment can be found here. More information about workflows in Alfresco can be found on Alfresco wiki page. I also found the following very useful:

Before you start make sure that you have Activiti BPMN 2.0 designer, which is an Eclipse plugin. This makes edition of workflow models easier.

Workflow description

We are going to create the following workflow:

In a software development company there are 3 teams: sales, management, and developers. Sales team has to have information about work estimates, e.g., number of development days. When development work is required, sales person contacts management and requests estimate for the work. More accurate description of the work (say scope document) is stored in external (to Alfresco) Project Management systems. One of the managers sends the request to developer to do the estimate for the work. The estimate comes back to the manager and after approval is returned to the sales person. If there is no approval for the estimate it comes back do the developer to make some changes. This workflow is presented in the following picture created in Activiti BPMN 2.0 designer.

  • Workflow is started by a sales person.
  • ‘Assign Estimate Task’ is done by a manager.
  • ‘Estimate Task’ is done by a developer.
  • ‘Review Estimate’ is done by the manager requesting estimate.
  • ‘Estimate Approved’ is done by the sales person requesting estimate.

The following XML file is associated with the workflow above:

<?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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="estimate" name="estimate">
 
    <startEvent id="start" name="Start"></startEvent>
    <userTask id="assignEstimateTask" name="Assign Estimate Task">
    </userTask>
    <userTask id="estimateTask" name="Estimate Task">
    </userTask>
    <userTask id="reviewEstimate" name="Review Estimate">
    </userTask>
    <exclusiveGateway id="reviewDecision" name="Review Decision"></exclusiveGateway>
    <userTask id="estimateApproved" name="Estimate Approved">
    </userTask>
    <endEvent id="end" name="End"></endEvent>
    <sequenceFlow id="flow1" name="" sourceRef="start" targetRef="assignEstimateTask"></sequenceFlow>
    <sequenceFlow id="flow3" name="" sourceRef="assignEstimateTask" targetRef="estimateTask"></sequenceFlow>
    <sequenceFlow id="flow4" name="" sourceRef="estimateTask" targetRef="reviewEstimate"></sequenceFlow>
    <sequenceFlow id="flow5" name="" sourceRef="reviewEstimate" targetRef="reviewDecision"></sequenceFlow>
    <sequenceFlow id="flow6" name="" sourceRef="reviewDecision" targetRef="estimateApproved">
    </sequenceFlow>
    <sequenceFlow id="flow7" name="" sourceRef="estimateApproved" targetRef="end"></sequenceFlow>
    <sequenceFlow id="flow8" name="" sourceRef="reviewDecision" targetRef="estimateTask"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_estimate">
    <bpmndi:BPMNPlane bpmnElement="estimate" id="BPMNPlane_estimate">
      <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
        <omgdc:Bounds height="35" width="35" x="30" y="200"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="assignEstimateTask" id="BPMNShape_assignEstimateTask">
        <omgdc:Bounds height="55" width="105" x="105" y="190"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="estimateTask" id="BPMNShape_estimateTask">
        <omgdc:Bounds height="55" width="105" x="250" y="190"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewEstimate" id="BPMNShape_reviewEstimate">
        <omgdc:Bounds height="55" width="105" x="395" y="190"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewDecision" id="BPMNShape_reviewDecision">
        <omgdc:Bounds height="40" width="40" x="540" y="197"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="estimateApproved" id="BPMNShape_estimateApproved">
        <omgdc:Bounds height="55" width="105" x="620" y="137"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
        <omgdc:Bounds height="35" width="35" x="765" y="147"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="65" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="105" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="210" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="250" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="355" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="395" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="500" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="540" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="560" y="197"></omgdi:waypoint>
        <omgdi:waypoint x="560" y="164"></omgdi:waypoint>
        <omgdi:waypoint x="620" y="164"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="725" y="164"></omgdi:waypoint>
        <omgdi:waypoint x="765" y="164"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
        <omgdi:waypoint x="560" y="237"></omgdi:waypoint>
        <omgdi:waypoint x="447" y="327"></omgdi:waypoint>
        <omgdi:waypoint x="302" y="245"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Let’s name this XML file estimate.bpmn20.xml.

Integration of Activiti workflows in Alfresco

There are several points of integration Activiti framework with Alfresco. They are described below.

Script execution listeners

When workflow is started then the script execution starts. There is common execution object accessible through all the script execution (from start to end) to share information between script tasks. Execution object has org.alfresco.repo.workflow.activiti.listener.ScriptExecutionListener defined. There are 3 events defined for the listener: start, end, and take. The events can be used to execute some code within the workflow. In the workflow model (estimate.bpmn20.xml) it is possible to define script execution listeners in extensionElements tags and use JavaScript API and JavaScript Services API within the code to be run.

When workflow starts we want to obtain full name of ‘Management’ group and save it in script variable wf_managementGroup to be accessible in all the tasks within the workflow. The following piece of code shows how to do it. groups.getGroup is defined in JavaScript Services API and is used to get names of all the groups.

 <process id="estimate" name="estimate">
 
    <extensionElements>
      <activiti:executionListener event="start" class="org.alfresco.repo.workflow.activiti.listener.ScriptExecutionListener">
        <activiti:field name="script">
          <activiti:string>execution.setVariable('wf_managementGroup', groups.getGroup('Management').getFullName());</activiti:string>
        </activiti:field>
      </activiti:executionListener>
    </extensionElements>
 
    <startEvent id="start" name="Start"></startEvent>

Script task listeners

For each task in the workflow, where user action is required (userTask tag), except script execution listener it is possible to use script task listener. When the task starts new task object is created for each task and this object is accessible during the task execution. There are 3 events defined for org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener: create, assignment, and complete.

Let’s say that on the task start we want to assign some script execution variables to task variables. The following piece of code shows how to do it. In the example below script execution variable bpm_workflowDueDate, which corresponds to due date of the workflow is assigned to dueDate property of the task. Note that due date for workflow can be different from due date of task.

 <userTask id="assignEstimateTask" name="Assign Estimate Task">
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>
              if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate;
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>

Connection between task in the flow and appropriate form in Alfresco share

To display each task in Alfresco share workflow forms are used. They are configured in share-workflow-form-config.xml file. In the workflow model (estimate.bpmn20.xml) it is possible to put attribute activiti:formKey in userTask tag, which is points to appropriate form defined in Alfresco share. To connect user tasks with forms it is necessary to define new type and use its name in workflow model and corresponding form configuration.

Let’s say that we defined wf:assignEstimateTask type. The type overrides bpm:packageActionGroup property of bpm:workflowTask type and has 2 mandatory aspects: bpm:assignee and wf:workInfo. The corresponding code is presented below.

<type name="wf:assignEstimateTask">
			<parent>bpm:workflowTask</parent>
 
			<overrides>
 
				<property name="bpm:packageActionGroup">
					<default>add_package_item_actions</default>
				</property>
 
			</overrides>
 
			<mandatory-aspects>
				<aspect>bpm:assignee</aspect>
				<aspect>wf:workInfo</aspect>
			</mandatory-aspects>
		</type>

This type is linked with workflow model using activiti:formKey attribute.

<userTask id="assignEstimateTask" name="Assign Estimate Task" activiti:formKey="wf:assignEstimateTask">

In the form configuration (share-workflow-form-config.xml) there is filter which displays form depending on the task type. The forms identify which properties of the type should be displayed and where/how to display them. The sample form configuration is presented below.

<config evaluator="task-type" condition="wf:assignEstimateTask">
		<forms>
			<form>
				<field-visibility>
					<show id="message" />
					<show id="bpm:dueDate" />
					<show id="bpm:priority" />
					<show id="wf:workDescription" />
					<show id="packageItems" />
					<show id="bpm:assignee" />
					<show id="bpm:comment" />
					<show id="transitions" />
				</field-visibility>
				<appearance>
					<set id="" appearance="title" label-id="workflow.set.general" />
					<set id="info" appearance=""
						template="/org/alfresco/components/form/2-column-set.ftl" />
					<set id="items" appearance="title" label-id="workflow.set.items" />
					<set id="assignee" appearance="title" label-id="workflow.set.assignee" />
					<set id="work" appearance="title" label-id="workflow.set.work" />
					<set id="other" appearance="title" label-id="workflow.set.other" />
					<set id="response" appearance="title" label-id="workflow.set.response" />
 
 
					<field id="message" label-id="workflow.field.message">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:dueDate" label-id="workflow.field.due" set="info" />
					<field id="bpm:priority" label-id="workflow.field.priority"
						set="info">
						<control
							template="/org/alfresco/components/form/controls/workflow/priority.ftl" />
					</field>
 
					<field id="packageItems" set="items" />
 
					<field id="wf:workDescription" set="work">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:assignee" label-id="workflow.field.assign_to"
						set="assignee" />
 
					<field id="bpm:comment" label-id="workflow.field.comment"
						set="response">
						<control template="/org/alfresco/components/form/controls/textarea.ftl" />
					</field>
					<field id="transitions" set="response" />
 
				</appearance>
			</form>
		</forms>
	</config>

This form is displayed as presented below:

Please note that wf:workDescription is property of wf:workInfo aspect, which is defined as follows.

	<aspect name="wf:workInfo">
			<properties>
				<property name="wf:workDescription">
					<type>d:text</type>
					<mandatory>true</mandatory>
				</property>
			</properties>
		</aspect>

Assignment to the task

There are 2 attributes that describe person/group that is responsible for the task. Both of them are set when task is being created and indicate, for each users it should be displayed. activiti:assignee identifies single user that is assigned to the task. activiti:candidateGroups identifies groups of users that claim the task.

Sample definitions of user/group assignment are presented below.

	 <userTask id="assignEstimateTask" name="Assign Estimate Task" activiti:candidateGroups="${wf_managementGroup}" activiti:formKey="wf:assignEstimateTask">
 <userTask id="estimateTask" name="Estimate Task" activiti:assignee="${bpm_assignee.properties.userName}" activiti:formKey="wf:estimateTask">

Decision in the workflow

In the workflow presented it is possible to make decision in ‘Review Estimate’ task whether estimate should be accepted or rejected. It is important to know that when sequenceFlow tags from workflow model (estimate.bpmn20.xml) are evaluated first matching one is executed. There are 2 flows (flow6 and flow8) from ‘Review Estimate’ task that either point to ‘Estimate Task’ or to ‘Estimate Approved’ task. To follow appropriate route there should be condition set on the first of them. In that way when condition is true first sequence will fire. If not, the second one will be executed. That condition can depend on some script execution variable, e.g., wf_reviewOutcome that was set in task execution listener on complete event. The code below presents part of workflow model which sets and uses variable responsible for workflow path choice.

 <userTask id="reviewEstimate" name="Review Estimate" activiti:assignee="${wf_manager.properties.userName}" activiti:formKey="wf:reviewEstimate">
      <extensionElements>
        <activiti:taskListener event="complete" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome'));</activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
   <sequenceFlow id="flow6" name="" sourceRef="reviewDecision" targetRef="estimateApproved">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${wf_reviewOutcome
				== 'Approve'}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow8" name="" sourceRef="reviewDecision" targetRef="estimateTask"></sequenceFlow>

After all the introduction let’s go through all the steps necessary to define the workflow.

Step 1 Define workflow model and customize it

Define workflow model in Activiti BPMN 2.0 designer and save it as alfresco/WebContent/WEB-INF/classes/alfresco/workflow/estimate.bpmn20.xml or as WEB-INF/classes/alfresco/workflow/estimate.bpmn20.xml in your Alfresco deployment directory on Tomcat server. Update the model to set necessary variables and point to appropriate types. Full listing of the file estimate.bpmn20.xmlused in this example is presented below.

  <?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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="estimate" name="estimate">
    <extensionElements>
      <activiti:executionListener event="start" class="org.alfresco.repo.workflow.activiti.listener.ScriptExecutionListener">
        <activiti:field name="script">
          <activiti:string>execution.setVariable('wf_managementGroup', groups.getGroup('Management').getFullName());</activiti:string>
        </activiti:field>
      </activiti:executionListener>
    </extensionElements>
    <startEvent id="start" name="Start" activiti:formKey="wf:estimate"></startEvent>
    <userTask id="assignEstimateTask" name="Assign Estimate Task" activiti:candidateGroups="${wf_managementGroup}" activiti:formKey="wf:assignEstimateTask">
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate;
if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
if (typeof bpm_comment != 'undefined') task.setVariable('bpm_comment', bpm_comment);
</activiti:string>
          </activiti:field>
        </activiti:taskListener>
        <activiti:taskListener event="complete" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>execution.setVariable('bpm_assignee', task.getVariable('bpm_assignee'));
execution.setVariable('bpm_comment', task.getVariable('bpm_comment'));
execution.setVariable('wf_manager', person);
execution.setVariable('bpm_dueDate', task.dueDate);
execution.setVariable('bpm_priority', task.priority);</activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
    <userTask id="estimateTask" name="Estimate Task" activiti:assignee="${bpm_assignee.properties.userName}" activiti:formKey="wf:estimateTask">
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_dueDate;
if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_priority;
if (typeof bpm_comment != 'undefined') task.setVariable('bpm_comment', bpm_comment);</activiti:string>
          </activiti:field>
        </activiti:taskListener>
        <activiti:taskListener event="complete" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>execution.setVariable('bpm_comment', task.getVariable('bpm_comment'));</activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
    <userTask id="reviewEstimate" name="Review Estimate" activiti:assignee="${wf_manager.properties.userName}" activiti:formKey="wf:reviewEstimate">
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate;
if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
if (typeof bpm_comment != 'undefined') task.setVariable('bpm_comment', bpm_comment);</activiti:string>
          </activiti:field>
        </activiti:taskListener>
        <activiti:taskListener event="complete" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome'));
execution.setVariable('bpm_comment', task.getVariable('bpm_comment'));</activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
    <exclusiveGateway id="reviewDecision" name="Review Decision"></exclusiveGateway>
    <userTask id="estimateApproved" name="Estimate Approved" activiti:assignee="${initiator.exists() ? initiator.properties.userName : 'admin'}" activiti:formKey="wf:estimateApproved">
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate;
if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
if (typeof bpm_comment != 'undefined') task.setVariable('bpm_comment', bpm_comment);</activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
    <endEvent id="end" name="End"></endEvent>
    <sequenceFlow id="flow1" name="" sourceRef="start" targetRef="assignEstimateTask"></sequenceFlow>
    <sequenceFlow id="flow3" name="" sourceRef="assignEstimateTask" targetRef="estimateTask"></sequenceFlow>
    <sequenceFlow id="flow4" name="" sourceRef="estimateTask" targetRef="reviewEstimate"></sequenceFlow>
    <sequenceFlow id="flow5" name="" sourceRef="reviewEstimate" targetRef="reviewDecision"></sequenceFlow>
    <sequenceFlow id="flow6" name="" sourceRef="reviewDecision" targetRef="estimateApproved">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${wf_reviewOutcome
				== 'Approve'}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow7" name="" sourceRef="estimateApproved" targetRef="end"></sequenceFlow>
    <sequenceFlow id="flow8" name="" sourceRef="reviewDecision" targetRef="estimateTask"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_estimate">
    <bpmndi:BPMNPlane bpmnElement="estimate" id="BPMNPlane_estimate">
      <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
        <omgdc:Bounds height="35" width="35" x="30" y="200"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="assignEstimateTask" id="BPMNShape_assignEstimateTask">
        <omgdc:Bounds height="55" width="105" x="105" y="190"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="estimateTask" id="BPMNShape_estimateTask">
        <omgdc:Bounds height="55" width="105" x="250" y="190"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewEstimate" id="BPMNShape_reviewEstimate">
        <omgdc:Bounds height="55" width="105" x="395" y="190"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewDecision" id="BPMNShape_reviewDecision">
        <omgdc:Bounds height="40" width="40" x="540" y="197"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="estimateApproved" id="BPMNShape_estimateApproved">
        <omgdc:Bounds height="55" width="105" x="620" y="137"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
        <omgdc:Bounds height="35" width="35" x="765" y="147"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="65" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="105" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="210" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="250" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="355" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="395" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="500" y="217"></omgdi:waypoint>
        <omgdi:waypoint x="540" y="217"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="560" y="197"></omgdi:waypoint>
        <omgdi:waypoint x="560" y="164"></omgdi:waypoint>
        <omgdi:waypoint x="620" y="164"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="725" y="164"></omgdi:waypoint>
        <omgdi:waypoint x="765" y="164"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
        <omgdi:waypoint x="560" y="237"></omgdi:waypoint>
        <omgdi:waypoint x="447" y="327"></omgdi:waypoint>
        <omgdi:waypoint x="302" y="245"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Step 2 Define types

Next step is to define types used in the model in activiti:formKey attribute. The types can:

  • inherit properties from other types (parent tag)
  • override inherited properties (overrides tag)
  • define new properties (properties tag)
  • add aspects to the types (mandatory-aspects tag)

To note, the task properties have to be set at the beginning of each task, because new task object is created each time new task starts. Therefore the following line is present in example above if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate;. Aspect objects once created are not destroyed until the end of script execution. This is good way to keep some variables that once set are applicable to all the tasks like in our case ‘link to the document’. The properties from aspects can be used in the form configuration. Definition of all the types used in the example is presented below. They were saved in the following location: /alfresco/WebContent/WEB-INF/classes/alfresco/workflow/workflowModel-custom.xml

<?xml version="1.0" encoding="UTF-8"?>
 
<model name="wf:workflowmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
 
	<imports>
		<!-- Import Alfresco Dictionary Definitions -->
		<import uri="http://www.alfresco.org/model/dictionary/1.0"
			prefix="d" />
		<!-- Import Alfresco System Definitions -->
		<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys" />
		<!-- Import Alfresco Content Domain Model Definitions -->
		<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" />
		<!-- Import User Model Definitions -->
		<import uri="http://www.alfresco.org/model/user/1.0" prefix="usr" />
		<import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm" />
	</imports>
 
	<namespaces>
		<namespace uri="http://www.alfresco.org/model/workflow/1.0"
			prefix="wf" />
	</namespaces>
 
	<types>
 
		<type name="wf:estimate">
			<parent>bpm:startTask</parent>
 
			<mandatory-aspects>
				<aspect>wf:workInfo</aspect>
			</mandatory-aspects>
 
		</type>
 
		<type name="wf:assignEstimateTask">
			<parent>bpm:workflowTask</parent>
 
			<overrides>
 
				<property name="bpm:packageActionGroup">
					<default>add_package_item_actions</default>
				</property>
 
			</overrides>
 
			<mandatory-aspects>
				<aspect>bpm:assignee</aspect>
				<aspect>wf:workInfo</aspect>
			</mandatory-aspects>
		</type>
 
		<type name="wf:estimateTask">
			<parent>bpm:workflowTask</parent>
 
			<overrides>
 
				<property name="bpm:packageActionGroup">
					<default>add_package_item_actions</default>
				</property>
 
				<property name="bpm:packageItemActionGroup">
					<default>edit_package_item_actions</default>
				</property>
 
			</overrides>
 
			<mandatory-aspects>
				<aspect>bpm:assignee</aspect>
				<aspect>wf:workInfo</aspect>
			</mandatory-aspects>
		</type>
 
 
		<type name="wf:reviewEstimate">
			<parent>bpm:activitiOutcomeTask</parent>
			<properties>
				<property name="wf:reviewOutcome">
					<type>d:text</type>
					<default>Reject</default>
					<constraints>
						<constraint name="wf:reviewOutcomeOptions" type="LIST">
							<parameter name="allowedValues">
								<list>
									<value>Approve</value>
									<value>Reject</value>
								</list>
							</parameter>
						</constraint>
					</constraints>
				</property>
			</properties>
			<overrides>
				<property name="bpm:packageItemActionGroup">
					<default>edit_package_item_actions</default>
				</property>
				<property name="bpm:outcomePropertyName">
					<default>{http://www.alfresco.org/model/workflow/1.0}reviewOutcome
					</default>
				</property>
			</overrides>
			<mandatory-aspects>
				<aspect>bpm:assignee</aspect>
				<aspect>wf:workInfo</aspect>
			</mandatory-aspects>
		</type>
 
 
		<type name="wf:estimateApproved">
			<parent>bpm:workflowTask</parent>
 
			<mandatory-aspects>
				<aspect>bpm:assignee</aspect>
				<aspect>wf:workInfo</aspect>
			</mandatory-aspects>
		</type>
 
	</types>
 
	<aspects>
 
		<aspect name="wf:workInfo">
			<properties>
				<property name="wf:workDescription">
					<type>d:text</type>
					<mandatory>true</mandatory>
				</property>
			</properties>
		</aspect>
 
	</aspects>
 
</model>

Step 3 Define workflow forms

For each task in the workflow it is possible to configure page to be shown. Is is done using config tag and evaluator and condition attributes. Part of /share/WebContent/WEB-INF/classes/alfresco/share-workflow-form-config.xml used in Alfresco share and responsible for rendering appropriate workflow forms is presented below. Please note, that first step of the flow does not have type defined therefore instead of task-type eveluator string-compare eveluator should be used evaluator="string-compare" condition="activiti$estimate", where ‘estimate’ is process id (id="estimate" name="estimate") defined in estimate.bpmn20.xml file. For all the other steps evaluator="task-type" is used and condition corresponds to type defined for particular step.

<config evaluator="string-compare" condition="activiti$estimate">
		<forms>
			<form>
				<field-visibility>
					<show id="bpm:workflowDescription" />
					<show id="bpm:workflowDueDate" />
					<show id="bpm:workflowPriority" />
					<show id="wf:workDescription" />
					<show id="packageItems" />
					<show id="bpm:comment" />
					<show id="bpm:sendEMailNotifications" />
				</field-visibility>
				<appearance>
					<set id="" appearance="title" label-id="workflow.set.general" />
					<set id="info" appearance=""
						template="/org/alfresco/components/form/2-column-set.ftl" />
					<set id="items" appearance="title" label-id="workflow.set.items" />
					<set id="work" appearance="title" label-id="workflow.set.work" />
					<set id="other" appearance="title" label-id="workflow.set.other" />
					<set id="response" appearance="title" label-id="workflow.set.response" />
 
					<field id="bpm:workflowDescription" label-id="workflow.field.message">
						<control template="/org/alfresco/components/form/controls/textarea.ftl">
							<control-param name="style">width: 95%</control-param>
						</control>
					</field>
 
					<field id="bpm:workflowDueDate" label-id="workflow.field.due"
						set="info" />
					<field id="bpm:workflowPriority" label-id="workflow.field.priority"
						set="info">
						<control
							template="/org/alfresco/components/form/controls/workflow/priority.ftl" />
					</field>
 
 
					<field id="packageItems" set="items" />
					<field id="wf:workDescription" set="work" />
 
					<field id="bpm:sendEMailNotifications" set="other">
						<control
							template="/org/alfresco/components/form/controls/workflow/email-notification.ftl" />
					</field>
 
					<field id="bpm:comment" label-id="workflow.field.comment"
						set="response">
						<control template="/org/alfresco/components/form/controls/textarea.ftl" />
					</field>
				</appearance>
			</form>
		</forms>
	</config>
 
 
	<config evaluator="task-type" condition="wf:assignEstimateTask">
		<forms>
			<form>
				<field-visibility>
					<show id="message" />
					<show id="bpm:dueDate" />
					<show id="bpm:priority" />
					<show id="wf:workDescription" />
					<show id="packageItems" />
					<show id="bpm:assignee" />
					<show id="bpm:comment" />
					<show id="transitions" />
				</field-visibility>
				<appearance>
					<set id="" appearance="title" label-id="workflow.set.general" />
					<set id="info" appearance=""
						template="/org/alfresco/components/form/2-column-set.ftl" />
					<set id="items" appearance="title" label-id="workflow.set.items" />
					<set id="assignee" appearance="title" label-id="workflow.set.assignee" />
					<set id="work" appearance="title" label-id="workflow.set.work" />
					<set id="other" appearance="title" label-id="workflow.set.other" />
					<set id="response" appearance="title" label-id="workflow.set.response" />
 
 
					<field id="message" label-id="workflow.field.message">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:dueDate" label-id="workflow.field.due" set="info" />
					<field id="bpm:priority" label-id="workflow.field.priority"
						set="info">
						<control
							template="/org/alfresco/components/form/controls/workflow/priority.ftl" />
					</field>
 
					<field id="packageItems" set="items" />
 
					<field id="wf:workDescription" set="work">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:assignee" label-id="workflow.field.assign_to"
						set="assignee" />
 
					<field id="bpm:comment" label-id="workflow.field.comment"
						set="response">
						<control template="/org/alfresco/components/form/controls/textarea.ftl" />
					</field>
					<field id="transitions" set="response" />
 
				</appearance>
			</form>
		</forms>
	</config>
 
	<config evaluator="task-type" condition="wf:estimateTask">
		<forms>
			<form>
				<field-visibility>
					<show id="message" />
					<show id="bpm:dueDate" />
					<show id="bpm:priority" />
					<show id="wf:workDescription" />
					<show id="packageItems" />
					<show id="bpm:comment" />
					<show id="transitions" />
				</field-visibility>
				<appearance>
					<set id="" appearance="title" label-id="workflow.set.general" />
					<set id="info" appearance=""
						template="/org/alfresco/components/form/2-column-set.ftl" />
					<set id="items" appearance="title" label-id="workflow.set.items" />
					<set id="work" appearance="title" label-id="workflow.set.work" />
					<set id="other" appearance="title" label-id="workflow.set.other" />
					<set id="response" appearance="title" label-id="workflow.set.response" />
 
 
					<field id="message" label-id="workflow.field.message">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:priority" label-id="workflow.field.priority"
						set="info" read-only="true">
						<control
							template="/org/alfresco/components/form/controls/workflow/priority.ftl" />
					</field>
					<field id="bpm:dueDate" set="info" label-id="workflow.field.due">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="packageItems" set="items" />
 
					<field id="wf:workDescription" set="work">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:comment" label-id="workflow.field.comment"
						set="response">
						<control template="/org/alfresco/components/form/controls/textarea.ftl" />
					</field>
 
					<field id="transitions" set="response" />
				</appearance>
			</form>
		</forms>
	</config>
 
 
	<config evaluator="task-type" condition="wf:reviewEstimate">
		<forms>
			<form>
				<field-visibility>
					<show id="message" />
					<show id="bpm:dueDate" />
					<show id="bpm:priority" />
					<show id="wf:workDescription" />
					<show id="packageItems" />
					<show id="bpm:comment" />
					<show id="wf:reviewOutcome" />
					<show id="transitions" />
				</field-visibility>
				<appearance>
					<set id="" appearance="title" label-id="workflow.set.general" />
					<set id="info" appearance=""
						template="/org/alfresco/components/form/2-column-set.ftl" />
					<set id="items" appearance="title" label-id="workflow.set.items" />
					<set id="work" appearance="title" label-id="workflow.set.work" />
					<set id="other" appearance="title" label-id="workflow.set.other" />
					<set id="response" appearance="title" label-id="workflow.set.response" />
 
 
					<field id="message" label-id="workflow.field.message">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:priority" label-id="workflow.field.priority"
						set="info" read-only="true">
						<control
							template="/org/alfresco/components/form/controls/workflow/priority.ftl" />
					</field>
					<field id="bpm:dueDate" set="info" label-id="workflow.field.due">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="packageItems" set="items" />
 
					<field id="wf:workDescription" set="work">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:comment" label-id="workflow.field.comment"
						set="response">
						<control template="/org/alfresco/components/form/controls/textarea.ftl" />
					</field>
 
					<field id="wf:reviewOutcome" set="response" />
					<field id="transitions" set="response" />
 
				</appearance>
			</form>
		</forms>
	</config>
 
 
 
	<config evaluator="task-type" condition="wf:estimateApproved">
		<forms>
			<form>
				<field-visibility>
					<show id="message" />
					<show id="bpm:dueDate" />
					<show id="bpm:priority" />
					<show id="wf:workDescription" />
					<show id="packageItems" />
					<show id="bpm:comment" />
					<show id="transitions" />
				</field-visibility>
				<appearance>
					<set id="" appearance="title" label-id="workflow.set.general" />
					<set id="info" appearance=""
						template="/org/alfresco/components/form/2-column-set.ftl" />
					<set id="items" appearance="title" label-id="workflow.set.items" />
					<set id="work" appearance="title" label-id="workflow.set.work" />
					<set id="other" appearance="title" label-id="workflow.set.other" />
					<set id="response" appearance="title" label-id="workflow.set.response" />
 
 
					<field id="message" label-id="workflow.field.message">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:priority" label-id="workflow.field.priority"
						set="info" read-only="true">
						<control
							template="/org/alfresco/components/form/controls/workflow/priority.ftl" />
					</field>
					<field id="bpm:dueDate" set="info" label-id="workflow.field.due">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="packageItems" set="items" />
 
					<field id="wf:workDescription" set="work">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="bpm:comment" label-id="workflow.field.comment"
						set="response">
						<control template="/org/alfresco/components/form/controls/info.ftl" />
					</field>
 
					<field id="transitions" set="response" />
 
				</appearance>
			</form>
		</forms>
	</config>

Step 4 Translate messages

All the translations are defined in /alfresco/WebContent/WEB-INF/classes/alfresco/workflow/workflow-messages_xx.properties

Step 5 Add new configuration files to Alfresco bootstrap sequence

Bootstrap configuration is defined in /alfresco/WebContent/WEB-INF/classes/alfresco/bootstrap-context.xml file. Please add path to workflow model file (alfresco/workflow/estimate.bpmn20.xml), path to model file with new types (alfresco/workflow/workflowModel-custom.xml ), and labels with translation if used instead of workflow-messages file.

    <bean id="workflowBootstrap" parent="workflowDeployer">
        <property name="workflowDefinitions">
            <list>
                ...
 
                <!-- Activiti estimate service workflow definition -->
                <props>
                    <prop key="engineId">activiti</prop>
                    <prop key="location">alfresco/workflow/estimate.bpmn20.xml</prop>
                    <prop key="mimetype">text/xml</prop>
                    <prop key="redeploy">false</prop>
                </props>
 
            </list>
        </property>
        <property name="models">
            <list>
               ...
               <value>alfresco/workflow/workflowModel-custom.xml</value>
            </list>
        </property>
        <property name="labels">
            <list>
               ...
               <value>alfresco/workflow/workflow-messages-custom</value>
            </list>
        </property>

Step 6 Start Alfresco and Alfresco Share

Log in as administrator to Alfresco. Otherwise you may not be able to connect to workflow console.

Step 7 Deploy type definition in Alfresco workflow console

Go to: http://localhost:8080/alfresco/faces/jsp/admin/workflow-console.jsp

To deploy estimate workflow definitions type:

deploy activiti alfresco/workflow/estimate.bpmn20.xml

If there was other version of estimate workflow definition it might be necessary to redeploy it. To do so you have to remove all the workflows that use it and then remove estimate workflow definition. This will ensure that there are no estimate workflow definition versions present in the system. Deploy estimate workflow definitions type.

delete all workflows imeanit
undeploy definition name activiti$estimate
deploy activiti alfresco/workflow/estimate.bpmn20.xml

This console might be handy for some workflow debugging.

Step 8

Enjoy the workflow.