cancel
Showing results for 
Search instead for 
Did you mean: 

Activiti Multi Instance tasks and jobs for recurrent tasks

koallen
Champ in-the-making
Champ in-the-making
We are looking to implement activiti 5.11 , I have a use case that I am not sure how to implement in activiti.

We need to schedule recurrent visits by a nurse to visit a patient at home, this may take place over a 30 day period for example. We also need to send multiple reminders to the nurse to submit the notes for each visit before transitioning to the next activity that requires the nurse to submit a missed visit report if the reminders are ignored.

The expected flow is that multiple instances are created and the timer for each instance should expire at different times, each triggering a separate escalation task based on whether the nurse submitted her notes before the timers expire .So, there might be five visits scheduled but escalation required for just two visits.

I set this up using the timecycle tag of timerevent definition with the intent that the escalation task is created every time the cycle is invoked.

Currently, the activity is set as multi-instance and the number of instances is passed as a process variable.

The test currently sets the number of requested instances at 5, and the number of instances are correctly created.

My expectation is that a timer is created for each created instance and that a job is created for each instance.

When I run the test, I see that there is just one job in the db rather than five jobs for each of the created instances.The job runs once and the test ends.

I am new to activiti after working with jbpm 3 for a number of years, what I would like to know is if my projected workflow is possible with activiti or whether it is possible to work directly with the Engine API to create the multiple instances myself and create the jobs as needed.

My diagram ,bpm20.xml file and test case are below.

I am not able to upload any attachments to this post, because I am getting errors when I try to do so,so, I cant provide the diagram


Thanks



