Introduction

This blog introduces sending periodical e-mail reminders in Alfresco Activiti Workflows.

Workflow with periodical e-mail reminders

To send periodical e-mail reminders (as well as to do any other periodical action) we have to start from defining the workflow. Let’s assume that we extend already existing workflow ‘Review And Approve Activiti Process’ defined in file ‘review.bpmn20.xml’. We will add periodical reminder (‘E-mail Reminder’) to review task to remind user that this task is pending by sending him e-mail.

E-mail Reminders Workflow</a>

Corresponding XML looks like follows. Two tasks were added to it:

  • Timer, which is boundaryEvent with id ‘timer_review’
  • E-mail Reminder, which is serviceTask with id ‘mailtask_review’
<?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://alfresco.org">
  <process id="activitiReview" name="Review And Approve Activiti Process" isExecutable="true">
 
    <startEvent id="start" activiti:formKey="wf:submitReviewTask"></startEvent>
 
    <sequenceFlow id="flow1" sourceRef="start" targetRef="reviewTask"></sequenceFlow>
 
    <userTask id="reviewTask" name="Review Task" activiti:assignee="${bpm_assignee.properties.userName}" activiti:formKey="wf:activitiReviewTask">
      <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;
            </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'));
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
 
    <sequenceFlow id="flow2" sourceRef="reviewTask" targetRef="reviewDecision"></sequenceFlow>
 
    <exclusiveGateway id="reviewDecision" name="Review Decision"></exclusiveGateway>
 
    <sequenceFlow id="flow3" sourceRef="reviewDecision" targetRef="approved">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${wf_reviewOutcome == 'Approve'}]]></conditionExpression>
    </sequenceFlow>
 
    <sequenceFlow id="flow4" sourceRef="reviewDecision" targetRef="rejected"></sequenceFlow>
 
    <userTask id="approved" name="Document Approved" activiti:assignee="${initiator.exists() ? initiator.properties.userName : 'admin'}" activiti:formKey="wf:approvedTask">
      <documentation>The document was reviewed and approved.</documentation>
      <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;
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
 
    <userTask id="rejected" name="Document Rejected" activiti:assignee="${initiator.exists() ? initiator.properties.userName : 'admin'}" activiti:formKey="wf:rejectedTask">
      <documentation>The document was reviewed and rejected.</documentation>
      <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;
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
 
    <sequenceFlow id="flow5" sourceRef="approved" targetRef="end"></sequenceFlow>
 
    <sequenceFlow id="flow6" sourceRef="rejected" targetRef="end"></sequenceFlow>
 
    <endEvent id="end"></endEvent>
 
    <boundaryEvent id="timer_review" name="Timer" attachedToRef="reviewTask" cancelActivity="false">
        <timerEventDefinition>
            <timeCycle>0 0 0 ? * MON</timeCycle>
    </timerEventDefinition>
    </boundaryEvent>
 
    <serviceTask id="mailtask_review" name="E-mail Reminder" activiti:class="org.alfresco.repo.workflow.activiti.script.AlfrescoScriptDelegate">
         <extensionElements>
             <activiti:field name="script">
                 <activiti:string>
 
                     logger.log("E-mail reminder - review task");
 
                     // Send e-mail reminder if assignee defined
                     if (bpm_assignee != null) {
                         var mail = actions.create("mail");
                         mail.parameters.to = bpm_assignee.properties.userName;
                         mail.parameters.subject = "Task reminder - document " + bpm_workflowDescription + " pending review";
                         mail.parameters.ignore_send_failure = true;
                         mail.execute(companyhome);
                     }
 
                 </activiti:string>
             </activiti:field>
         </extensionElements>
    </serviceTask>
 
    <sequenceFlow id="flow7" sourceRef="timer_review" targetRef="mailtask_review"></sequenceFlow>
 
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_activitiReview">
    <bpmndi:BPMNPlane bpmnElement="activitiReview" id="BPMNPlane_activitiReview">
      <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
        <omgdc:Bounds height="35.0" width="35.0" x="30.0" y="200.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewTask" id="BPMNShape_reviewTask">
        <omgdc:Bounds height="55.0" width="105.0" x="125.0" y="190.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewDecision" id="BPMNShape_reviewDecision">
        <omgdc:Bounds height="40.0" width="40.0" x="290.0" y="197.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="approved" id="BPMNShape_approved">
        <omgdc:Bounds height="55.0" width="105.0" x="390.0" y="97.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="rejected" id="BPMNShape_rejected">
        <omgdc:Bounds height="55.0" width="105.0" x="390.0" y="297.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
        <omgdc:Bounds height="35.0" width="35.0" x="555.0" y="307.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="timer_review" id="BPMNShape_timer_review">
        <omgdc:Bounds height="30.0" width="30.0" x="175.0" y="230.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="mailtask_review" id="BPMNShape_mailtask_review">
        <omgdc:Bounds height="55.0" width="105.0" x="137.0" y="297.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="65.0" y="217.0"></omgdi:waypoint>
        <omgdi:waypoint x="125.0" y="217.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="230.0" y="217.0"></omgdi:waypoint>
        <omgdi:waypoint x="290.0" y="217.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="310.0" y="197.0"></omgdi:waypoint>
        <omgdi:waypoint x="310.0" y="124.0"></omgdi:waypoint>
        <omgdi:waypoint x="390.0" y="124.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="310.0" y="237.0"></omgdi:waypoint>
        <omgdi:waypoint x="310.0" y="324.0"></omgdi:waypoint>
        <omgdi:waypoint x="390.0" y="324.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="495.0" y="124.0"></omgdi:waypoint>
        <omgdi:waypoint x="572.0" y="124.0"></omgdi:waypoint>
        <omgdi:waypoint x="572.0" y="307.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="495.0" y="324.0"></omgdi:waypoint>
        <omgdi:waypoint x="555.0" y="324.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="190.0" y="260.0"></omgdi:waypoint>
        <omgdi:waypoint x="189.0" y="297.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Timer

Complete reference to boundaryEvent can be found here. In our case we care about two attributes:

  • attachedToRef – indicates task which starts timer
  • cancelActivity – if set to ‘false’ does not cancel original task (defined in attachedToRef attribute)

Complete reference to timerEventDefinitions can be found here. In our case we selected to use timeCycle element, which specifies repeating interval and can be given as cron expression, e.g., run ‘E-mail Reminder’ task every Monday at 00:00am – 0 0 0 ? * MON

E-mail Reminder

This task is serviceTask, which means that it executes without any user interaction. In our case we define it as ‘org.alfresco.repo.workflow.activiti.script.AlfrescoScriptDelegate’ class to make it possible to write JavaScript code, which uses Alfresco JavaScript API. In this task, simply send e-mail with reminder to user, which was selected as review assignee.