[jbpm-commits] JBoss JBPM SVN: r6135 - in jbpm4/trunk/modules: bpmn/src/main/java/org/jbpm/bpmn/flownodes and 15 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Tue Jan 26 13:44:05 EST 2010


Author: jbarrez
Date: 2010-01-26 13:44:04 -0500 (Tue, 26 Jan 2010)
New Revision: 6135

Added:
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEventActivity.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cmd/DeleteJobCmd.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/PeriodicStartProcessTimer.java
   jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/util/
   jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/util/DateUtils.java
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/TimerStartEventTest.java
Removed:
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEvent.java
Modified:
   jbpm4/trunk/modules/api/src/main/java/org/jbpm/api/ManagementService.java
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/StartEventBinding.java
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java
   jbpm4/trunk/modules/devguide/src/main/docbook/en/modules/ch03-Bpmn2.xml
   jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/TaskActivity.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/CronExpression.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/Duration.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/TimerImpl.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/jobexecutor/JobExecutorTimerSession.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/svc/ManagementServiceImpl.java
   jbpm4/trunk/modules/pvm/src/main/resources/jbpm.execution.hbm.xml
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/intermediatecatch/IntermediateCatchTimerEventTest.java
Log:
JBPM-2722: impl of timer start event + basic test coverage

Modified: jbpm4/trunk/modules/api/src/main/java/org/jbpm/api/ManagementService.java
===================================================================
--- jbpm4/trunk/modules/api/src/main/java/org/jbpm/api/ManagementService.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/api/src/main/java/org/jbpm/api/ManagementService.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -39,4 +39,10 @@
 
   /** search for jobs */
   JobQuery createJobQuery();
+  
+  /**
+   * Deletes the job with the given id.
+   * @return True if the deletion was succesful.
+   */
+  boolean deleteJob(long jobId);
 }

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/StartEventBinding.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/StartEventBinding.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/StartEventBinding.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -23,7 +23,7 @@
 
 import org.jbpm.bpmn.model.BpmnProcessDefinition;
 import org.jbpm.bpmn.parser.BpmnParser;
-import org.jbpm.pvm.internal.job.JobImpl;
+import org.jbpm.pvm.internal.job.PeriodicStartProcessTimer;
 import org.jbpm.pvm.internal.model.ActivityImpl;
 import org.jbpm.pvm.internal.model.TimerDefinitionImpl;
 import org.jbpm.pvm.internal.util.XmlUtil;
@@ -50,10 +50,10 @@
       parse.addProblem("multiple start events not yet supported", element);
     }
     
-    String id = XmlUtil.attribute(element, "id");
+    String id = XmlUtil.attribute(element, "id", true, parse);
     Element eventDefinition = XmlUtil.element(element);
     if (eventDefinition != null && "timerEventDefinition".equals(eventDefinition.getNodeName())) {
-      return createTimerStartEvent((BpmnParser) parser, parse, id);
+      return createTimerStartEvent(processDefinition, eventDefinition, id, (BpmnParser) parser, parse);
     } else if (eventDefinition != null){
       parse.addProblem("Invalid eventDefinition type : " + eventDefinition.getNodeName());
     }
@@ -61,13 +61,28 @@
     return new NoneStartEventActivity(); // default
   }
   