<?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" xmlnsSmiley Surprisedmgdc="http://www.omg.org/spec/DD/20100524/DC" xmlnsSmiley Surprisedmgdi="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="visitsEmailReport" name="Visits Email" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="visitPatient" name="VisitPatient">
      <extensionElements>
        <activiti:taskListener event="create" class="com.hha.curatio.activiti.listeners.VisitsEscalatorListener"></activiti:taskListener>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="false">
        <loopCardinality>${nrOfVisits}</loopCardinality>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="startVisitsflow" name="Start Visits" sourceRef="startevent1" targetRef="visitPatient"></sequenceFlow>
    <boundaryEvent id="boundarytimer1" name="Timer" attachedToRef="visitPatient" cancelActivity="false">
      <timerEventDefinition>
        <timeCycle>R5/PT45S</timeCycle>
      </timerEventDefinition>
    </boundaryEvent>
    <endEvent id="endevent1" name="End"></endEvent>
    <parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
    <sequenceFlow id="flow3" sourceRef="parallelgateway1" targetRef="endevent1"></sequenceFlow>
    <userTask id="usertask1" name="Escalate">
      <extensionElements>
        <activiti:taskListener event="create" class="com.hha.curatio.activiti.listeners.VisitsEscalatorListener"></activiti:taskListener>
      </extensionElements>
    </userTask>
    <exclusiveGateway id="exgwvisits" name="Exclusive Gateway"></exclusiveGateway>
    <userTask id="usertask2" name="Missed Visit Report">
      <extensionElements>
        <activiti:taskListener event="create" class="com.hha.curatio.activiti.listeners.VisitsEscalatorListener"></activiti:taskListener>
      </extensionElements>
    </userTask>
    <sequenceFlow id="completedVisitflow" name="Completed Visit Flow" sourceRef="exgwvisits" targetRef="parallelgateway1">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${missedVisitsReport == false}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow9" sourceRef="usertask1" targetRef="parallelgateway1"></sequenceFlow>
    <sequenceFlow id="flow10" sourceRef="usertask2" targetRef="parallelgateway1"></sequenceFlow>
    <sequenceFlow id="flowtoMissedVisit" sourceRef="exgwvisits" targetRef="usertask2">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${missedVisitsReport == false}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flowtoEscalate" sourceRef="boundarytimer1" targetRef="usertask1"></sequenceFlow>
    <sequenceFlow id="flow11" sourceRef="visitPatient" targetRef="exgwvisits"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_visitsEmailReport">
    <bpmndi:BPMNPlane bpmnElement="visitsEmailReport" id="BPMNPlane_visitsEmailReport">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="40.0" y="80.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="visitPatient" id="BPMNShape_visitPatient">
        <omgdc:Bounds height="55.0" width="105.0" x="228.0" y="64.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="boundarytimer1" id="BPMNShape_boundarytimer1">
        <omgdc:Bounds height="30.0" width="30.0" x="320.0" y="74.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="263.0" y="400.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="parallelgateway1" id="BPMNShape_parallelgateway1">
        <omgdc:Bounds height="40.0" width="40.0" x="260.0" y="300.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="562.0" y="61.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="20.0" y="173.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="exgwvisits" id="BPMNShape_exgwvisits">
        <omgdc:Bounds height="40.0" width="40.0" x="250.0" y="180.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="280.0" y="340.0"></omgdi:waypoint>
        <omgdi:waypoint x="280.0" y="400.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
        <omgdi:waypoint x="614.0" y="116.0"></omgdi:waypoint>
        <omgdi:waypoint x="612.0" y="319.0"></omgdi:waypoint>
        <omgdi:waypoint x="300.0" y="320.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10">
        <omgdi:waypoint x="72.0" y="228.0"></omgdi:waypoint>
        <omgdi:waypoint x="72.0" y="319.0"></omgdi:waypoint>
        <omgdi:waypoint x="260.0" y="320.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="completedVisitflow" id="BPMNEdge_completedVisitflow">
        <omgdi:waypoint x="270.0" y="220.0"></omgdi:waypoint>
        <omgdi:waypoint x="280.0" y="300.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="42.0" width="100.0" x="-15.0" y="-20.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="startVisitsflow" id="BPMNEdge_startVisitsflow">
        <omgdi:waypoint x="75.0" y="97.0"></omgdi:waypoint>
        <omgdi:waypoint x="228.0" y="91.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="100.0" x="10.0" y="0.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
        <omgdi:waypoint x="280.0" y="119.0"></omgdi:waypoint>
        <omgdi:waypoint x="270.0" y="180.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flowtoEscalate" id="BPMNEdge_flowtoEscalate">
        <omgdi:waypoint x="350.0" y="89.0"></omgdi:waypoint>
        <omgdi:waypoint x="562.0" y="88.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flowtoMissedVisit" id="BPMNEdge_flowtoMissedVisit">
        <omgdi:waypoint x="250.0" y="200.0"></omgdi:waypoint>
        <omgdi:waypoint x="125.0" y="200.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>


/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.hha.curatio.activiti;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ManagementService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.jobexecutor.JobExecutor;
import org.activiti.engine.impl.util.ClockUtil;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.JobQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.ActivitiRule;
import org.activiti.engine.test.Deployment;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"activiti-context.xml"})
public class VisitsEmailReportTest {

    @Autowired
    @Rule
    public ActivitiRule actRule;

    @Autowired
    private RuntimeService rtService;
    @Autowired
    private TaskService tskService;
    @Autowired
    private ManagementService mgtService;

    @Autowired
    private SpringProcessEngineConfiguration processEngineConfig;

    @Deployment(resources={"com/hha/curatio/activiti/VisitsEmailReport.bpmn20.xml"})
    @Test
    public void testitShouldRuntimersForVisits(){
        Map<String, Object> variables = new HashMap<String, Object>();
    variables.put("nrOfVisits", "5");
    ProcessInstance processInstance = rtService
        .startProcessInstanceByKey("visitsEmailReport", variables);

//    Task task = tskService.createTaskQuery().taskName("VisitPatient")
//        .singleResult();
//    assertEquals("VisitPatient", task.getName());
        List<Task> tasks;
        tasks = tskService.createTaskQuery().taskName("VisitPatient").list();
        assertEquals(1, tasks.size());
//        for(Task ta : tasks){
//
//    taskService.complete(ta.getId());
//        }


    Calendar nowCal = new GregorianCalendar();
    nowCal.add(Calendar.MINUTE, 30);
    ClockUtil.setCurrentTime(nowCal.getTime());
    JobQuery jobQuery = mgtService.createJobQuery().processInstanceId(processInstance.getId());
    assertEquals(1, jobQuery.count());
    waitForJobExecutorToProcessAllJobs(50000L, 25L);

//    Task task2 = tskService.createTaskQuery().taskName("Escalate").singleResult();
//    assertNotNull(task2);
//    assertEquals("Escalate", task2.getName());

//    tskService.complete(task2.getId());
    }

