Timer Boundary and Catching Event usage from Java code

Dear all,

I’m new to Flowable so it may sounds like a trivial question but I actually don’t understand how to proper use the TimerBoundaryEvent and TimerCatchingEvent.

There are 2 behaviours I would like to achieve in my workflow:

  1. If a tasks last longer than X seconds, than move to another task (which I guess is the TimerBoundaryEvent )
  2. Wait X seconds between two tasks (which I guess is the TimerCatchingEvent)

To obtain this I created two very simple workflows
SampleWorkflow1 SampleWorkflow2

In the first case I set the time duration to PT2S, in the first task I have a thread.sleep(5000) which should make the task last more than the timeout. What I get here is that the process terminates without the second task to be executed.
I the second case I get the same behaviour like whatever comes after the first task is ignored.

I add below the diagram xml and the java classes (task delegates and test class).

I tried having a look at other topics or examples but they did not help me.
Could you please help me with that?

Thanks a lot,
Jacopo

Diagram 1

<?xml version="1.0" encoding="UTF-8"?>
      <process id="myProcess" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <serviceTask id="sampleWorkflow" name="Task1" activiti:class="workflows.SampleTask"></serviceTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="sampleWorkflow"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow2" sourceRef="servicetask1" targetRef="endevent1"></sequenceFlow>
    <serviceTask id="servicetask1" name="Task2" activiti:class="workflows.SampleTask2"></serviceTask>
    <boundaryEvent id="boundarytimer1" name="Timer" attachedToRef="sampleWorkflow" cancelActivity="true">
      <timerEventDefinition>
        <timeDuration>PT2S</timeDuration>
      </timerEventDefinition>
    </boundaryEvent>
    <sequenceFlow id="flow3" sourceRef="boundarytimer1" targetRef="servicetask1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="70.0" y="70.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sampleWorkflow" id="BPMNShape_sampleWorkflow">
        <omgdc:Bounds height="55.0" width="105.0" x="180.0" y="60.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="boundarytimer1" id="BPMNShape_boundarytimer1">
        <omgdc:Bounds height="30.0" width="30.0" x="271.0" y="74.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="540.0" y="71.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
        <omgdc:Bounds height="55.0" width="105.0" x="350.0" y="61.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="105.0" y="87.0"></omgdi:waypoint>
        <omgdi:waypoint x="180.0" y="87.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="455.0" y="88.0"></omgdi:waypoint>
        <omgdi:waypoint x="540.0" y="88.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="301.0" y="89.0"></omgdi:waypoint>
        <omgdi:waypoint x="350.0" y="88.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Diagram 2

<?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:xsd="http://www.w3.org/2001/XMLSchema" 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="myProcess" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <serviceTask id="sampleWorkflow" name="Task1" activiti:class="workflows.SampleTask"></serviceTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="sampleWorkflow"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow2" sourceRef="servicetask1" targetRef="endevent1"></sequenceFlow>
    <serviceTask id="servicetask1" name="Task2" activiti:class="workflows.SampleTask2"></serviceTask>
    <eventBasedGateway id="eventgateway1" name="Event Gateway"></eventBasedGateway>
    <sequenceFlow id="flow3" sourceRef="sampleWorkflow" targetRef="eventgateway1"></sequenceFlow>
    <intermediateCatchEvent id="timerintermediatecatchevent1" name="TimerCatchEvent">
      <timerEventDefinition>
        <timeDuration>PT2S</timeDuration>
      </timerEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="flow4" sourceRef="eventgateway1" targetRef="timerintermediatecatchevent1"></sequenceFlow>
    <sequenceFlow id="flow5" sourceRef="timerintermediatecatchevent1" targetRef="servicetask1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="70.0" y="70.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sampleWorkflow" id="BPMNShape_sampleWorkflow">
        <omgdc:Bounds height="55.0" width="105.0" x="180.0" y="60.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="650.0" y="70.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
        <omgdc:Bounds height="55.0" width="105.0" x="460.0" y="60.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="eventgateway1" id="BPMNShape_eventgateway1">
        <omgdc:Bounds height="40.0" width="40.0" x="310.0" y="67.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="timerintermediatecatchevent1" id="BPMNShape_timerintermediatecatchevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="390.0" y="70.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="105.0" y="87.0"></omgdi:waypoint>
        <omgdi:waypoint x="180.0" y="87.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="565.0" y="87.0"></omgdi:waypoint>
        <omgdi:waypoint x="650.0" y="87.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="285.0" y="87.0"></omgdi:waypoint>
        <omgdi:waypoint x="310.0" y="87.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="350.0" y="87.0"></omgdi:waypoint>
        <omgdi:waypoint x="390.0" y="87.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="425.0" y="87.0"></omgdi:waypoint>
        <omgdi:waypoint x="460.0" y="87.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Tester class