-  protected TimerStartEvent createTimerStartEvent(BpmnParser parser, Parse parse, String eventId) {
-    TimerStartEvent timerStartEvent = new TimerStartEvent();
+  protected TimerStartEventActivity createTimerStartEvent(BpmnProcessDefinition processDefinition, 
+          Element timerEventDefinition, String eventId, BpmnParser parser, Parse parse) {
     
-    TimerDefinitionImpl timerDefinition = parser.parseTimerEventDefinition(null, parse, eventId);
+    TimerStartEventActivity timerStartEvent = new TimerStartEventActivity();
+    TimerDefinitionImpl timerDefinition = parser.parseTimerEventDefinition(timerEventDefinition, parse, eventId);
     
-    // schedule job    
+    if (timerDefinition == null) { // problem explanation will already be added to parse, no need to do it here
+      return null;
+    }
     
+    PeriodicStartProcessTimer startProcessTimer = new PeriodicStartProcessTimer();
+    startProcessTimer.setProcessDefinitionName(processDefinition.getName());
+    
+    if (timerDefinition.getDueDate() != null) {
+      startProcessTimer.setDuedate(timerDefinition.getDueDate());
+    } else if (timerDefinition.getDueDateDescription() != null) {
+      startProcessTimer.setIntervalExpression(timerDefinition.getDueDateDescription());
+    } else if (timerDefinition.getCronExpression() != null) {
+      startProcessTimer.setIntervalExpression(timerDefinition.getCronExpression());
+    }
+    
+    startProcessTimer.schedule();
     return timerStartEvent;
   }
   

Deleted: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEvent.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEvent.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEvent.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -1,45 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source
- * Copyright 2005, JBoss Inc., and individual contributors as indicated
- * by the @authors tag. See the copyright.txt in the distribution for a
- * full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.jbpm.bpmn.flownodes;
-
-import org.jbpm.api.activity.ActivityExecution;
-import org.jbpm.pvm.internal.model.ExecutionImpl;
-
-
-/**
- * @author Joram Barrez
- */
-public class TimerStartEvent extends BpmnActivity {
-
-  private static final long serialVersionUID = 1L;
-
-  public void execute(ActivityExecution execution) {
-    
-  }
-  
-  public void execute(ExecutionImpl execution) {
-    
-  }
-  
-  
-
-}

Copied: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEventActivity.java (from rev 6124, jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEvent.java)
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEventActivity.java	                        (rev 0)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/TimerStartEventActivity.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.bpmn.flownodes;
+
+
+
+/**
+ * @author Joram Barrez
+ */
+public class TimerStartEventActivity extends NoneStartEventActivity {
+
+  private static final long serialVersionUID = 1L;
+  
+  // At the moment no difference with a none start event. Could change in the future.
+
+}

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -111,7 +111,7 @@
     parse.contextStackPush(processDefinition);
     try {
 
-      String id = XmlUtil.attribute(processElement, "id", false, parse);
+      String id = XmlUtil.attribute(processElement, "id", true, parse);
       String name = XmlUtil.attribute(processElement, "name", false, parse);
       
       if (id != null && !"".equals(id)) {
@@ -335,6 +335,11 @@
   }
   
   /**
+   * Parses a <timerEventDefinition> element:
+   *    * sets dueDate if 'timeDate' is used
+   *    * sets duedateDescription if a duration expression is used
+   *    * set cronExpression if a cron expression is used
+   * 
    * @param timerEventDefinitionElement The XML element that defines the timer definition
    * @param activity The activity on which the timer definition must be created
    * @param eventId The id of the event on which the timer is defined
@@ -346,7 +351,7 @@
     
     if ( (timeDate != null && timeCycle != null)
             || (timeDate == null && timeCycle == null) ) {
-      parse.addProblem("Intermediate catch event '" + eventId +
+      parse.addProblem("timerEventDefinition for event '" + eventId +
               "' requires either a timeDate or a timeCycle definition (but not both)");
       return null;
     }
@@ -363,18 +368,6 @@
     
     return timerDefinition;
   }
-  
-  protected void parseTimeCycle(String catchEventId, Parse parse, Element timeCycle, TimerDefinitionImpl timerDefinition) {
-    String cycleExpression = timeCycle.getTextContent();
-    if (Duration.isValidExpression(cycleExpression)) {
-      timerDefinition.setDueDateDescription(cycleExpression);
-    } else if (CronExpression.isValidExpression(cycleExpression)) {
-      timerDefinition.setCronExpression(cycleExpression);
-    } else {
-      parse.addProblem("couldn't parse timeDate duration '"+ cycleExpression 
-              + "' on intermediate catch timer event " + catchEventId);
-    }
-  }
 
   protected void parseTimeDate(String catchEventId, Parse parse, Element timeDate, TimerDefinitionImpl timerDefinition) {
     String dueDateTime = timeDate.getTextContent();
@@ -391,6 +384,18 @@
               + "' on intermediate catch timer event " + catchEventId, e);
     }
   }
+  
+  protected void parseTimeCycle(String catchEventId, Parse parse, Element timeCycle, TimerDefinitionImpl timerDefinition) {
+    String cycleExpression = timeCycle.getTextContent();
+    if (Duration.isValidExpression(cycleExpression)) {
+      timerDefinition.setDueDateDescription(cycleExpression);
+    } else if (CronExpression.isValidExpression(cycleExpression)) {
+      timerDefinition.setCronExpression(cycleExpression);
+    } else {
+      parse.addProblem("couldn't parse timeDate duration '"+ cycleExpression 
+              + "' on intermediate catch timer event " + catchEventId);
+    }
+  }
 
   public void parseResources(Element documentElement, Parse parse, BpmnProcessDefinition processDefinition) {
 

Modified: jbpm4/trunk/modules/devguide/src/main/docbook/en/modules/ch03-Bpmn2.xml
===================================================================
--- jbpm4/trunk/modules/devguide/src/main/docbook/en/modules/ch03-Bpmn2.xml	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/devguide/src/main/docbook/en/modules/ch03-Bpmn2.xml	2010-01-26 18:44:04 UTC (rev 6135)
@@ -242,8 +242,9 @@
       If a name is defined for the process element, it is be used as <emphasis role="bold">key</emphasis>
       for that process (ie. starting a process can be done by calling executionService.startProcessInstanceByKey("myBusinessProcess").
       If no name is defined, the id will be used as key. So having only an id defined, will allow
-      to start a process instance using that id. Note that for a key the same rules apply as with
-      JPDL: whitespace and non alpha-numeric characters are replace by an underscore.
+      to start a process instance using that id. So basically, name and key are of equivalent 
+      in usage, for example to search process definitions. Note that for a key the same rules apply as with
+      JPDL: whitespace and non alpha-numeric characters are replaced by an underscore.
      </para>
   
   </section>

Modified: jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/TaskActivity.java
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/TaskActivity.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/TaskActivity.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -27,10 +27,10 @@
 import org.jbpm.api.JbpmException;
 import org.jbpm.api.activity.ActivityExecution;
 import org.jbpm.api.model.Transition;
+import org.jbpm.pvm.internal.cal.Duration;
 import org.jbpm.pvm.internal.env.EnvironmentImpl;
 import org.jbpm.pvm.internal.history.HistoryEvent;
 import org.jbpm.pvm.internal.history.events.TaskActivityStart;
-import org.jbpm.pvm.internal.job.TimerImpl;
 import org.jbpm.pvm.internal.model.ActivityImpl;
 import org.jbpm.pvm.internal.model.ExecutionImpl;
 import org.jbpm.pvm.internal.script.ScriptManager;
@@ -85,7 +85,7 @@
     // calculate the due date of the task based on the due date duration
     String dueDateDescription = taskDefinition.getDueDateDescription();
     if (dueDateDescription != null) {
-      task.setDuedate(TimerImpl.calculateDueDate(dueDateDescription));
+      task.setDuedate(Duration.calculateDueDate(dueDateDescription));
     }
 
     // save task so that TaskDbSession.findTaskByExecution works for assign event listeners

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/CronExpression.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/CronExpression.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/CronExpression.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -360,7 +360,7 @@
         
         try {
             new CronExpression(cronExpression);
-        } catch (ParseException pe) {
+        } catch (Exception pe) {
             return false;
         }
         
@@ -746,7 +746,7 @@
         return i;
     }
 
-    public String getCronExpression() {
+    public String getCronExpressionString() {
         return cronExpression;
     }
     
@@ -1502,7 +1502,7 @@
     public Object clone() {
         CronExpression copy = null;
         try {
-            copy = new CronExpression(getCronExpression());
+            copy = new CronExpression(getCronExpressionString());
             copy.setTimeZone(getTimeZone());
         } catch (ParseException ex) { // never happens since the source is valid...
             throw new IncompatibleClassChangeError("Not Cloneable.");

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/Duration.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/Duration.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/Duration.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -22,12 +22,21 @@
 package org.jbpm.pvm.internal.cal;
 
 import java.io.Serializable;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.jbpm.api.JbpmException;
+import org.jbpm.pvm.internal.env.EnvironmentImpl;
+import org.jbpm.pvm.internal.script.ScriptManager;
+import org.jbpm.pvm.internal.util.Clock;
 
 /**
  * represents a time duration.
@@ -59,6 +68,14 @@
   protected int weeks;
   protected int months;
   protected int years;
+  
+  private final static String dateFormat = "yyyy-MM-dd HH:mm:ss";
+  
+  private static final Pattern dateDurationPattern = Pattern.compile("\\s*(#\\{.+\\})\\s*"
+          + "(?:(\\+|-)\\s*(\\d+\\s+(?:business\\s+)?\\w+))?\\s*");
+       
+  private static final Pattern durationPattern = Pattern.compile("\\s*(\\d+\\s+(?:business\\s+)?"
+          + "\\w+)\\s*");
 
   /** constructor for persistence.  note that this type is to be immutable. */
   protected Duration() {
@@ -91,7 +108,56 @@
     }
     return true;
   }
+  
+  public static Date calculateDueDate(String durationExpression) {
+    Date dueDate;
+    // is due date description in date_expression +|- fixed_duration format?
+    Matcher dateDurationMatcher = dateDurationPattern.matcher(durationExpression);
+    if (dateDurationMatcher.matches()) {
+      // evaluate date expression
+      String dateExpression = dateDurationMatcher.group(1);
+      Object result = ScriptManager.getScriptManager().evaluateExpression(dateExpression, null);
+      // convert result to Date
+      if (result instanceof Date) {
+        dueDate = (Date) result;
+      } else if (result instanceof Calendar) {
+        Calendar calendar = (Calendar) result;
+        dueDate = calendar.getTime();
+      } else if (result instanceof String) {
+        try {
+          // TODO use a locale-sensitive date format?
+          dueDate = new SimpleDateFormat(dateFormat).parse((String) result);
+        } catch (ParseException e) {
+          throw new JbpmException("invalid base date: " + result, e);
+        }
+      } else {
+        throw new JbpmException("invalid base date: " + result);
+      }
 
+      // fixed duration is optional
+      String operationString = dateDurationMatcher.group(2);
+      if (operationString != null) {
+        char operation = operationString.charAt(0);
+        String duration = dateDurationMatcher.group(3);
+        // add duration to base date
+        BusinessCalendar businessCalendar = EnvironmentImpl.getFromCurrent(BusinessCalendar.class);
+        dueDate = operation == '+' ? businessCalendar.add(dueDate, duration)
+            : businessCalendar.subtract(dueDate, duration);
+      }
+    } else {
+      // is due date description in fixed_duration format?
+      Matcher durationMatcher = durationPattern.matcher(durationExpression);
+      if (durationMatcher.matches()) {
+        // add duration to current date
+        dueDate = EnvironmentImpl.getFromCurrent(BusinessCalendar.class).add(
+            Clock.getTime(), durationMatcher.group(1));
+      } else {
+        throw new JbpmException("invalid due date duration: " + durationExpression);
+      }
+    }
+    return dueDate;
+  }
+
   public Duration(boolean isBusinessTime, int millis, int seconds, int minutes, int hours, int days, int weeks, int months, int years) {
     this.isBusinessTime = isBusinessTime;
     this.millis = millis;

Added: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cmd/DeleteJobCmd.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cmd/DeleteJobCmd.java	                        (rev 0)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cmd/DeleteJobCmd.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.pvm.internal.cmd;
+
+import org.jbpm.api.JbpmException;
+import org.jbpm.api.cmd.Environment;
+import org.jbpm.pvm.internal.job.JobImpl;
+import org.jbpm.pvm.internal.session.DbSession;
+
+
+/**
+ * @author Joram Barrez
+ */
+public class DeleteJobCmd extends AbstractCommand<Boolean>{
+  
+  private static final long serialVersionUID = 1L;
+  
+  protected long jobId;
+  
+  public DeleteJobCmd(long jobId) {
+    this.jobId = jobId;
+  }
+  
+  public Boolean execute(Environment environment) throws Exception {
+    DbSession dbSession = environment.get(DbSession.class);
+    if (dbSession==null) {
+      throw new JbpmException("no " + DbSession.class.getName() + " found in environment");
+    }
+    JobImpl job = dbSession.get(JobImpl.class, jobId);
+    if (job != null) {
+      dbSession.delete(job);
+      return true;
+    } 
+    return false;
+  }
+
+}

Added: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/PeriodicStartProcessTimer.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/PeriodicStartProcessTimer.java	                        (rev 0)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/PeriodicStartProcessTimer.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -0,0 +1,200 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.pvm.internal.job;
+
+import java.text.ParseException;
+
+import org.jbpm.api.ExecutionService;
+import org.jbpm.api.JbpmException;
+import org.jbpm.api.RepositoryService;
+import org.jbpm.api.cmd.Environment;
+import org.jbpm.internal.log.Log;
+import org.jbpm.pvm.internal.cal.CronExpression;
+import org.jbpm.pvm.internal.cal.Duration;
+import org.jbpm.pvm.internal.env.EnvironmentImpl;
+import org.jbpm.pvm.internal.session.DbSession;
+import org.jbpm.pvm.internal.util.Clock;
+
+
+/**
+ * Job that periodically startes a new process instance of a given process definition.
+ * 
+ * @author Joram Barrez
+ */
+public class PeriodicStartProcessTimer extends TimerImpl {
+
+  private static final long serialVersionUID = 1L;
+  
+  private static final Log LOG = Log.getLog(PeriodicStartProcessTimer.class.getName());
+
+  // Override execution logic of regular timer
+  public Boolean execute(Environment environment) throws Exception {
+    
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Periodic start process triggered at " + Clock.getTime());
+    }
+    
+    boolean processDefinitionExists = processDefinitionExists(environment);
+    boolean newDueDateCalculated = false;
+    
+    if (processDefinitionExists) {
+      startProcessInstance(environment);
+      newDueDateCalculated = calculateDueDate(environment);
+    } 
+    
+    if (!processDefinitionExists || !newDueDateCalculated) {
+      deleteThisJob(environment);
+    } else {
+      saveThisJob(environment);
+    }
+    
+    return null;
+  }
+  
+  protected boolean processDefinitionExists(Environment environment) {
+    RepositoryService repositoryService = environment.get(RepositoryService.class);
+    boolean processExists = !repositoryService.createProcessDefinitionQuery()
+                                .processDefinitionName(getProcessDefinitionName()).list().isEmpty();
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Process definition with name " + getProcessDefinitionName() + " still exists: " + processExists);
+    }
+    
+    return processExists;
+  }
+
+  protected void startProcessInstance(Environment environment) {
+    ExecutionService executionService = environment.get(ExecutionService.class);
+    if (executionService == null) {
+      throw new JbpmException("no " + ExecutionService.class.getName() + " in environment"); 
+    }
+    
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Starting a new process instance for process definition with name " + getProcessDefinitionName());
+    }
+    
+    executionService.startProcessInstanceByKey(getProcessDefinitionName());
+  }
+  
+  protected boolean calculateDueDate(Environment environment) throws ParseException {
+      
+    if (getIntervalExpression() != null && Duration.isValidExpression(getIntervalExpression())) {
+      duedate = Duration.calculateDueDate(getIntervalExpression());
+    } else if (getIntervalExpression() != null &&  CronExpression.isValidExpression(getIntervalExpression())) {
+      duedate = new CronExpression(getIntervalExpression()).getNextValidTimeAfter(Clock.getTime());
+    } else {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("No next duedate calculated for periodic start process job " +
+                "with intervalExpression " + getIntervalExpression());
+      }
+      return false;
+    }
+    
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Next process start duedate: " + duedate);
+    }
+    
+    return true;
+  }
+  
+  protected void saveThisJob(Environment environment) {
+    DbSession dbSession = environment.get(DbSession.class);
+    if (dbSession == null) {
+      throw new JbpmException("no " + DbSession.class.getName() + " in environment"); 
+    }
+    dbSession.save(this);
+  }
+  
+  protected void deleteThisJob(Environment environment) {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Deleting periodic start job for process definition with name " + getProcessDefinitionName());
+    }
+    
+    DbSession dbSession = environment.get(DbSession.class);
+    if (dbSession == null) {
+      throw new JbpmException("no " + DbSession.class.getName() + " in environment"); 
+    }
+    dbSession.delete(this);
+  }
+  
+  @Override
+  public void schedule() {
+    if (duedate == null && getIntervalExpression() != null) {
+      try {
+        calculateDueDate(EnvironmentImpl.getCurrent());
+      } catch (ParseException e) {
+        throw new JbpmException("Cannot parse intervalExpression", e);
+      }
+    } else if (duedate == null) {
+      throw new JbpmException("Cannot schedule periodic start process timer: " +
+                              "no duedate or intervalExpression set");
+    }
+    super.schedule();
+  }
+  
+  @Override
+  public void validate() {
+    if (getProcessDefinitionName() == null) {
+      throw new JbpmException("No process definition name set for periodic start process timer");
+    }
+    if (duedate == null && getIntervalExpression() == null) {
+      throw new JbpmException("No duedate or intervalExpression found for periodic start process timer");
+    }
+  }
+  
+  public String getProcessDefinitionName() {
+    return signalName;
+  }
+  
+  public void setProcessDefinitionName(String processDefinitionName) {
+    this.signalName = processDefinitionName;
+  }
+
+  public String getIntervalExpression() {
+    return eventName;
+  }
+
+  /**
+   * Sets the expression that will define the interval between 
+   * two sequential process starts by this job.
+   * 
+   * Possible expression types are {@link Duration} and {@link CronExpression}.
+   */
+  public void setIntervalExpression(String intervalExpression) {
+    this.eventName = intervalExpression;
+  }
+  
+  @Override
+  public String toString() {
+    StringBuilder strb = new StringBuilder();
+    strb.append("PeriodicProcessStart[");
+    if (duedate != null) {
+      strb.append(duedate);
+    }
+    if (getIntervalExpression() != null) {
+      strb.append("| " + getIntervalExpression());
+    }
+    strb.append("]");
+    return strb.toString();
+  }
+
+}

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/TimerImpl.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/TimerImpl.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/TimerImpl.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -21,24 +21,19 @@
  */
 package org.jbpm.pvm.internal.job;
 
