/**
* Copyright (C) 2007 Bull S. A. S.
* Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois
* This library 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
* version 2.1 of the License.
* This library 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
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
**/
package org.jbpm.env.session.timer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.TimerTask;
import java.util.Map.Entry;
import java.util.logging.Logger;
import org.jbpm.PvmException;
import org.jbpm.cal.BusinessCalendar;
import org.jbpm.cal.Duration;
import org.jbpm.env.Environment;
import org.jbpm.env.EnvironmentFactory;
import org.jbpm.env.session.Job;
import org.jbpm.env.session.Timer;
import org.jbpm.env.session.TimerSession;
import org.jbpm.jobexecutor.JobExecutor;
import org.jbpm.jobexecutor.JobSession;
import org.jbpm.pvm.Activity;
import org.jbpm.pvm.impl.ObjectReference;
import org.jbpm.tx.StandardTransaction;
import org.jbpm.tx.Transaction;
import org.jbpm.util.Listener;
/**
* Timers created with this service are committed at the end of the transaction,
* so their execution will be late if the delay is shorter than the transaction.
* In that case, they will be executed at the end of the transaction.
*
* @author Pascal Verdage
*/
public class StandardTimerSession implements TimerSession {
private static final Logger log = Logger.getLogger(TimerSession.class.getName());
private static class Pair<T1,T2> {
private T1 first;
private T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 first() {
return first;
}
public T2 second() {
return second;
}
}
private static class NotificationInfo extends Pair<Date,Long> {
private NotificationInfo(Date date, long repeatDelay) {
super(date, repeatDelay);
}
}
/* A JobNotificationTask should be in scheduledTimers or toBeScheduledTimers */
/* contains the JobExecutor notifications that will be scheduled
* when the transaction ends */
protected static Map<Long, NotificationInfo> toBeScheduledTimers =
new Hashtable<Long,NotificationInfo>();
protected static Map<Long,NotificationTask> scheduledTimers =
new HashMap<Long,NotificationTask>();
/* timers to signal the JobExecutor are sent when the transaction is commited */
protected static java.util.Timer javaTimer = new java.util.Timer();
/* we need to save the environment factory because there is
* no open environment when the listener is notified;
* but we keep this internal */
private EnvironmentFactory environmentFactory;
private static class NotificationTask extends TimerTask {
private EnvironmentFactory environmentFactory;
private NotificationTask(EnvironmentFactory environmentFactory) {
this.environmentFactory = environmentFactory;
}
@Override
public void run() {
log.finer("Sending notification to JobExecutor");
if (Environment.getCurrent() == null) {
environmentFactory.openEnvironment();
Environment.getCurrent().get(JobExecutor.class).jobWasAdded();
Environment.getCurrent().close();
} else {
throw new PvmException("No environment should be open in this thread");
}
}
}
//this method is called by the binding
public void start() {
environmentFactory = Environment.getCurrent().getEnvironmentFactory();
// notify the job executor after the transaction is completed
Listener jobExecutorNotifier = new Listener() {
public void event(Object source, String eventName, Object info) {
if ( (Transaction.EVENT_AFTERCOMPLETION.equals(eventName))
&& (StandardTransaction.STATE_COMMITTED.equals(info)) ) {
log.finer("creating " + toBeScheduledTimers.size() +
" notifications for timers");
for (Entry<Long, NotificationInfo> entry:
toBeScheduledTimers.entrySet()) {
NotificationTask task =
new NotificationTask(environmentFactory);
scheduledTimers.put(entry.getKey(), task);
if (entry.getValue().second() > 0) {
javaTimer.schedule(task,
entry.getValue().first(),
entry.getValue().second());
} else {
javaTimer.schedule(task, entry.getValue().first());
}
}
toBeScheduledTimers.clear();
}
}
};
Environment.getCurrent().get(Transaction.class)
.addListener(jobExecutorNotifier);
}
protected static void validScheduling(Timer timer) {
if (timer == null)
throw new IllegalArgumentException("null timer scheduled");
if (timer.getActivityReference() == null)
throw new IllegalArgumentException("null activity scheduled");
if (timer.getEligibleDate() == null)
throw new IllegalArgumentException("timer scheduled at null date");
if (timer.getEligibleDate().getTime() < 0)
throw new IllegalArgumentException("timer scheduled with a negative date");
}
public void schedule(Timer timer) {
validScheduling(timer);
log.fine("scheduling " + timer.getActivityReference() +
" for " + timer.getEligibleDate() +
" with repeat every " + timer.getRepeat());
// then persist the job
Environment.getCurrent().get(JobSession.class).save(timer);
// we prepare the JobExecutor notification
long delay = 0;
if (timer.getRepeat() != null) {
BusinessCalendar businessCalendar = new BusinessCalendar();
Duration duration = new Duration(timer.getRepeat());
delay = businessCalendar.add(new Date(0), duration).getTime();
}
toBeScheduledTimers.put(timer.getDbid(),
new NotificationInfo(timer.getEligibleDate(), delay));
}
public boolean cancel(Timer timer) {
boolean result = false;
if (timer != null) {
//remove JobExecutor notification
NotificationTask notificationTask =
scheduledTimers.remove(timer.getDbid());
if (notificationTask != null)
result = notificationTask.cancel();
//remove timer
log.fine("canceling " + timer);
if (toBeScheduledTimers.remove(timer.getDbid()) != null)
result = true;
Environment.getCurrent().get(JobSession.class).delete(timer);
}
return result;
}
private Timer getNextTask() {
return Environment.getCurrent()
.get(JobSession.class).findNextTimer();
}
public ObjectReference<Activity> getNextScheduledActivity() {
ObjectReference<Activity> result = null;
Timer timer = getNextTask();
if (timer != null)
result = timer.getActivityReference();
return result;
}
public long getNextScheduledActivityTime() {
long result = -1;
Timer timer = getNextTask();
if (timer != null && timer.getEligibleDate() != null)
result =
timer.getEligibleDate().getTime() -
System.currentTimeMillis();
return result;
}
public Collection<ObjectReference<Activity>> getScheduledActivities() {
Collection<ObjectReference<Activity>> result =
new ArrayList<ObjectReference<Activity>>();
Collection<Timer> timers =
Environment.getCurrent().get(JobSession.class)
.findAllTimers();
for (Timer timer : timers)
result.add(timer.getActivityReference());
return result;
}
public ObjectReference<Activity> getScheduledActivity(long id) {
ObjectReference<Activity> result = null;
Job job =
Environment.getCurrent().get(JobSession.class).get(id);
Timer timer = null;
if (job instanceof Timer) {
timer = (Timer) job;
}
if (timer != null)
result = timer.getActivityReference();
return result;
}
public void initialize() {
log.info("Re-initialize TimerService");
// the timers were persisted. We have to schedule notifications
Collection<Timer> timers =
Environment.getCurrent().get(JobSession.class)
.findAllTimers();
BusinessCalendar businessCalendar = new BusinessCalendar();
for (Timer timer : timers) {
Duration duration = new Duration(timer.getRepeat());
long delay = businessCalendar.add(new Date(0), duration).getTime();
NotificationInfo notificationInfo =
new NotificationInfo(timer.getEligibleDate(), delay);
toBeScheduledTimers.put(timer.getDbid(), notificationInfo);
}
}
}