package workflows;

import java.util.HashMap;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;

public class TimerTester {

  public static void main(String[] args) {
    ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
        .setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1").setJdbcUsername("sa")
        .setJdbcPassword("").setJdbcDriver("org.h2.Driver")
        .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

    ProcessEngine processEngine = cfg.buildProcessEngine();

    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deployment = repositoryService.createDeployment()
        .addClasspathResource("workflows/Sampleworkflow.bpmn").deploy();

    ProcessDefinition processDefinition =
        repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId())
        .singleResult();

    RuntimeService runtimeService = processEngine.getRuntimeService();

    ProcessInstance processInstance =
        runtimeService.startProcessInstanceByKey("myProcess", new HashMap<String, Object>());
  }
}

Delegate class
package workflows;

import java.time.LocalTime;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

public class SampleTask implements JavaDelegate {

  @Override
  public void execute(DelegateExecution execution) {
    System.out.println("doing something at " + LocalTime.now());
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("finished something at " + LocalTime.now());
  }
}

Hi Japelleg.

I did not try your tests, but you can have a look on the working examples
TimerBoundaryEvent:

TimerCatchingEvent:

Regards
Martin

Dear Martin,

thanks for your answer. I had a look at the tests but I still have doubts.
First of all I see calls to many methods which are available through org.flowable.engine.test.Deployment which I guess I won’t use in my application.

The class I provided is on purpose not a test class. This is because I want my java application to execute the workflow when asked to.
What I expected is that once I start the process with runtimeService.startProcessInstanceByKey then the workflow starts and proceeds according to the machine time. Is this assumption true?

Then why should the timers be triggered manually?

Thanks a lot,

Jacopo

[Edit]
I added the following lines in my class:

ProcessInstance processInstance =
        runtimeService.startProcessInstanceByKey("myProcess", variables);
    
    ManagementService managementService = cfg.getManagementService();
    TimerJobQuery tjq = managementService.createTimerJobQuery().processInstanceId(processInstance.getId());
    System.out.println("Timer Job Query: " + tjq.count());
    
    //timerintermediatecatchevent1
    List<Job> list = managementService.createTimerJobQuery().list();    
    Job job = list.get(0);
    
    System.out.println(job.getDuedate());
    
    managementService.moveTimerToExecutableJob(job.getId());
    managementService.executeJob(job.getId());

And what I get is:

doing something at 15:52:16.423184600
finished something at 15:52:21.425871500
Timer Job Query: 1
Thu May 16 15:55:00 CEST 2019
doing something else at 15:52:22.118266600
finished something else at 15:52:24.119064800

So it looks like the due date is properly set, but why does the workflow immediately goes to the next task instead of waiting for the due date?

What I need to achieve is essentially to let some time pass between the two tasks.

Hi Jacopo.

Yes, it is.

    managementService.moveTimerToExecutableJob(job.getId());
    managementService.executeJob(job.getId());

Is not needed when you put some Thread.sleep with the check whether the job was executed.
Similar to the following code:

Or you can just wait and check expected result after some time.

Regards
Martin

Hi Martin,

thanks for the reply.

This sounds good to me:

But then I don’t see what is wrong with my example. Please let me know if I should provide more information.

Thanks,
Jacopo

Hey Jacopo,

Correct me if I am wrong, but is your question similar to Catch the event of a blocked instance only after a timeout?

Your example is not OK since you are blocking in your service task? Which is not a wait state and the engine will wait for it’s execution before continuing, this means that the timer task won’t even be created since you are in a running state of the process.

You would have to handle the waiting and proceeding in your own java logic.

Btw, you don’t need to manually execute the job. The reason why it is done like that in the tests is to simulate the events. You can also see that every test that Martin linked has a User task as a wait state. This means that the engine will first do a commit and give back the control of the process execution.

Cheers,
Filip

Hi Filip,