-import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Calendar;
 import java.util.Date;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.jbpm.api.JbpmException;
 import org.jbpm.api.cmd.Environment;
 import org.jbpm.api.job.Timer;
 import org.jbpm.internal.log.Log;
-import org.jbpm.pvm.internal.cal.BusinessCalendar;
+import org.jbpm.pvm.internal.cal.Duration;
 import org.jbpm.pvm.internal.env.EnvironmentImpl;
 import org.jbpm.pvm.internal.id.DbidGenerator;
 import org.jbpm.pvm.internal.jobexecutor.JobAddedNotification;
 import org.jbpm.pvm.internal.jobexecutor.JobExecutor;
 import org.jbpm.pvm.internal.model.ObservableElement;
-import org.jbpm.pvm.internal.script.ScriptManager;
 import org.jbpm.pvm.internal.session.DbSession;
 import org.jbpm.pvm.internal.session.TimerSession;
 import org.jbpm.pvm.internal.tx.Transaction;
@@ -58,10 +53,6 @@
   private static final Log log = Log.getLog(TimerImpl.class.getName());
 
   private final static String dateFormat = "yyyy-MM-dd HH:mm:ss";
-  private static final Pattern dateDurationPattern = Pattern.compile("\\s*(#\\{.+\\})\\s*"
-      + "(?:(\\+|-)\\s*(\\d+\\s+(?:business\\s+)?\\w+))?\\s*");
-  private static final Pattern durationPattern = Pattern.compile("\\s*(\\d+\\s+(?:business\\s+)?"
-      + "\\w+)\\s*");
 
   protected String signalName;
   protected String eventName;
@@ -80,62 +71,13 @@
 
   public void setDueDateDescription(String dueDateDescription) {
     if (dueDateDescription != null) {
-      duedate = calculateDueDate(dueDateDescription);
+      duedate = Duration.calculateDueDate(dueDateDescription);
     }
   }
 
-  public static Date calculateDueDate(String dueDateDescription) {
-    Date dueDate;
-    // is due date description in date_expression +|- fixed_duration format?
-    Matcher dateDurationMatcher = dateDurationPattern.matcher(dueDateDescription);
-    if (dateDurationMatcher.matches()) {
-      // evaluate date expression
-      String dateExpression = dateDurationMatcher.group(1);
-      Object result = ScriptManager.getScriptManager().evaluateExpression(dateExpression, null);
-      // convert result to Date
-      if (result instanceof Date) {
-        dueDate = (Date) result;
-      } else if (result instanceof Calendar) {
-        Calendar calendar = (Calendar) result;
-        dueDate = calendar.getTime();
-      } else if (result instanceof String) {
-        try {
-          // TODO use a locale-sensitive date format?
-          dueDate = new SimpleDateFormat(dateFormat).parse((String) result);
-        } catch (ParseException e) {
-          throw new JbpmException("invalid base date: " + result, e);
-        }
-      } else {
-        throw new JbpmException("invalid base date: " + result);
-      }
-
-      // fixed duration is optional
-      String operationString = dateDurationMatcher.group(2);
-      if (operationString != null) {
-        char operation = operationString.charAt(0);
-        String duration = dateDurationMatcher.group(3);
-        // add duration to base date
-        BusinessCalendar businessCalendar = EnvironmentImpl.getFromCurrent(BusinessCalendar.class);
-        dueDate = operation == '+' ? businessCalendar.add(dueDate, duration)
-            : businessCalendar.subtract(dueDate, duration);
-      }
-    } else {
-      // is due date description in fixed_duration format?
-      Matcher durationMatcher = durationPattern.matcher(dueDateDescription);
-      if (durationMatcher.matches()) {
-        // add duration to current date
-        dueDate = EnvironmentImpl.getFromCurrent(BusinessCalendar.class).add(
-            Clock.getTime(), durationMatcher.group(1));
-      } else {
-        throw new JbpmException("invalid due date duration: " + dueDateDescription);
-      }
-    }
-    return dueDate;
-  }
-
   public Boolean execute(Environment environment) throws Exception {
     if (log.isDebugEnabled()) log.debug("executing " + this);
-
+    
     if (environment==null) {
       throw new JbpmException("environment is null");
     }
@@ -189,6 +131,18 @@
 
     return deleteThisJob;
   }