    public void waitForJobExecutorToProcessAllJobs(long maxMillisToWait, long intervalMillis) {
    JobExecutor jobExecutor = processEngineConfig.getJobExecutor();
    jobExecutor.start();

    try {
      Timer timer = new Timer();
      InteruptTask task = new InteruptTask(Thread.currentThread());
      timer.schedule(task, maxMillisToWait);
      boolean areJobsAvailable = true;
      try {
        while (areJobsAvailable && !task.isTimeLimitExceeded()) {
          Thread.sleep(intervalMillis);
          areJobsAvailable = areJobsAvailable();
        }
      } catch (InterruptedException e) {
      } finally {
        timer.cancel();
      }
      if (areJobsAvailable) {
        throw new ActivitiException("time limit of " + maxMillisToWait + " was exceeded");
      }

    } finally {
      jobExecutor.shutdown();
    }
  }

     public boolean areJobsAvailable() {
         boolean jobs = false;
        List<Job> list = mgtService
                      .createJobQuery()
                      .executable()
                      .list();
        jobs = mgtService
                          .createJobQuery()
                          .executable()
                          .list()
                          .isEmpty();

    return jobs;
  }

     private static class InteruptTask extends TimerTask {
    protected boolean timeLimitExceeded = false;
    protected Thread thread;
    public InteruptTask(Thread thread) {
      this.thread = thread;
    }
    public boolean isTimeLimitExceeded() {
      return timeLimitExceeded;
    }
    public void run() {
      timeLimitExceeded = true;
      thread.interrupt();
    }
  }

}

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://www.activiti.org/schema/spring/components"
   xsi:schemaLocation="http://www.springframework.org/schema/beans   
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context 
                           http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/tx      
                           http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                           http://www.activiti.org/schema/spring/components
                           http://www.activiti.org/schema/spring/components/activiti.xsd">

   <activiti:annotation-driven process-engine="processEngine" />

<!–   
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
   </bean>–>

   <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
      <property name="databaseType" value="mysql" />
      <property name="dataSource" ref="bonecpDS" />
      <property name="transactionManager" ref="transactionManager" />
      <property name="databaseSchemaUpdate" value="true" />
      <property name="history" value="audit" />
      <property name="jobExecutorActivate" value="false" />
   </bean>

   <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
      <property name="processEngineConfiguration" ref="processEngineConfiguration" />
   </bean>

   <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
   <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
   <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
   <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
   <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
        <bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />

       <bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule" >
          <property name="processEngine" ref="processEngine" />
       </bean>

</beans>
4 REPLIES 4

trademak
Star Contributor
Star Contributor
Hi,

It's not fully clear to me what the exact requirements are, but it sounds like the multi instance should not be defined on a user task but on a sub process.
The sub process then contains a user task with a boundary timer on it. Then you get for example 5 user tasks with a boundary timer job for every 5 of them.

Best regards,

koallen
Champ in-the-making
Champ in-the-making
Thanks for the quick response, I agree that multi-instance sub processes should fix the issue.I will go ahead and re-implement with sub processes.

Love your book by the way, easy to follow.

koallen
Champ in-the-making
Champ in-the-making
Following on from the last question, the multi-instance sub processes create the multiple tasks, but only one timer is created. I checked the database and job count variable, just one timer is created even though there are five tasks created.

koallen
Champ in-the-making
Champ in-the-making
I just realized that I was testing the timer from the wrong spot.