Author: jbarrez
Date: 2010-01-27 07:03:08 -0500 (Wed, 27 Jan 2010)
New Revision: 6138
Added:
jbpm4/trunk/modules/devguide/src/main/docbook/en/images/bpmn2.timer.start.event.png
jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/bpmn/event/timerstart/
jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/bpmn/event/timerstart/TimerStartTest.java
jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/bpmn/event/timerstart/
jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/bpmn/event/timerstart/timer_start_event.bpmn.xml
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/StartProcessTimer.java
Removed:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/PeriodicStartProcessTimer.java
Modified:
jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/StartEventBinding.java
jbpm4/trunk/modules/devguide/src/main/docbook/en/modules/ch03-Bpmn2.xml
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/hibernate/DbSessionImpl.java
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/repository/RepositorySessionImpl.java
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/DbSession.java
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/RepositorySession.java
jbpm4/trunk/modules/pvm/src/main/resources/jbpm.execution.hbm.xml
jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/TimerStartEventTest.java
Log:
JBPM-2722: completed implementation of timer start event
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-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/StartEventBinding.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -21,11 +21,16 @@
*/
package org.jbpm.bpmn.flownodes;
+import java.util.List;
+
import org.jbpm.bpmn.model.BpmnProcessDefinition;
import org.jbpm.bpmn.parser.BpmnParser;
-import org.jbpm.pvm.internal.job.PeriodicStartProcessTimer;
+import org.jbpm.internal.log.Log;
+import org.jbpm.pvm.internal.env.EnvironmentImpl;
+import org.jbpm.pvm.internal.job.StartProcessTimer;
import org.jbpm.pvm.internal.model.ActivityImpl;
import org.jbpm.pvm.internal.model.TimerDefinitionImpl;
+import org.jbpm.pvm.internal.session.DbSession;
import org.jbpm.pvm.internal.util.XmlUtil;
import org.jbpm.pvm.internal.xml.Parse;
import org.jbpm.pvm.internal.xml.Parser;
@@ -34,6 +39,8 @@
public class StartEventBinding extends BpmnBinding {
+
+ private static final Log LOG = Log.getLog(StartEventBinding.class.getName());
public StartEventBinding() {
super("startEvent");
@@ -61,6 +68,9 @@
return new NoneStartEventActivity(); // default
}
+ /**
+ * Timer start event
+ */
protected TimerStartEventActivity createTimerStartEvent(BpmnProcessDefinition
processDefinition,
Element timerEventDefinition, String eventId, BpmnParser parser, Parse parse)
{
@@ -71,7 +81,7 @@
return null;
}
- PeriodicStartProcessTimer startProcessTimer = new PeriodicStartProcessTimer();
+ StartProcessTimer startProcessTimer = new StartProcessTimer();
startProcessTimer.setProcessDefinitionName(processDefinition.getName());
if (timerDefinition.getDueDate() != null) {
@@ -82,8 +92,31 @@
startProcessTimer.setIntervalExpression(timerDefinition.getCronExpression());
}
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Scheduling a new start process timer for process definition "
+ + processDefinition.getName());
+ }
+
+ deleteStartProcessTimers(processDefinition.getName()); // Only the latest procDef
should have a timer start
startProcessTimer.schedule();
return timerStartEvent;
}
+ /**
+ * Deletes all existing timer start events for a given process definition.
+ *
+ * This is required when a new version of a process definition with a timer start is
deployed:
+ * only the latest may version may be started by the timer start event.
+ */
+ protected void deleteStartProcessTimers(String processDefinitionName) {
+ DbSession dbSession = EnvironmentImpl.getCurrent().get(DbSession.class);
+ List<StartProcessTimer> existingTimers =
dbSession.findStartProcessTimers(processDefinitionName);
+ for (StartProcessTimer spt : existingTimers) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Removing existing start process timer: " + spt);
+ }
+ dbSession.delete(spt);
+ }
+ }
+
}
Added:
jbpm4/trunk/modules/devguide/src/main/docbook/en/images/bpmn2.timer.start.event.png
===================================================================
(Binary files differ)
Property changes on:
jbpm4/trunk/modules/devguide/src/main/docbook/en/images/bpmn2.timer.start.event.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
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-27
07:59:50 UTC (rev 6137)
+++ jbpm4/trunk/modules/devguide/src/main/docbook/en/modules/ch03-Bpmn2.xml 2010-01-27
12:03:08 UTC (rev 6138)
@@ -1219,6 +1219,124 @@
<title>Advanced constructs</title>
+ <section id="Timer start event">
+
+ <title>Timer start event</title>
+
+ <para>
+ A timer start event is used to indicate that a process should be started when a
given
+ time condition is met. This could be a specific point in time (eg. October 10th,
2010 at
+ 5am), but also and more typically a recurring time (eg. every Friday at
midnight).
+ </para>
+
+ <para>
+ A timer start event is visualized as a circle with the clock icon inside.
+ <mediaobject><imageobject><imagedata align="center"
fileref="images/bpmn2.timer.start.event.png"/></imageobject></mediaobject>
+ </para>
+
+ <para>
+ Using a Timer Start event is done by adding a <emphasis
role="bold">timerEventDefinition</emphasis>
+ element below the startEvent element:
+ <programlisting>
+<startEvent name="Every Monday morning"
id="myStart">
+ <emphasis
role="bold"><timerEventDefinition/></emphasis>
+</startEvent>
+ </programlisting>
+ Following time definitions are possible:
+ <itemizedlist>
+ <listitem>
+ <emphasis role="bold">timeDate: </emphasis>specifies a
fixed date when a process instance
+ must be created. The default format of the date specification is
+ "dd/MM/yyyy hh:mm:ss". This can engine-wide be changed by
setting the
+ <emphasis
role="bold">jbpm.duedatetime.format</emphasis> property in the
configuration.
+ <programlisting>
+<startEvent id="myStartEvent" >
+ <timerEventDefinition>
+ <emphasis role="bold"><timeDate>10/10/2099
00:00:00</timeDate></emphasis>
+ </timerEventDefinition>
+</startEvent>
+ </programlisting>
+ Note that using a fixed duedate makes the process only useable for a single
time.
+ After the process instance is created, the timer start event will never fire
again.
+ </listitem>
+ <listitem>
+ <emphasis role="bold">timeCycle: </emphasis>specifies
the recurring time when a new
+ process instance will be created. Two types are possible
+
+ <para>
+ <emphasis role="bold">Duration expression:
</emphasis>
+ <programlisting>
+quantity [business] {second | seconds | minute | minutes |
+ hour | hours | day | days | week |
+ weeks | month | months | year | years}
+ </programlisting>
+ This is completely similar to a timer duration definition in JPDL. Note
that the
+ BPMN2 start timer event also understands "business
time". This
+ allows for example to define a "business day" as an
interval from
+ 9am to 5pm. This way, the time from 5pm to 9am will not be taken in
account when
+ the time on which the event will fire is calculated.
+ Please refer to the JPDL userguide for more details on how this business
calendar can be customized.
+ The following example shows a timer start event that will start a new
process
+ instance every five hours.
+ <programlisting>
+<startEvent id="myStartEvent" >
+ <timerEventDefinition>
+ <emphasis role="bold"><timeCycle>5
hours</timeCycle></emphasis>
+ </timerEventDefinition>
+</startEvent>
+ </programlisting>
+ </para>
+ <para>
+ <emphasis role="bold"><ulink
url="http://en.wikipedia.org/wiki/CRON_expression">Cron
expression:</ulink></emphasis>
+ altough duration expressions cover already a great deal of recurring time
definitions,
+ sometimes they are not easy to use.
+ When for example a process instance should be started every Friday night
23pm,
+ cron expressions allow a more natural way of defining such repeating
occurrences.
+ </para>
+ <para>
+ The following example shows a timer start event that will start a new
process
+ instance every Friday at 23pm.
+ <programlisting>
+<startEvent id="myStartEvent" >
+ <timerEventDefinition>
+ <emphasis role="bold"><timeCycle>0 0 23 ? *
FRI</timeCycle></emphasis>
+</timerEventDefinition>
+</startEvent>
+ </programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ The timer start event implementation in jBPM also has following features:
+ <itemizedlist>
+ <listitem>
+ Process definitions that have a timer start event, can be started as if it
also
+ were a none start event. This means that calling for example
+ executionService.startProcessInstanceByKey(key) just works.
+ </listitem>
+ <listitem>
+ The timer start event is internally implemented as a scheduled job. This
means
+ that a <emphasis role="bold">job executor</emphasis>
has to be configured for
+ the timer start event to work. The advantage of this implementation is that
the
+ timer start event firing is transactional (eg. if a service tasks right after
the
+ timer start event fails, the transaction will be rolled back and the timer
start event
+ will be retried later) and able to cope with a server crash (ie. the when the
server
+ comes back up, the timer start event will be picked up by the job executor
just as if
+ nothing has happened).
+ </listitem>
+ <listitem>
+ When a new version of a process definition with a timer start event is
deployed, the
+ old timer start event job is removed from the system. This means that
+ <emphasis role="bold">only the latest version of the process
definition will be used</emphasis>
+ to create a new process instances.
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </section> <!-- end of Timer start event -->
+
<section id="intermediateEvents">
<title>Intermediate events</title>
@@ -1275,7 +1393,7 @@
</para>
<para>
- Following delay definitions are possible.
+ Following delay definitions are possible (similar to those for a Timer Start
Event).
<itemizedlist>
<listitem>
<emphasis role="bold">timeDate: </emphasis>specifies a
fixed date when the timer will
@@ -1292,7 +1410,7 @@
</listitem>
<listitem>
<emphasis role="bold">timeCycle: </emphasis>specifies a
delay duration relative to the
- time when the execution enters the timer event. Two possible definitions are
possible
+ time when the execution enters the timer event. Two types are possible
<para>
<emphasis role="bold">Duration expression:
</emphasis>
Added:
jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/bpmn/event/timerstart/TimerStartTest.java
===================================================================
---
jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/bpmn/event/timerstart/TimerStartTest.java
(rev 0)
+++
jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/bpmn/event/timerstart/TimerStartTest.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -0,0 +1,79 @@
+/*
+ * 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.examples.bpmn.event.timerstart;
+
+import java.util.Calendar;
+
+import org.jbpm.api.NewDeployment;
+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 TimerStartTest extends JbpmTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Clock.setExplicitTime(DateUtils.getDateAtMidnight(10, Calendar.OCTOBER, 2099));
+ NewDeployment deployment = repositoryService.createDeployment();
+
deployment.addResourceFromClasspath("org/jbpm/examples/bpmn/event/timerstart/timer_start_event.bpmn.xml");
+ registerDeployment(deployment.deploy());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ Clock.setExplicitTime(null);
+ super.tearDown();
+ }
+
+ public void testTimerStartEventWithDurationAsTimeCycle() {
+
+ // 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();
+ assertEquals(0, procInstQuery.count());
+
+ // need to change current date to calculate the next duedate internally correctl
+ 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()));
+ }
+
+}
Added:
jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/bpmn/event/timerstart/timer_start_event.bpmn.xml
===================================================================
---
jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/bpmn/event/timerstart/timer_start_event.bpmn.xml
(rev 0)
+++
jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/bpmn/event/timerstart/timer_start_event.bpmn.xml 2010-01-27
12:03:08 UTC (rev 6138)
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://schema.omg.org/spec/BPMN/2.0
../../../../../../../../../../bpmn/src/main/resources/BPMN20.xsd"
+
xmlns="http://schema.omg.org/spec/BPMN/2.0"
+
typeLanguage="http://www.w3.org/2001/XMLSchema"
+
expressionLanguage="http://www.w3.org/1999/XPath"
+
targetNamespace="http://jbpm.org/example/bpmn2/timer_start_end_event...
+
xmlns:jbpm="http://jbpm.org/bpmn2">
+
+ <process id="noneStartEndEvent">
+
+ <startEvent id="start" >
+ <timerEventDefinition>
+ <timeCycle>10 hours</timeCycle>
+ </timerEventDefinition>
+ </startEvent>
+
+ <sequenceFlow id="flow1" sourceRef="start"
targetRef="wait" />
+
+ <receiveTask id="wait" />
+
+ <sequenceFlow id="flow2" sourceRef="wait"
targetRef="end" />
+
+ <endEvent id="end" />
+
+ </process>
+
+</definitions>
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-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/cal/Duration.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -110,52 +110,59 @@
}
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);
+ Date duedate = null;
+ if (durationExpression != null) {
+ ScriptManager scriptManager = ScriptManager.getScriptManager();
+
+ Date baseDate;
+ String durationString = null;
+ char durationSeparator = '+'; // needs to be initialized
+
+ if (durationExpression.startsWith("#")) {
+
+ String baseDateEL = durationExpression.substring(0,
durationExpression.indexOf("}") + 1);
+ Object result = scriptManager.evaluateExpression(baseDateEL, null);
+
+ if (result instanceof Date) {
+ baseDate = (Date) result;
+ } else if (result instanceof Calendar) {
+ baseDate = ((Calendar) result).getTime();
+ } else {
+ throw new JbpmException("Invalid basedate type: " + baseDateEL +
" is of type " + result.getClass().getName()
+ + ". Only Date and Calendar are supported");
}
+
+ int endOfELIndex = durationExpression.indexOf("}");
+ if (endOfELIndex < (durationExpression.length() - 1)) {
+ durationSeparator = durationExpression.substring(endOfELIndex +
1).trim().charAt(0);
+ if (durationSeparator != '+' && durationSeparator !=
'-') {
+ throw new JbpmException("Invalid duedate, + or - missing after
EL");
+ }
+ durationString = durationExpression.substring(endOfELIndex +
1).substring(2).trim();
+ }
+
} else {
- throw new JbpmException("invalid base date: " + result);
+
+ baseDate = Clock.getTime();
+ durationString = durationExpression;
+
}
- // 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
+ if (durationString == null || durationString.length() == 0) {
+ duedate = baseDate;
+ } else {
+ if (durationString.contains("business") && durationSeparator ==
'-') {
+ throw new JbpmException("Invalid duedate, subtraction ('-') not
supported if duedate contains 'business'");
+ }
BusinessCalendar businessCalendar =
EnvironmentImpl.getFromCurrent(BusinessCalendar.class);
- dueDate = operation == '+' ? businessCalendar.add(dueDate, duration)
- : businessCalendar.subtract(dueDate, duration);
+ if (durationSeparator == '+') {
+ duedate = businessCalendar.add(baseDate, durationString);
+ } else {
+ duedate = businessCalendar.subtract(baseDate, durationString);
+ }
}
- } 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;
+ return duedate;
}
public Duration(boolean isBusinessTime, int millis, int seconds, int minutes, int
hours, int days, int weeks, int months, int years) {
Modified:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/hibernate/DbSessionImpl.java
===================================================================
---
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/hibernate/DbSessionImpl.java 2010-01-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/hibernate/DbSessionImpl.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -43,6 +43,7 @@
import org.jbpm.pvm.internal.history.model.HistoryProcessInstanceImpl;
import org.jbpm.pvm.internal.id.DbidGenerator;
import org.jbpm.pvm.internal.job.JobImpl;
+import org.jbpm.pvm.internal.job.StartProcessTimer;
import org.jbpm.pvm.internal.model.ExecutionImpl;
import org.jbpm.pvm.internal.query.DeploymentQueryImpl;
import org.jbpm.pvm.internal.query.HistoryActivityInstanceQueryImpl;
@@ -427,6 +428,14 @@
return (JobImpl<?>) query.uniqueResult();
}
+ public List<StartProcessTimer> findStartProcessTimers(String
processDefinitionName) {
+ Query query = session.createQuery(
+ "select spt from " + StartProcessTimer.class.getName() + " as spt
" +
+ "where spt.signalName = :procDefName");
+ query.setString("procDefName", processDefinitionName);
+ return query.list();
+ }
+
public ProcessInstanceQueryImpl createProcessInstanceQuery() {
return new ProcessInstanceQueryImpl();
}
Deleted:
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 2010-01-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/PeriodicStartProcessTimer.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -1,200 +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.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();
- }
-
-}
Copied:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/StartProcessTimer.java
(from rev 6135,
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/StartProcessTimer.java
(rev 0)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/job/StartProcessTimer.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -0,0 +1,213 @@
+/*
+ * 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.ProcessDefinition;
+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.session.RepositorySession;
+import org.jbpm.pvm.internal.util.Clock;
+
+
+/**
+ * Job that starts a new process instance of a given process definition
+ * on a fixed duedate/periodic time.
+ *
+ * @author Joram Barrez
+ */
+public class StartProcessTimer extends TimerImpl {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Log LOG = Log.getLog(StartProcessTimer.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());
+ }
+
+ RepositorySession repoSession = environment.get(RepositorySession.class);
+ if (repoSession == null) {
+ throw new JbpmException("Could not find a" +
RepositorySession.class.getName() + " impl in environment");
+ }
+
+ ProcessDefinition procDef =
repoSession.findLatestProcessDefinitionByName(getProcessDefinitionName());
+ executionService.startProcessInstanceById(procDef.getId());
+ }
+
+ 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 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 start process 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 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 start process
timer");
+ }
+ if (duedate == null && getIntervalExpression() == null) {
+ throw new JbpmException("No duedate or intervalExpression found for 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 (getProcessDefinitionName() != null) {
+ strb.append(getProcessDefinitionName());
+ }
+ 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/repository/RepositorySessionImpl.java
===================================================================
---
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/repository/RepositorySessionImpl.java 2010-01-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/repository/RepositorySessionImpl.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -179,7 +179,23 @@
return null;
}
+
+ public ProcessDefinitionImpl findLatestProcessDefinitionByName(String
processDefinitionName) {
+ ProcessDefinition processDefinition = createProcessDefinitionQuery()
+ .processDefinitionName(processDefinitionName)
+ .orderDesc(ProcessDefinitionQuery.PROPERTY_VERSION)
+ .page(0, 1)
+ .uniqueResult();
+ if (processDefinition != null) {
+ return findProcessDefinitionById(processDefinition.getId());
+ } else {
+ validateRepositoryCache();
+ }
+
+ return null;
+ }
+
public DeploymentProperty findDeploymentPropertyByProcessDefinitionId(String
processDefinitionId) {
DeploymentProperty deploymentProperty = (DeploymentProperty) session.createQuery(
"select deploymentProperty " +
Modified:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/DbSession.java
===================================================================
---
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/DbSession.java 2010-01-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/DbSession.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -28,6 +28,7 @@
import org.jbpm.pvm.internal.client.ClientExecution;
import org.jbpm.pvm.internal.client.ClientProcessDefinition;
import org.jbpm.pvm.internal.job.JobImpl;
+import org.jbpm.pvm.internal.job.StartProcessTimer;
import org.jbpm.pvm.internal.model.ExecutionImpl;
import org.jbpm.pvm.internal.model.ScopeInstanceImpl;
import org.jbpm.pvm.internal.query.DeploymentQueryImpl;
@@ -122,4 +123,7 @@
/** the first job to finish among non-owned jobs or null if none */
public JobImpl<?> findFirstDueJob();
+
+ /** returns a list of start process timers for the given process definition */
+ public List<StartProcessTimer> findStartProcessTimers(String
processDefinitionId);
}
Modified:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/RepositorySession.java
===================================================================
---
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/RepositorySession.java 2010-01-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/session/RepositorySession.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -46,6 +46,8 @@
ProcessDefinitionImpl findProcessDefinitionById(String processDefinitionId);
ProcessDefinitionImpl findProcessDefinitionByKey(String processDefinitionKey);
+
+ ProcessDefinitionImpl findLatestProcessDefinitionByName(String processDefinitionName);
void updateDeploymentResource(String deploymentId, String resourceName, byte[] bytes);
}
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-27 07:59:50
UTC (rev 6137)
+++ jbpm4/trunk/modules/pvm/src/main/resources/jbpm.execution.hbm.xml 2010-01-27 12:03:08
UTC (rev 6138)
@@ -248,7 +248,7 @@
<property name="eventName" column="EVENT_" />
<property name="repeat" column="REPEAT_" />
- <subclass name="org.jbpm.pvm.internal.job.PeriodicStartProcessTimer"
discriminator-value="PeriodicStartProcess" />
+ <subclass name="org.jbpm.pvm.internal.job.StartProcessTimer"
discriminator-value="PeriodicStartProcess" />
</subclass>
Modified:
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 2010-01-27
07:59:50 UTC (rev 6137)
+++
jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/startevent/TimerStartEventTest.java 2010-01-27
12:03:08 UTC (rev 6138)
@@ -22,8 +22,12 @@
package org.jbpm.bpmn.test.startevent;
import java.util.Calendar;
+import java.util.List;
import org.jbpm.api.JbpmException;
+import org.jbpm.api.ProcessDefinition;
+import org.jbpm.api.ProcessDefinitionQuery;
+import org.jbpm.api.ProcessInstance;
import org.jbpm.api.ProcessInstanceQuery;
import org.jbpm.api.job.Job;
import org.jbpm.pvm.internal.util.Clock;
@@ -119,7 +123,7 @@
private static final String TIMER_START_TIMECYCLE_CRON_EXPR =
"<definitions
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
- " <process id='timerStartTimeCycleCronExpression'>" +
+ " <process id='timerStartTimeCycleCronExpression'
name='timerStartCron'>" +
" <startEvent id='theStart' >" +
" <timerEventDefinition >" +
" <timeCycle>0 0 22 * * ?</timeCycle>" + // every day at
22:00
@@ -189,7 +193,7 @@
.processDefinitionId(findProcessDefinitionId("timerStartTimeCycleDuration"));
assertEquals(0, procInstQuery.count());
- // need to change current date to calculate the next duedate correctly
+ // need to change current date to calculate the next duedate internally correctly
Clock.setExplicitTime(DateUtils.getDate(10, Calendar.OCTOBER, 2099, 10, 0, 0));
managementService.executeJob(startProcessTimer.getId());
assertEquals(1, procInstQuery.count());
@@ -235,6 +239,7 @@
String deployId = deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
// Delete the process definition
+ String procDefId = findProcessDefinitionId("timerStartTimeCycleDuration");
repositoryService.deleteDeploymentCascade(deployId);
registeredDeployments.remove(0);
@@ -243,11 +248,65 @@
assertNotNull(startProcessTimer);
// When the timer triggers, it notices that the process definition is gone and it
deletes itselves
+ // No new process instance should have been started
+ ProcessInstanceQuery procInstQuery =
executionService.createProcessInstanceQuery().processDefinitionId(procDefId);
+ assertEquals(0, procInstQuery.count());
+
managementService.executeJob(startProcessTimer.getId());
startProcessTimer = managementService.createJobQuery().uniqueResult();
assertNull(startProcessTimer);
+ assertEquals(0, procInstQuery.count());
}
+ /*
+ * Processes with a timer start event should be createable through the
executionService.
+ */
+ public void testStartProcessInstanceByKey() {
+ deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+ ProcessInstance pi =
executionService.startProcessInstanceByKey("timerStartTimeCycleDuration");
+ assertNotNull(pi);
+
+ Job startProcessTimer = managementService.createJobQuery().uniqueResult();
+ managementService.deleteJob(Long.valueOf(startProcessTimer.getId()));
+ }
+
+ public void testOnlyOneStartProcessActiveAfterRedeploy() {
+ deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+ List<Job> startProcessTimers = managementService.createJobQuery().list();
+ assertEquals(1, startProcessTimers.size());
+ String firstJobId = startProcessTimers.get(0).getId();
+
+ // Redeploy -> first job should be deleted
+ deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+ assertEquals(2, repositoryService.createProcessDefinitionQuery().list().size());
+ startProcessTimers = managementService.createJobQuery().list();
+ assertEquals(1, startProcessTimers.size());
+
+ String secondJobId = startProcessTimers.get(0).getId();
+ assertTrue(!firstJobId.equals(secondJobId));
+
+ managementService.deleteJob(Long.valueOf(secondJobId));
+ }
+
+ public void testLatestProcessDefinitionUsedAfterRedeploy() {
+ deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+ Job startProcessTimer = managementService.createJobQuery().uniqueResult();
+
+ // Redeploy
+ deployBpmn2XmlString(TIMER_START_TIMECYCLE_DURATION);
+ List<ProcessDefinition> procDefs =
repositoryService.createProcessDefinitionQuery()
+
.orderAsc(ProcessDefinitionQuery.PROPERTY_VERSION).list();
+ assertEquals(2, procDefs.size());
+
+ // Firing start process timer -> new process instance for latests version of proc
def
+ startProcessTimer = managementService.createJobQuery().uniqueResult();
+ managementService.executeJob(startProcessTimer.getId());
+ ProcessInstance procInst =
executionService.createProcessInstanceQuery().uniqueResult();
+ assertEquals(procDefs.get(1).getId(), procInst.getProcessDefinitionId());
+
+ managementService.deleteJob(Long.valueOf(startProcessTimer.getId()));
+ }
+
private String findProcessDefinitionId(String processDefinitionKey) {
return repositoryService.createProcessDefinitionQuery()
.processDefinitionName(processDefinitionKey).uniqueResult().getId();