4 Replies Latest reply on Sep 12, 2008 2:28 PM by kukeltje

    Task timer and TaskInstance.dueDate field

    p4elka

      Hello

      JBPM allow to use timers with task instance, where timer has it's own due date not related to task due date. Timer's due date cannot be changed after timer is created (at least using API provided i.e SchedulerService or JobSession).
      Sometimes it is required to change due date for timer associated with task instance after task instance is created, and I haven't found standard way of doing this in JBPM.

      Here is how I think this could be implemented and I would be glad to get your opinion on this.
      I believe that link between task instance due date and task timer due date is missing in current JBPM implementation.
      Task due date should be ignored when timer is associated with task, and task instance should be initialized with timer due date.
      Upon change of task due date, we should look whether task has timer associated and if yes, timer instance should be found and updated with new due date value.
      Another approach could be not to change dates in existing timers but cancel timer and create new one using existing timer declaration.

      What do you think ?

      Thanks a lot !

        • 1. Re: Task timer and TaskInstance.dueDate field
          lblaauw

          Hey,

          Allthough it has been a while since the post below was posted, I am
          facing a somewhat simular dilemma...

          Basically I also have a timer asociated on a task node. Seems to be
          working just fine, but when i get a tasklist the task duedate is not set
          to the timer and I agree with the poster of the original message that
          the task duedate really should be the Timer duedate wich was asociated
          with this task to begin with. Obviously I could 'kludge' some db calls
          to fill my own task objects, go through the jobs of the processInstance
          and add the timers to my own custom task objects... Dont really want
          to do that however since I feel the aproach below makes sense.

          Any of the jBPM gurus like to comment ?

          Greetings and thanks upfront for your time,

          Leo de Blaauw


          "p4elka" wrote:
          Hello

          JBPM allow to use timers with task instance, where timer has it's own due date not related to task due date. Timer's due date cannot be changed after timer is created (at least using API provided i.e SchedulerService or JobSession).
          Sometimes it is required to change due date for timer associated with task instance after task instance is created, and I haven't found standard way of doing this in JBPM.

          Here is how I think this could be implemented and I would be glad to get your opinion on this.
          I believe that link between task instance due date and task timer due date is missing in current JBPM implementation.
          Task due date should be ignored when timer is associated with task, and task instance should be initialized with timer due date.
          Upon change of task due date, we should look whether task has timer associated and if yes, timer instance should be found and updated with new due date value.
          Another approach could be not to change dates in existing timers but cancel timer and create new one using existing timer declaration.

          What do you think ?

          Thanks a lot !


          • 2. Re: Task timer and TaskInstance.dueDate field
            kukeltje

            task due date and timer due date are 2 different things, even when used on a task. So therefor the task duedate is empty even though there can be a timer with a duedate. We cannot automatically fill it with the duedate of a timer since that can be a very different thing....

            • 3. Re: Task timer and TaskInstance.dueDate field
              lblaauw

              True,

              I understand the current implementation but in my view functionally they
              shouldnt be different things. I feel you need to be able to have them to
              be the same thing i.e. when a task has a deadline(duedate) I would like
              to have the process instance be able to do two optional things:

              1. escalate via a transition to another step in the process whilst deleting
              the original task

              2. send an escalation message as a sort of notice therefore taking a transition out of the node but leaving the task sit where it is to just notify
              someone the task is now overdue.

              The above scenario seems pretty common in commercial tooling and I
              am kinda strugling to fit this into jBPM I guess..

              Greetz
              Leo

              "kukeltje" wrote:
              task due date and timer due date are 2 different things, even when used on a task. So therefor the task duedate is empty even though there can be a timer with a duedate. We cannot automatically fill it with the duedate of a timer since that can be a very different thing....


              • 4. Re: Task timer and TaskInstance.dueDate field
                kukeltje

                1: Sure, no problem... just model it that way

                2: leaving the node but keeping the task and 'just' notify? Via Email? That is possible with a timer. Via a new task, leaving the node where to? A new tasknode? And what if the user finishes the task before someone else can react on this task? Do you want to cancel it then?

                Remember that jBPM van have tasks that are not in the flow... you can create and start them when a timer goes off. And e.g. cancel them when a transition is taken on the original node.

                Just to show you (and this is a one time effort by me ;-)) I've made an example of this which is even somewhat configurable.


                These are all possible with jBPM *now* just think out of the box and model these things into your process. Or write some code (actionhandlers etc..) The thing is, escalation, or the way people want it, is so dependant on the case, it cannot to the fullest extend be part of the processlanguage.

                The solution for the initial problem 'reported' is to give both the timer and the duedate (which is just for informing the original owner) the same date. but what need is a date for a reminder if when the duedate is reached

                package net.vankuijk.jbpm;
                
                import java.util.ArrayList;
                import java.util.Collection;
                import java.util.List;
                import java.util.Map;
                
                import org.apache.commons.logging.Log;
                import org.apache.commons.logging.LogFactory;
                import org.jbpm.db.AbstractDbTestCase;
                import org.jbpm.graph.def.ActionHandler;
                import org.jbpm.graph.def.Node;
                import org.jbpm.graph.def.ProcessDefinition;
                import org.jbpm.graph.exe.ExecutionContext;
                import org.jbpm.graph.exe.ProcessInstance;
                import org.jbpm.graph.exe.Token;
                import org.jbpm.graph.node.TaskNode;
                import org.jbpm.taskmgmt.def.Task;
                import org.jbpm.taskmgmt.exe.TaskInstance;
                import org.jbpm.taskmgmt.exe.TaskMgmtInstance;
                
                public class EscalationExampleTest extends AbstractDbTestCase {
                
                 ArrayList ownerIDs;
                 ArrayList escalatorIDs;
                
                 List ownerTasks;
                 List escalatorTasks;
                
                 public void setUp() throws Exception {
                 super.setUp();
                
                 ownerIDs = new ArrayList();
                 ownerIDs.add("owner");
                
                 escalatorIDs = new ArrayList();
                 escalatorIDs.add("escalator");
                
                 }
                
                 static String processDefinition = "<process-definition name='testEscalation'>"
                 + " <swimlane name='ownerSwimlane'>"
                 + " <assignment pooled-actors='owner' />"
                 + " </swimlane>"
                
                 + " <event type='node-leave'>"
                 + " <action class='net.vankuijk.jbpm.EscalationExampleTest$CancelEscalationTask' />"
                 + " </event>"
                
                 + " <action name='createEscalationTask' class='net.vankuijk.jbpm.EscalationExampleTest$CreateEscalationTask'>"
                 + " <escalationTaskNodeName>task escalated</escalationTaskNodeName>"
                 + " </action>"
                
                 + " <swimlane name='escalationSwimlane'>"
                 + " <assignment pooled-actors='escalator' />"
                 + " </swimlane>"
                
                 + " <start-state>"
                 + " <transition name='to_task' to='task' />"
                 + " </start-state>"
                
                 + " <task-node name='task'>"
                 + " <task name='task 0' swimlane='ownerSwimlane'>"
                 + " <timer duedate='5 seconds'>"
                 + " <action ref-name='createEscalationTask' />"
                 + " </timer>"
                 + " </task>"
                 + " <transition name='to_end' to='end' />"
                 + " </task-node>"
                
                 + " <task-node name='task escalated'>"
                 + " <task name='escalated task 0' swimlane='escalationSwimlane' signalling='false'/>"
                 + " </task-node>"
                
                 + " <end-state name='end' />"
                
                 + "</process-definition>";
                
                 void deployProcessDefinition(String xml) {
                 ProcessDefinition processDefinition = ProcessDefinition
                 .parseXmlString(xml);
                 jbpmContext.deployProcessDefinition(processDefinition);
                 newTransaction();
                 }
                
                 public void testNormalFlow() {
                
                 deployProcessDefinition(processDefinition);
                
                 ProcessInstance processInstance = jbpmContext
                 .newProcessInstanceForUpdate("testEscalation");
                 processInstance.signal();
                
                 processInstance = saveAndReload(processInstance);
                
                 ownerTasks = jbpmContext.getGroupTaskList(ownerIDs);
                 TaskInstance ownerTask = (TaskInstance) ownerTasks.get(0);
                 ownerTask.end();
                
                 assertEquals(0, processInstance.getTaskMgmtInstance()
                 .getUnfinishedTasks(processInstance.getRootToken()).size());
                
                 assertEquals(true, processInstance.hasEnded());
                
                 }
                
                 public void testEndingNormalTaskAfterEscalationCancelsEscalation() {
                 deployProcessDefinition(processDefinition);
                
                 ProcessInstance processInstance = jbpmContext
                 .newProcessInstanceForUpdate("testEscalation");
                 long piId = processInstance.getId();
                 processInstance.signal();
                
                 assertEquals(1, processInstance.getTaskMgmtInstance()
                 .getTaskInstances().size());
                
                 processInstance = saveAndReload(processInstance);
                
                 ownerTasks = jbpmContext.getGroupTaskList(ownerIDs);
                 assertEquals(1, ownerTasks.size());
                
                 escalatorTasks = jbpmContext.getGroupTaskList(escalatorIDs);
                 assertEquals(0, escalatorTasks.size());
                
                 processJobs(10000);
                
                 processInstance = jbpmContext.getProcessInstance(piId);
                
                 assertEquals(2, processInstance.getTaskMgmtInstance()
                 .getTaskInstances().size());
                
                 escalatorTasks = jbpmContext.getGroupTaskList(escalatorIDs);
                 assertEquals(1, escalatorTasks.size());
                
                 ownerTasks = jbpmContext.getGroupTaskList(ownerIDs);
                 TaskInstance ownerTask = (TaskInstance) ownerTasks.iterator().next();
                 ownerTask.end();
                
                 // processInstance = saveAndReload(processInstance);
                
                 // Escalation task should be cancelled
                 escalatorTasks = jbpmContext.getGroupTaskList(escalatorIDs);
                 assertEquals(0, escalatorTasks.size());
                
                 }
                
                 public void testEscalationEnded_NormalTaskStays() {
                 deployProcessDefinition(processDefinition);
                
                 ProcessInstance processInstance = jbpmContext
                 .newProcessInstanceForUpdate("testEscalation");
                 long piId = processInstance.getId();
                 processInstance.signal();
                
                 assertEquals(1, processInstance.getTaskMgmtInstance()
                 .getTaskInstances().size());
                
                 processInstance = saveAndReload(processInstance);
                
                 ownerTasks = jbpmContext.getGroupTaskList(ownerIDs);
                 assertEquals(1, ownerTasks.size());
                
                 escalatorTasks = jbpmContext.getGroupTaskList(escalatorIDs);
                 assertEquals(0, escalatorTasks.size());
                
                 processJobs(10000);
                
                 processInstance = jbpmContext.getProcessInstance(piId);
                
                 assertEquals(2, processInstance.getTaskMgmtInstance()
                 .getTaskInstances().size());
                
                 escalatorTasks = jbpmContext.getGroupTaskList(escalatorIDs);
                 assertEquals(1, escalatorTasks.size());
                
                 TaskInstance escalatorTask = (TaskInstance) escalatorTasks.iterator()
                 .next();
                 assertEquals("escalated task 0", escalatorTask.getName());
                 escalatorTask.end();
                
                 processInstance = saveAndReload(processInstance);
                
                 // owner task should still be there... is it?
                 ownerTasks = jbpmContext.getGroupTaskList(ownerIDs);
                 assertEquals(1, ownerTasks.size());
                
                 escalatorTasks = jbpmContext.getGroupTaskList(escalatorIDs);
                 assertEquals(0, escalatorTasks.size());
                
                 }
                
                 public static class CreateEscalationTask implements ActionHandler {
                 private static final long serialVersionUID = 1L;
                
                 public String escalationTaskNodeName;
                
                 public void execute(ExecutionContext executionContext) throws Exception {
                
                
                 if ("".equals(escalationTaskNodeName) || escalationTaskNodeName == null) {
                 log.warn("escalationTaskNodeName not specified");
                 return;
                 }
                
                 Token token = executionContext.getToken();
                 TaskMgmtInstance tmi = executionContext.getTaskMgmtInstance();
                
                 Node node = executionContext.getProcessDefinition().getNode(
                 escalationTaskNodeName);
                 if (node != null) {
                 TaskNode escalatedTaskNode = (TaskNode) node;
                
                 // assuming 1 task in node... otherwise also use e.g. a param
                 // like escalationTaskNodeName. Errorhandling for this not done yet
                 Task escalateTask = (Task) escalatedTaskNode.getTasksMap()
                 .values().iterator().next();
                
                 Token childToken = new Token(token, token.getFullName()
                 + "_child_" + token.getChildren().size());
                
                 childToken.setNode(escalatedTaskNode);
                
                 tmi.createTaskInstance(escalateTask, childToken);
                 } else {
                 log.warn("escalationTaskNode with name " + escalationTaskNodeName + " not found.");
                 }
                
                 }
                 }
                
                 public static class CancelEscalationTask implements ActionHandler {
                 private static final long serialVersionUID = 1L;
                
                 public void execute(ExecutionContext executionContext) throws Exception {
                
                 // Assuming max one active childToken and max one unfinished task in
                 // this childtoken
                 Map activeChildren = executionContext.getToken()
                 .getActiveChildren();
                 if (!activeChildren.isEmpty()) {
                 Token childToken = (Token) activeChildren.values().iterator()
                 .next();
                 Collection unfinishedTasks = executionContext
                 .getTaskMgmtInstance().getUnfinishedTasks(childToken);
                 if (!unfinishedTasks.isEmpty()) {
                 ((TaskInstance) unfinishedTasks.iterator().next()).cancel();
                 }
                 }
                
                 }
                 }
                
                 private static final Log log = LogFactory.getLog(EscalationExampleTest.class);
                
                }
                
                


                Maybe someone wants to make a wiki page out of this....