NOTE: This example has not been updated to work with jBPM versions 3.2 or greater. See below the example for 3.2.1.
Suppose you receive an order on 2006/10/05 and some related task is created on 10/7; the task needs to be done within 5 days of recieving the order. How do we do that?
jBPM's definition language, JPDL, does not provide a packaged mechanism for dynamically setting Due Dates on Timers and Tasks based on arbitrary data. However, it is not too difficult to work up a solution that brings us the flexibility we need. Here is an example that takes advantage of an ActionHandler to set these due dates dynamically based on a process instance variable:
<?xml version="1.0" encoding="UTF-8"?> <process-definition name="Due date Test"> <start-state name='START' > <transition name='done' to='NODE1'></transition> </start-state> <task-node name='NODE1'> <task name='task1'></task> <event type='node-enter' > <create-timer name='myTimeout' duedate='2000 days' > <script>System.out.println("I reset my timer!");</script> </create-timer> <!-- Dynamically set due date of the timer created in the line above. (This ActionHandler can also be used to dynamically set a Task's due date by substituting the <timerName> tag with a <taskName> tag) --> <action name='setThisTimer' class='com.???.UpdateDueDateAH'> <timerName>myTimeout</timerName> <!-- Process instance variable containing a java.util.Date. This value will be used as a base value for calculating the Timer's new due date. If not provided, the current time will be used. --> <baseTimeVar>testDate</baseTimeVar> <!-- The 'baseTimeVar' Date can be modified by optionally adding a period of time. The value is a valid jBPM Duration styled string --> <addDuration>2 minutes</addDuration> </action> </event> <event type='node-leave' > <!-- To mimic the 'node context' of the short hand timer syntax <timer>, we would need to ensure that our timer is cancelled on node exit. --> <cancel-timer name='timeout' ></cancel-timer> </event> <event type='task-create'> <!-- Use UpdateDueDateAH here to dynamically set a task's due date --> <action name='setDueDate' class='com.???.UpdateDueDateAH'> <taskName>task1</taskName> <baseTimeVar>testDate</baseTimeVar> <addDuration>5 minutes</addDuration> </action> </event> <transition name='done' to='NODE2'></transition> <transition name='_sys_redoNode' to='NODE1'></transition> </task-node> <state name='NODE2'> <!-- Timers with this syntax are created on node-enter and cancelled on node-leave. If timer creation is needed from within an event element or the timer should continue after the node has exited, then use <create-timer> instead --> <timer name='thisNodeOnlyTimeout' duedate='5 minutes' transition='done' ></timer> <transition name='done' to='END'></transition> </state> <end-state name="END" ></end-state> </process-definition>
...and here is the UpdateDueDateAH class:
/* * UpdateDueDateAH.java * * Created on September 11, 2006, 9:20 AM */ package com.???; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; import org.jbpm.taskmgmt.exe.*; import org.jbpm.calendar.BusinessCalendar; import org.jbpm.calendar.Duration; import org.jbpm.svc.Services; import org.jbpm.scheduler.SchedulerService; import org.jbpm.db.SchedulerSession; import org.jbpm.scheduler.exe.Timer; import java.io.*; import java.util.Calendar; import java.util.Date; import java.util.List; /** * @author brittm */ public class UpdateDueDateAH implements ActionHandler{ //-- variables set by process def action parameter elements -- private String baseTimeVar = null; private String addDuration = null; //a jbpm Duration styled string private String timerName = null; //either this OR taskName should be set private String taskName = null; //either this OR timerName should be set private ExecutionContext ec = null; public Log log = LogFactory.getLog(this.getClass()); public UpdateDueDateAH() { } /*Execute the action handler*/ public void execute(ExecutionContext executionContext){ ec = executionContext; try{ Token token = ec.getToken(); Date newDueDate = null; if(timerName != null) { SchedulerSession schedulerSession = ec.getJbpmContext().getSchedulerSession(); //apparently throws an exception if no timer is found... List<Timer> timers = (List<Timer>)schedulerSession.findTimersByName(timerName, token); for(Timer timer : timers) { try{ newDueDate = this.calculateDueDate(); timer.setDueDate(newDueDate); }catch(Exception e) { throw new Exception("Timer '" + timer.getName() + "' due date was not updated to " + newDueDate + "': " + e); } schedulerSession.saveTimer(timer); log.info("Timer '" + timer.getName() + "' due date updated to " + timer.getDueDate()); } }else if(taskName != null) { TaskMgmtInstance tmi = ec.getTaskMgmtInstance(); for(TaskInstance task : (Collection<TaskInstance>)tmi.getTaskInstances()) { //if wildcard is specified, update all tasks... if(taskName.equals("*")) { try{ newDueDate = this.calculateDueDate(); task.setDueDate(newDueDate); }catch(Exception e) { throw new Exception("Task '" + task.getName() + "' due date was not updated to " + newDueDate + "': " + e); } log.info("Task '" + task.getName() + "' due date updated to " + task.getDueDate()); }else{ if(taskName.equals(task.getName())) { try{ newDueDate = this.calculateDueDate(); task.setDueDate(newDueDate); }catch(Exception e) { throw new Exception("Task '" + task.getName() + "' due date was not updated to '" + newDueDate + "': " + e); } log.info("Task '" + task.getName() + "' due date updated to "+task.getDueDate()); } } } } }catch(Exception e){ log.error(e.getMessage(), e); } } private Date calculateDueDate() throws Exception { Date dueDate = null; Calendar cal = Calendar.getInstance(); //if a baseTime is specified, we'll use that; otherwise, we'll just use the current time if(baseTimeVar != null && baseTimeVar.length() > 0) { Object baseTime = ec.getContextInstance().getVariable(baseTimeVar); if(baseTime != null) { if(baseTime instanceof String) { throw new Exception("Could not calculate Due Date: the variable '" + baseTimeVar + "' should be a type of java.util.Date."); }else if(baseTime instanceof Date) { cal.setTime((Date)baseTime); }else{ throw new Exception("Could not calculate Due Date: baseTimeVar '" + baseTimeVar + "' was specified but no valid date/time data was found."); } }else{ throw new Exception("Could not calculate Due Date: baseTimeVar was specified but no data was found."); } } if(addDuration != null) { BusinessCalendar businessCalendar = new BusinessCalendar(); Duration duration = new Duration(addDuration); dueDate = businessCalendar.add( cal.getTime(), duration ); } return dueDate; } public void setTimerName(String timerName) { if(timerName != null && timerName.trim().length() > 0) this.timerName = timerName; } public void setTaskName(String taskName) { if(taskName != null && taskName.trim().length() > 0) this.taskName = taskName; } public void setBaseTimeVar(String baseTimeVar) { this.baseTimeVar = baseTimeVar; } /* * Takes a jbpm Duration styled string */ public void setAddDuration(String addDuration) { this.addDuration = addDuration; } }
-
An example for jbpm v.3.2.1.
This is a snippet from a process definition:
... <state name="state1"> <event type="timer-create"> <action name="timerCreated" class="org.sample.jbpm.handler.ChangeDueDateActionHandler"> <timerName>varTimer</timerName> <delay>#{newDelay}</delay> </action> </event> <timer duedate="10 years" name="varTimer" transition="fired"> <action name="message" class="..."></action> </timer> <transition to="node1" name="fired"></transition> <transition to="end-state1" name="notFired"></transition> </state> ...
Below is a handler which changes the due date of a timer.
package org.sample.jbpm.handler; import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.calendar.BusinessCalendar; import org.jbpm.calendar.Duration; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.job.Timer; import org.jbpm.scheduler.SchedulerService; import org.jbpm.svc.Services; public class ChangeDueDateActionHandler implements ActionHandler { private static final long serialVersionUID = 1705L; private static final Log log = LogFactory.getLog(ChangeDueDateActionHandler.class); static BusinessCalendar businessCalendar = new BusinessCalendar(); String timerName; String delay; public void execute(ExecutionContext executionContext) throws Exception { try { Timer timer = executionContext.getTimer(); if (timer != null && timerName.equals(timer.getName())) { String dueDate = delay; // get the value of a variable if (delay.startsWith("#{")) { dueDate = (String) executionContext.getVariable(delay.substring(2, delay.length() -1)); } log.info("Changing the timer: " + timer + " to fire in " + dueDate); SchedulerService schedulerService = (SchedulerService) Services.getCurrentService(Services.SERVICENAME_SCHEDULER); // delete the existing timer schedulerService.deleteTimersByName(timer.getName(), executionContext.getToken()); // create a new one with the right delay Duration duration = new Duration(dueDate); Date dueDateDate = businessCalendar.add(new Date(), duration); timer.setDueDate(dueDateDate); schedulerService.createTimer(timer); } else { log.debug("Doesn't match: " + timer); } } catch (Exception ex) { ex.printStackTrace(); } } public String getDelay() { return delay; } public void setDelay(String delay) { this.delay = delay; } public String getTimerName() { return timerName; } public void setTimerName(String timerName) { this.timerName = timerName; } }
Comments