Wednesday, February 18, 2009

Creating "Timer" tasks in JBoss JBPM using JPDL

NOTE: This example has not been updated to work with jBPM versions 3.2 or greater.

Use-Case
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'/>
</start-state>

<task-node name='NODE1'>
<task name='task1'/>
<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' />
</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 name='_sys_redoNode' to='NODE1'/>
</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' />
<transition name='done' to='END'/>
</state>

<end-state name="END" />
</process-definition>
...and here is the UpdateDueDateAH? class:
/*
* UpdateDueDateAH.java
*
*
*/

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;

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 timers = schedulerSession.findTimersByName(timerName, token);
for(Object o : timers) {
Timer timer = (Timer)o;
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(Object o : tmi.getTaskInstances()) {
TaskInstance task = (TaskInstance)o;
//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(java.lang.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;
}
}

Softwares used : JBoss JBPM JPDL 3.2.2, XML

Print this post

0 comments: