Dynamic start timer value

Hi,
I’m trying to dynamically set the start timer so it is configurable but have one issue at the moment.

I am setting the config in our application.yaml (we have a custom CycleBusinessCalendar to do the timezone)

process:
  startTime: 0 0 19 ? * * TZ Europe/Paris

A spring bean to access the config

@Component
@Getter
public class ProcessConfiguration {
  private String processStartTime;

  public ProcessConfiguration(
      @Value("${process.startTime:0 0 19 ? * * TZ Europe/Paris}") String processStartTime) {
    this.processStartTime = processStartTime;
  }
}

And the start timer defined as

    <bpmn:startEvent id="Process_Start_Timer" name="start Process">
      <bpmn:outgoing>SequenceFlow_0loyj3j</bpmn:outgoing>
      <bpmn:timerEventDefinition>
        <bpmn:timeCycle xsi:type="bpmn:tFormalExpression">${processConfiguration.getProcessStartTime()}</bpmn:timeCycle>
      </bpmn:timerEventDefinition>
    </bpmn:startEvent>

This is a SpringBoot application, and the issue I am having is that on first deploy I check in the ACT_RU_JOB table and the due date and repeat columns are correct with whats on the config. If I then stop the application and change the config these don’t get updated to the new values. Is this the correct place to check or is it not possible to change it in this way?

Thanks,

Matt

1 Like

Fun use case!

Take a look at this:

and

You can update the runtime with their code snippet. And if you update the previous versions as stated in the expression.

edit: updated with cleaner version as there was the runtime snippet already provided

Thanks for the reply. So I have also come up with this solution which works, but was wondering if it could be improved.

So the timer configuration is now

    <bpmn:startEvent id="Process_Start_Timer" name="start Process">
      <bpmn:outgoing>SequenceFlow_0loyj3j</bpmn:outgoing>
      <bpmn:timerEventDefinition>
        <bpmn:timeCycle xsi:type="bpmn:tFormalExpression">${process.startTime}</bpmn:timeCycle>
      </bpmn:timerEventDefinition>
    </bpmn:startEvent>

The spring config bean

@Component
@Getter
public class ProcessConfiguration {
  private static Map<String, String> timerDefinitions = new HashMap<>();

  public ProcessConfiguration(
      @Value("${process.startTime:0 0 19 ? * * TZ Europe/Paris}") String processStartTime) {
    timerDefinitions.put("process.startTime", processStartTime);
  }

  public String getTimerDefinition(String startTimer) {
    return timerDefinitions.get(startTimer);
  }
}

and our custom CycleBusinessCalendar

@AllArgsConstructor
public class TZCycleBusinessCalendar extends CycleBusinessCalendar {

  public static final String NAME = "cycle";

  private ProcessConfiguration processConfiguration;

  @Override
  public Date resolveDuedate(String duedateDescription) {
    return resolveDuedate(duedateDescription, null);
  }

  @Override
  public Date resolveDuedate(String duedateDescription, Date startDate) {
    try {
      // replace description from config  if needed
      duedateDescription = Optional.ofNullable(processConfiguration.getTimerDefinition(duedateDescription))
          .orElse(duedateDescription);

      if (duedateDescription.startsWith("R")) {
        return super.resolveDuedate(duedateDescription, startDate);
      }
      String[] cronExpression = duedateDescription.split(" TZ ", 2);
      if (cronExpression.length == 2) {
        TimeZone tz = TimeZone.getTimeZone(cronExpression[1].trim());
        CronTrigger ct = new CronTrigger(cronExpression[0], tz);
        SimpleTriggerContext triggerContext = new SimpleTriggerContext();
        triggerContext.update(null, null, ClockUtil.getCurrentTime());
        return ct.nextExecutionTime(triggerContext);
      } else {
        return super.resolveDuedate(duedateDescription, startDate);
      }
    } catch (Exception e) {
    }
  }

}

and on application startup we call the recalculateJobDueDate only on start timers

@Component
@AllArgsConstructor
public class StartTimerRecalculate implements ApplicationListener<ProcessApplicationStartedEvent> {
  private ManagementService managementService;

  @Override
  public void onApplicationEvent(ProcessApplicationStartedEvent event) {
    managementService.createJobQuery()
        .timers()
        .list()
        .stream()
        .map(job -> (TimerEntity)job)
        .filter(timerEntity -> "timer-start-event".equals(timerEntity.getJobHandlerType()))
        .forEach(timerEntity -> managementService.recalculateJobDuedate(timerEntity.getId(), true));
  }
}

Is there a better point to do the recalculate?

Off the top of my head you may want to disable the job executor by default so on startup the executor is not running, then update your timers, then manually turn the executor on.

This way you don’t have the chance of timers being executed by the job executor while you are also updating each of the timers

Cheers, will look into that

Ive had a look but I’m not entirely sure on how to activate the JobExecutor.
To disable it i’ve put the following in the application.yaml

camunda:
  bpm:
    job-execution.enabled: false

But if you could point me in the right direction for enabling it that would be great.
Cheers

So, just managed to get it working with this

((ProcessEngineConfigurationImpl) processEngineConfiguration).getJobExecutor().start();

Is this appropriate?

That looks good.

Thanks for the help

Would be great if you wrote up a little blog post or share a github repo that others can refer back to. You use case is very interesting and sure there are others that have dealt with similar issues.

Custom Business calendars was a hot topic on the forums for a while, and the concept of resetting your timers on engine startup is a great nuance that is not obvious in the engines usage.

1 Like

Will try and do a little repo to show what I’ve done when I get a chance :slight_smile:

1 Like

It could even be something as simple as just add another post into this threat with a recap of the changes and your findings. Nothing heavy is needed.

What exactly is the purpose of being able to use an expression for the Timer Definition if the expression isn’t re-evaluated each time the job is scheduled?

The purpose was to allow for the start time to be modified without needed to redeploy the diagram. In the code above each time the next start job is scheduled it will use the configured start time. Currently in the above example it comes from the properties file so would require a restart, but this could easily come from another source like db which means you would have greater flexibility over the schedule of the process without the need for redeployment.
Again currently if the existing schedule is wrong and needs changing then a restart is needed, but suspect something could be done to do the recalculation without that need.
Hope that helps

1 Like

Hi Matt, thanks for the additional information. I think I understand what this post is meant to answer. My question was meant to be asked outside of the context of this provided solution in this thread. As in, what is the purpose of being able to use an expression for the Timer Definition if that expression, if OOTB Camunda (without this solution) only evaluates that expression once every time the diagram is deployed?