essentially what I need to achieve is:

  1. Introduce a delay between the execution of two tasks: after task 1 wait n seconds before going to task 2
  2. Introduce a timeout for the task execution: if the tasks takes longer than n seconds that should be skipped.

Thanks,
Jacopo

Hey Jacopo,

  1. If you want a business delay then you should use the Timer Intermediate Event. With it you can introduce a delay such that after task 1 is done you need to wait before going to task 2.
  2. As I said in my previous comment, you can’t do this with modeling. The reason for that is that the continuation of the process is blocked by that task so the engine is waiting for the task to finish before continuing. I would advise you to do those things inside your own service.

Cheers,
Filip

Hi Filip,

ok then let’s focus on the Timer Intermediate event. I created the following workflow:


where the to service tasks simply print out the current time, the timer intermediate event has the time duration set PT10S.

My expectation would be to see the current time and then current time + 10s. Is it sensible?

What I get is the following, so only one of the two tasks is executed and I see it immediately.

Found process definition : My process
2019-06-11T12:54:02.548356600Z

Could you please tell we what am I doing wrong here and how to get the desired behaviour?
Thanks a lot in advanvce for your help! :pray:

I attach the complete code for reference
WorkflowExecutor.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;

public class WorkflowExecutor {

  private static String filename = "src/main/resources/diagrams/DelayDiagram.bpmn";

  public static void main(String[] args) throws FileNotFoundException {

    ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
        .setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1").setJdbcUsername("sa")
        .setJdbcPassword("").setJdbcDriver("org.h2.Driver")
        .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

    ProcessEngine processEngine = cfg.buildProcessEngine();

    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deployment = repositoryService.createDeployment()
        .addInputStream("myProcess.bpmn20.xml", new FileInputStream(filename)).deploy();
    // .addClasspathResource("SimpleProcess.bpmn").deploy();

    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
        .deploymentId(deployment.getId()).singleResult();
    System.out.println("Found process definition : " + processDefinition.getName());

    // Start process
    RuntimeService runtimeService = processEngine.getRuntimeService();
    Map<String, Object> variables = new HashMap<String, Object>();
    ProcessInstance processInstance =
        runtimeService.startProcessInstanceByKey("myProcess", variables);
  }
}

DelayTask.java

import java.time.Instant;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;

public class DelayTask implements JavaDelegate {
  @Override
  public void execute(DelegateExecution execution) throws Exception {
    System.out.println(Instant.now());
  }
}

DelayDiagram.bpmn

<?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:xsd="http://www.w3.org/2001/XMLSchema" 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="myProcess" name="My process" isExecutable="true">
    <serviceTask id="servicetask1" name="Task1" activiti:class="apackage.DelayTask"></serviceTask>
    <serviceTask id="servicetask2" name="Task2" activiti:class="apackage.DelayTask"></serviceTask>
    <intermediateCatchEvent id="timerintermediatecatchevent1" name="TimerCatchEvent">
      <timerEventDefinition>
        <timeDuration>PT10S</timeDuration>
      </timerEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="flow1" sourceRef="servicetask1" targetRef="timerintermediatecatchevent1"></sequenceFlow>
    <sequenceFlow id="flow2" sourceRef="timerintermediatecatchevent1" targetRef="servicetask2"></sequenceFlow>
    <startEvent id="startevent1" name="Start"></startEvent>
    <sequenceFlow id="flow3" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="servicetask2" targetRef="endevent1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
        <omgdc:Bounds height="55.0" width="105.0" x="104.0" y="20.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="servicetask2" id="BPMNShape_servicetask2">
        <omgdc:Bounds height="55.0" width="105.0" x="320.0" y="20.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="timerintermediatecatchevent1" id="BPMNShape_timerintermediatecatchevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="240.0" y="30.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="24.0" y="30.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="460.0" y="30.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="209.0" y="47.0"></omgdi:waypoint>
        <omgdi:waypoint x="240.0" y="47.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="275.0" y="47.0"></omgdi:waypoint>
        <omgdi:waypoint x="320.0" y="47.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="59.0" y="47.0"></omgdi:waypoint>
        <omgdi:waypoint x="104.0" y="47.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="425.0" y="47.0"></omgdi:waypoint>
        <omgdi:waypoint x="460.0" y="47.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

It looks like you don’t have the async executor running. Set the asyncExecutorActivate flag to true.