+  
+  public void validate() {
+    if (getExecution() == null) {
+      throw new JbpmException("timer has no execution specified");
+    }
+    if ((getSignalName() == null) && (getEventName() == null)) {
+      throw new JbpmException("timer has no signalName or eventName specified");
+    }
+    if (getDueDate() == null) {
+      throw new JbpmException("timer scheduled at null date");
+    }
+  }
 
   public String toString() {
     StringBuilder text = new StringBuilder();

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/jobexecutor/JobExecutorTimerSession.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/jobexecutor/JobExecutorTimerSession.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/jobexecutor/JobExecutorTimerSession.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -49,10 +49,9 @@
 
   public void schedule(Timer timer) {
     if (timer == null) throw new JbpmException("null timer scheduled");
-    if (timer.getExecution() == null) throw new JbpmException("timer has no execution specified");
-    if ((timer.getSignalName() == null) && (timer.getEventName() == null)) throw new JbpmException("timer has no signalName or eventName specified");
-    if (timer.getDueDate() == null) throw new JbpmException("timer scheduled at null date");
-
+    TimerImpl timerImpl = (TimerImpl) timer;
+    timerImpl.validate();
+    
     log.debug("scheduling " + timer);
     session.save(timer);
     

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -307,8 +307,7 @@
       
       if (timer.getDuedate() == null && timerDefinition.getCronExpression() != null) {
         try {
-          timer.setDuedate(new CronExpression(timerDefinition.getCronExpression())
-                                                  .getNextValidTimeAfter(Clock.getTime()));
+          timer.setDuedate(new CronExpression(timerDefinition.getCronExpression()).getNextValidTimeAfter(Clock.getTime()));
         } catch (ParseException pe) {
           throw new JbpmException("Can't parse cron expression " + timerDefinition.getCronExpression(), pe);
         }

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/svc/ManagementServiceImpl.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/svc/ManagementServiceImpl.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/svc/ManagementServiceImpl.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -24,6 +24,7 @@
 import org.jbpm.api.JobQuery;
 import org.jbpm.api.ManagementService;
 import org.jbpm.pvm.internal.cmd.CreateJobQueryCmd;
+import org.jbpm.pvm.internal.cmd.DeleteJobCmd;
 import org.jbpm.pvm.internal.cmd.ExecuteJobCmd;
 import org.jbpm.pvm.internal.query.JobQueryImpl;
 
@@ -42,4 +43,8 @@
     query.setCommandService(commandService);
     return query;
   }
+  
+  public boolean deleteJob(long jobId) {
+    return commandService.execute(new DeleteJobCmd(jobId));
+  }
 }

Modified: jbpm4/trunk/modules/pvm/src/main/resources/jbpm.execution.hbm.xml
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/resources/jbpm.execution.hbm.xml	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/pvm/src/main/resources/jbpm.execution.hbm.xml	2010-01-26 18:44:04 UTC (rev 6135)
@@ -247,8 +247,11 @@
       <property name="signalName" column="SIGNAL_" />
       <property name="eventName" column="EVENT_" />
       <property name="repeat" column="REPEAT_" />
+      
+      <subclass name="org.jbpm.pvm.internal.job.PeriodicStartProcessTimer" discriminator-value="PeriodicStartProcess" />
+    
     </subclass>
-     
+    
   </class>
 
   <!-- ### HibernatePvmDbSession QUERIES ################################## -->

Added: jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/util/DateUtils.java
===================================================================
--- jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/util/DateUtils.java	                        (rev 0)
+++ jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/util/DateUtils.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.test.util;
+
+import java.util.Calendar;
+import java.util.Date;
+
+
+/**
+ * @author Joram Barrez
+ */
+public class DateUtils {
+
+  // No need to instantiate
+  private DateUtils() {}
+  
+  public static Date getDate(int day, int month, int year, int hour, int minute, int second) {
+    Calendar cal = Calendar.getInstance();
+    cal.set(Calendar.DAY_OF_MONTH, day);
+    cal.set(Calendar.MONTH, month);
+    cal.set(Calendar.YEAR, year);
+    cal.set(Calendar.HOUR_OF_DAY, hour);
+    cal.set(Calendar.MINUTE, minute);
+    cal.set(Calendar.SECOND, second);
+    cal.set(Calendar.MILLISECOND, 0);
+    return cal.getTime();
+  }
+  
+  public static Date getDateAtMidnight(int day, int month, int year) {
+    Calendar cal = Calendar.getInstance();
+    cal.set(Calendar.DAY_OF_MONTH, day);
+    cal.set(Calendar.MONTH, month);
+    cal.set(Calendar.YEAR, year);
+    cal.set(Calendar.HOUR_OF_DAY, 0);
+    cal.set(Calendar.MINUTE, 0);
+    cal.set(Calendar.SECOND, 0);
+    cal.set(Calendar.MILLISECOND, 0);
+    return cal.getTime();
+  }
+  
+}

Modified: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/intermediatecatch/IntermediateCatchTimerEventTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/intermediatecatch/IntermediateCatchTimerEventTest.java	2010-01-26 18:25:52 UTC (rev 6134)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/intermediatecatch/IntermediateCatchTimerEventTest.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -30,6 +30,7 @@
 import org.jbpm.pvm.internal.util.Clock;
 import org.jbpm.test.JbpmTestCase;
 import org.jbpm.test.assertion.CollectionAssertions;
+import org.jbpm.test.util.DateUtils;
 
 /**
  * @author Joram Barrez
@@ -65,7 +66,7 @@
     "  </process>" +
     "</definitions>";
   
-  private static final String TIMER_CATCH_WITH_TIMECYCLE =
+  private static final String TIMER_CATCH_WITH_TIMECYCLE_DURATION =
     "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
     "  <process id='timeCycleProcess'>" +
     "    <startEvent id='theStart' />" +
@@ -132,8 +133,7 @@
   }
   
   public void testInvalidProcess() {
-    final String expectedMsg = "Intermediate catch event 'intermediateTimer' requires either a " +
-                                        "timeDate or a timeCycle definition (but not both)";
+    final String expectedMsg = "requires either a timeDate or a timeCycle definition (but not both)";
     try {
       deployBpmn2XmlString(BAD_PROCESS_1);
       fail();
@@ -150,7 +150,7 @@
   }
   
   public void testTimeCycleExpression() {
-    deployBpmn2XmlString(TIMER_CATCH_WITH_TIMECYCLE);
+    deployBpmn2XmlString(TIMER_CATCH_WITH_TIMECYCLE_DURATION);
     
     long processStartTime = 5000;
     Clock.setExplicitTime(new Date(processStartTime));
@@ -173,7 +173,7 @@
     CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), "intermediateTimer");
     
     Job timerJob = managementService.createJobQuery().processInstanceId(pi.getId()).uniqueResult();
-    Date expectedDueDate = getDateAtMidnight(10, Calendar.OCTOBER, 2099);
+    Date expectedDueDate = DateUtils.getDateAtMidnight(10, Calendar.OCTOBER, 2099);
     assertEquals(expectedDueDate.getTime(), timerJob.getDuedate().getTime());
     
     managementService.executeJob(timerJob.getId());
@@ -182,13 +182,13 @@
   
   public void testCronExpression() {
     deployBpmn2XmlString(TIMER_CATCH_WITH_CRON_EXPRESSION);
-    Clock.setExplicitTime(getDate(20, Calendar.JANUARY, 2010, 0, 1, 1)); // Start on 61 seconds
+    Clock.setExplicitTime(DateUtils.getDate(20, Calendar.JANUARY, 2010, 0, 1, 1)); // Start on 61 seconds
     
     ProcessInstance pi = executionService.startProcessInstanceByKey("timeDateProcess");
     CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), "intermediateTimer");
     
     Job timerJob = managementService.createJobQuery().processInstanceId(pi.getId()).uniqueResult();
-    assertEquals(getDate(20, Calendar.JANUARY, 2010, 0, 2, 0).getTime(), timerJob.getDuedate().getTime()); 
+    assertEquals(DateUtils.getDate(20, Calendar.JANUARY, 2010, 0, 2, 0).getTime(), timerJob.getDuedate().getTime()); 
     
     managementService.executeJob(timerJob.getId());
     assertProcessInstanceEnded(pi);
@@ -196,41 +196,19 @@
   
   public void testCronExpression2() {
     deployBpmn2XmlString(TIMER_CATCH_WITH_CRON_EXPRESSION_2);
-    Clock.setExplicitTime(getDateAtMidnight(21, Calendar.JANUARY, 2010)); // 21/01/2009 is a Thursday
+    Clock.setExplicitTime(DateUtils.getDateAtMidnight(21, Calendar.JANUARY, 2010)); // 21/01/2009 is a Thursday
     
     ProcessInstance pi = executionService.startProcessInstanceByKey("timeDateProcess");
     CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), "intermediateTimer");
     
     Job timerJob = managementService.createJobQuery().processInstanceId(pi.getId()).uniqueResult();
-    assertEquals(getDate(22, Calendar.JANUARY, 2010, 23, 0, 0).getTime(), timerJob.getDuedate().getTime());
+    assertEquals(DateUtils.getDate(22, Calendar.JANUARY, 2010, 23, 0, 0).getTime(), timerJob.getDuedate().getTime());
     
     managementService.executeJob(timerJob.getId());
     assertProcessInstanceEnded(pi);
   }
   
-  private Date getDateAtMidnight(int day, int month, int year) {
-    Calendar cal = Calendar.getInstance();
-    cal.set(Calendar.DAY_OF_MONTH, day);
-    cal.set(Calendar.MONTH, month);
-    cal.set(Calendar.YEAR, year);
-    cal.set(Calendar.HOUR_OF_DAY, 0);
-    cal.set(Calendar.MINUTE, 0);
-    cal.set(Calendar.SECOND, 0);
-    cal.set(Calendar.MILLISECOND, 0);
-    return cal.getTime();
-  }
-  
-  private Date getDate(int day, int month, int year, int hour, int minute, int second) {
-    Calendar cal = Calendar.getInstance();
-    cal.set(Calendar.DAY_OF_MONTH, day);
-    cal.set(Calendar.MONTH, month);
-    cal.set(Calendar.YEAR, year);
-    cal.set(Calendar.HOUR_OF_DAY, hour);
-    cal.set(Calendar.MINUTE, minute);
-    cal.set(Calendar.SECOND, second);
-    cal.set(Calendar.MILLISECOND, 0);
-    return cal.getTime();
-  }
+ 
 
 
 }

Added: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/TimerStartEventTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/TimerStartEventTest.java	                        (rev 0)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/TimerStartEventTest.java	2010-01-26 18:44:04 UTC (rev 6135)
@@ -0,0 +1,256 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.bpmn.test.startevent;
+
+import java.util.Calendar;
+
+import org.jbpm.api.JbpmException;
+import org.jbpm.api.ProcessInstanceQuery;
+import org.jbpm.api.job.Job;
+import org.jbpm.pvm.internal.util.Clock;
+import org.jbpm.test.JbpmTestCase;
+import org.jbpm.test.util.DateUtils;
+
+
+/**
+ * @author Joram Barrez
+ */
+public class TimerStartEventTest extends JbpmTestCase {
+  
+  private static final String INVALID_PROCESS_1 = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='invalidProcess1'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition />" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String INVALID_PROCESS_2 = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='invalidProcess1'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition >" +
+    "        <timeCycle>5 hours</timeCycle>" +
+    "        <timeDate>10/10/1985</timeDate>" +
+    "      </timerEventDefinition>" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String INVALID_PROCESS_3 = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='invalidProcess1'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition >" +
+    "        <timeCycle>5 abcdefghijklmnop</timeCycle>" +
+    "      </timerEventDefinition>" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String INVALID_PROCESS_4 = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='invalidProcess1'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition >" +
+    "        <timeCycle>Z 0 22 * * ?</timeCycle>" +
+    "      </timerEventDefinition>" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String TIMER_START_FIXED_DUEDATE = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='timerStartFixedDueDate'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition >" +
+    "        <timeDate>10/10/2099 00:00:00</timeDate>" +
+    "      </timerEventDefinition>" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='wait' />" +
+    "    <receiveTask id='wait' />" +
+    "    <sequenceFlow id='flow2' sourceRef='wait' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String TIMER_START_TIMECYCLE_DURATION = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='timerStartTimeCycleDuration'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition >" +
+    "        <timeCycle>10 hours</timeCycle>" +
+    "      </timerEventDefinition>" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='wait' />" +
+    "    <receiveTask id='wait' />" +
+    "    <sequenceFlow id='flow2' sourceRef='wait' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String TIMER_START_TIMECYCLE_CRON_EXPR = 
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='timerStartTimeCycleCronExpression'>" +
+    "    <startEvent id='theStart' >" +
+    "      <timerEventDefinition >" +
+    "        <timeCycle>0 0 22 * * ?</timeCycle>" + // every day at 22:00
+    "      </timerEventDefinition>" +
+    "    </startEvent>" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='wait' />" +
+    "    <receiveTask id='wait' />" +
+    "    <sequenceFlow id='flow2' sourceRef='wait' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  @Override
+  protected void tearDown() throws Exception {
+    Clock.setExplicitTime(null);
+    super.tearDown();
+  }
+  
+  public void testInvalidProcess() {
+    testDeployInvalidProcess(INVALID_PROCESS_1);
+    testDeployInvalidProcess(INVALID_PROCESS_2);
+    testDeployInvalidProcess(INVALID_PROCESS_3);
+    testDeployInvalidProcess(INVALID_PROCESS_4);
+  }
+  
+  private void testDeployInvalidProcess(String process) {
+    try {
+      deployBpmn2XmlString(process);
+      fail();
+    } catch (JbpmException e) {
+      // Exception is expected
+    }
+  }
+  
+  public void testTimerStartEventWithFixedDuedate() {
+    deployBpmn2XmlString(TIMER_START_FIXED_DUEDATE);
+    
+    // After deployment, there should be one job in the database that starts a new process instance
+    Job startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertNotNull(startProcessTimer);
+    assertEquals(DateUtils.getDateAtMidnight(10, Calendar.OCTOBER, 2099).getTime(), startProcessTimer.getDuedate().getTime());
+    
+    ProcessInstanceQuery procInstQuery = executionService.createProcessInstanceQuery()
+                            .processDefinitionId(findProcessDefinitionId("timerStartFixedDueDate"));
+    
+    // Triggering the job should start a new process instance of the deployed process definition
+    assertEquals(0, procInstQuery.count());
+    managementService.executeJob(startProcessTimer.getId());
+    assertEquals(1, procInstQuery.count());
+    
+    // Since a fixed duedate was used, the job should have been deleted from the database
+    startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertNull(startProcessTimer);
+  }
+  
+  public void testTimerStartEventWithDurationAsTimeCycle() {
+    Clock.setExplicitTime(DateUtils.getDateAtMidnight(10, Calendar.OCTOBER, 2099));
+    deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+    
+    // After deployment, there should be one job in the database that starts a new process instance
+    Job startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertNotNull(startProcessTimer);
+    assertEquals(DateUtils.getDate(10, Calendar.OCTOBER, 2099, 10, 0, 0).getTime(), startProcessTimer.getDuedate().getTime());
+    
+    // Triggering the job should start a new process instance of the deployed process definition
+    ProcessInstanceQuery procInstQuery = executionService.createProcessInstanceQuery()
+                      .processDefinitionId(findProcessDefinitionId("timerStartTimeCycleDuration"));
+    assertEquals(0, procInstQuery.count());
+    
+    // need to change current date to calculate the next duedate correctly
+    Clock.setExplicitTime(DateUtils.getDate(10, Calendar.OCTOBER, 2099, 10, 0, 0)); 
+    managementService.executeJob(startProcessTimer.getId());
+    assertEquals(1, procInstQuery.count());
+    
+    // Since a timeCycle was used, the job should have been recreated with a new duedate
+    startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertEquals(DateUtils.getDate(10, Calendar.OCTOBER, 2099, 20, 0, 0).getTime(), startProcessTimer.getDuedate().getTime());
+    
+    
+    // So we need to manually delete it
+    managementService.deleteJob(Long.valueOf(startProcessTimer.getId()));
+  }
+  
+  public void testTimerStartEventWithCronExpressionAsTimeCycle() {
+    Clock.setExplicitTime(DateUtils.getDateAtMidnight(10, Calendar.OCTOBER, 2099));
+    deployBpmn2XmlString(TIMER_START_TIMECYCLE_CRON_EXPR);
+    
+    // After deployment, there should be one job in the database that starts a new process instance
+    Job startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertNotNull(startProcessTimer);
+    assertEquals(DateUtils.getDate(10, Calendar.OCTOBER, 2099, 22, 0, 0).getTime(), startProcessTimer.getDuedate().getTime());
+    
+    // Triggering the job should start a new process instance of the deployed process definition
+    ProcessInstanceQuery procInstQuery = executionService.createProcessInstanceQuery()
+                      .processDefinitionId(findProcessDefinitionId("timerStartTimeCycleCronExpression"));
+    assertEquals(0, procInstQuery.count());
+    
+    // need to change current date to calculate the next duedate correctly
+    Clock.setExplicitTime(DateUtils.getDate(10, Calendar.OCTOBER, 2099, 22, 0, 0)); 
+    managementService.executeJob(startProcessTimer.getId());
+    assertEquals(1, procInstQuery.count());
+    
+    // Since a timeCycle was used, the job should have been recreated with a new duedate
+    startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertEquals(DateUtils.getDate(11, Calendar.OCTOBER, 2099, 22, 0, 0).getTime(), startProcessTimer.getDuedate().getTime());
+    
+    // So we need to manually delete it
+    managementService.deleteJob(Long.valueOf(startProcessTimer.getId()));
+  }
+  
+  public void testDeleteProcessDefinitionBeforeTimerTriggers() {
+    Clock.setExplicitTime(DateUtils.getDateAtMidnight(10, Calendar.OCTOBER, 2099));
+    String deployId = deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+    
+    // Delete the process definition
+    repositoryService.deleteDeploymentCascade(deployId);
+    registeredDeployments.remove(0);
+    
+    // After process definition deletion, the timer is still in the database
+    Job startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertNotNull(startProcessTimer);
+    
+    // When the timer triggers, it notices that the process definition is gone and it deletes itselves
+    managementService.executeJob(startProcessTimer.getId());
+    startProcessTimer = managementService.createJobQuery().uniqueResult();
+    assertNull(startProcessTimer);
+  }
+  
+  private String findProcessDefinitionId(String processDefinitionKey) {
+    return repositoryService.createProcessDefinitionQuery()
+              .processDefinitionName(processDefinitionKey).uniqueResult().getId();
+  }
+
+}



More information about the jbpm-commits mailing list