Version 9

    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;
         }
    }