/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.ejb.timer;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.TimeZone;
import java.util.logging.Logger;
import javax.ejb.EJBException;
import javax.ejb.ScheduleExpression;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import com.caucho.config.timer.CronExpression;
import com.caucho.config.timer.CronTrigger;
import com.caucho.config.timer.EjbTimer;
import com.caucho.config.timer.TimeoutInvoker;
import com.caucho.config.timer.TimerTask;
import com.caucho.config.types.Trigger;
import com.caucho.ejb.server.AbstractEjbBeanManager;
import com.caucho.resources.TimerTrigger;
import com.caucho.util.Alarm;
import com.caucho.util.L10N;
/**
* Resin EJB timer service.
*/
// TODO This should probably be a application/server/cluster managed bean
// itself - would get rid of the boilerplate factory code; I could not figure
// out how to make that happen - tried @ApplicationScoped.
public class EjbTimerService implements TimerService {
private static final L10N L = new L10N(EjbTimerService.class);
@SuppressWarnings("unused")
private static final Logger log = Logger.getLogger(EjbTimerService.class
.getName());
private final AbstractEjbBeanManager _server;
private final TimeoutInvoker _timeout;
private final LinkedList<TimerTask> _timers = new LinkedList<TimerTask>();
/**
* Creates a new timer service.
*
* @param context
* EJB context.
*/
public EjbTimerService(AbstractEjbBeanManager server)
{
_server = server;
_timeout = new EjbTimerInvocation(server);
}
/**
* Create a single-action timer that expires after a specified duration.
*
* @param duration
* The number of milliseconds that must elapse before the timer
* expires.
* @param info
* Application information to be delivered along with the timer
* expiration notification. This can be null.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If duration is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method fails due to a system-level failure.
*/
@Override
public Timer createTimer(long duration, Serializable info)
throws IllegalArgumentException, IllegalStateException, EJBException
{
if (duration < 0) {
throw new IllegalArgumentException("Timer duration must not be negative.");
}
long expiration = Alarm.getCurrentTime() + duration;
return createOneTimeTimer(expiration, info);
}
/**
* Create a single-action timer that expires after a specified duration.
*
* @param duration
* The number of milliseconds that must elapse before the timer
* expires.
* @param timerConfig
* Timer configuration.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If duration is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method fails due to a system-level failure.
*/
@Override
public Timer createSingleActionTimer(long duration, TimerConfig timerConfig)
throws IllegalArgumentException, IllegalStateException, EJBException
{
if (duration < 0) {
throw new IllegalArgumentException(L
.l("Timer duration must not be negative."));
}
long expiration = Alarm.getCurrentTime() + duration;
Serializable info = null;
if (timerConfig != null)
info = timerConfig.getInfo();
return createOneTimeTimer(expiration, info);
}
/**
* Create an interval timer whose first expiration occurs after a specified
* duration, and whose subsequent expirations occur after a specified
* interval.
*
* @param initialDuration
* The number of milliseconds that must elapse before the first timer
* expiration notification.
* @param intervalDuration
* The number of milliseconds that must elapse between timer
* expiration notifications. Expiration notifications are scheduled
* relative to the time of the first expiration. If expiration is
* delayed (e.g. due to the interleaving of other method calls on the
* bean) two or more expiration notifications may occur in close
* succession to "catch up".
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If initialDuration is negative, or intervalDuration is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createTimer(long initialDuration, long intervalDuration,
Serializable info) throws IllegalArgumentException,
IllegalStateException, EJBException
{
if (initialDuration < 0) {
throw new IllegalArgumentException(L
.l("Timer initial duration must not be negative."));
}
if (intervalDuration < 0) {
throw new IllegalArgumentException(L
.l("Timer interval duration must not be negative."));
}
Date expiration = new Date(Alarm.getCurrentTime() + initialDuration);
return createRepeatingTimer(expiration, intervalDuration, info);
}
/**
* Create an interval timer whose first expiration occurs after a specified
* duration, and whose subsequent expirations occur after a specified
* interval.
*
* @param initialDuration
* The number of milliseconds that must elapse before the first timer
* expiration notification.
* @param intervalDuration
* The number of milliseconds that must elapse between timer
* expiration notifications. Expiration notifications are scheduled
* relative to the time of the first expiration. If expiration is
* delayed (e.g. due to the interleaving of other method calls on the
* bean) two or more expiration notifications may occur in close
* succession to "catch up".
* @param timerConfig
* Timer configuration.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If initialDuration is negative, or intervalDuration is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createIntervalTimer(long initialDuration, long intervalDuration,
TimerConfig timerConfig) throws IllegalArgumentException,
IllegalStateException, EJBException
{
if (initialDuration < 0) {
throw new IllegalArgumentException(L
.l("Timer initial duration must not be negative."));
}
if (intervalDuration < 0) {
throw new IllegalArgumentException(L
.l("Timer interval duration must not be negative."));
}
Date expiration = new Date(Alarm.getCurrentTime() + initialDuration);
Serializable info = null;
if (timerConfig != null)
info = timerConfig.getInfo();
return createRepeatingTimer(expiration, intervalDuration, info);
}
/**
* Create a single-action timer that expires at a given point in time.
*
* @param expiration
* The point in time at which the timer must expire.
* @param info
* Application information to be delivered along with the timer
* expiration notification. This can be null.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If expiration is null, or expiration.getTime() is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createTimer(Date expiration, Serializable info)
throws IllegalArgumentException, IllegalStateException, EJBException
{
if (expiration == null) {
throw new IllegalArgumentException(L
.l("Timer expiration must not be null."));
}
if (expiration.getTime() < 0) {
throw new IllegalArgumentException(L
.l("Timer expiration must not be negative."));
}
return createOneTimeTimer(expiration.getTime(), info);
}
/**
* Create a single-action timer that expires at a given point in time.
*
* @param expiration
* The point in time at which the timer must expire.
* @param timerConfig
* Timer configuration.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If expiration is null, or expiration.getTime() is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createSingleActionTimer(Date expiration, TimerConfig timerConfig)
throws IllegalArgumentException, IllegalStateException, EJBException
{
if (expiration == null) {
throw new IllegalArgumentException(L
.l("Timer expiration must not be null."));
}
if (expiration.getTime() < 0) {
throw new IllegalArgumentException(L
.l("Timer expiration must not be negative."));
}
Serializable info = null;
if (timerConfig != null)
info = timerConfig.getInfo();
return createOneTimeTimer(expiration.getTime(), info);
}
/**
* Create an interval timer whose first expiration occurs at a given point in
* time and whose subsequent expirations occur after a specified interval.
*
* @param initialExpiration
* The point in time at which the first timer expiration must occur.
* @param intervalDuration
* The number of milliseconds that must elapse between timer
* expiration notifications. Expiration notifications are scheduled
* relative to the time of the first expiration. If expiration is
* delayed (e.g. due to the interleaving of other method calls on the
* bean) two or more expiration notifications may occur in close
* succession to "catch up".
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If initialExpiration is null, or initialExpiration.getTime() is
* negative, or intervalDuration is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createTimer(Date initialExpiration, long intervalDuration,
Serializable info) throws IllegalArgumentException,
IllegalStateException, EJBException
{
if (initialExpiration == null) {
throw new IllegalArgumentException(L
.l("Timer initial expiration must not be null."));
}
if (initialExpiration.getTime() < 0) {
throw new IllegalArgumentException(L
.l("Timer initial expiration must not be negative."));
}
if (intervalDuration < 0) {
throw new IllegalArgumentException(L
.l("Timer interval duration must not be negative."));
}
return createRepeatingTimer(initialExpiration, intervalDuration, info);
}
/**
* Create an interval timer whose first expiration occurs at a given point in
* time and whose subsequent expirations occur after a specified interval.
*
* @param initialExpiration
* The point in time at which the first timer expiration must occur.
* @param intervalDuration
* The number of milliseconds that must elapse between timer
* expiration notifications. Expiration notifications are scheduled
* relative to the time of the first expiration. If expiration is
* delayed (e.g. due to the interleaving of other method calls on the
* bean) two or more expiration notifications may occur in close
* succession to "catch up".
* @param timerConfig
* Timer configuration.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If initialExpiration is null, or initialExpiration.getTime() is
* negative, or intervalDuration is negative.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createIntervalTimer(Date initialExpiration,
long intervalDuration, TimerConfig timerConfig)
throws IllegalArgumentException, IllegalStateException, EJBException
{
if (initialExpiration == null) {
throw new IllegalArgumentException(L
.l("Timer initial expiration must not be null."));
}
if (initialExpiration.getTime() < 0) {
throw new IllegalArgumentException(L
.l("Timer initial expiration must not be negative."));
}
if (intervalDuration < 0) {
throw new IllegalArgumentException(L
.l("Timer interval duration must not be negative."));
}
Serializable info = null;
if (timerConfig != null)
info = timerConfig.getInfo();
return createRepeatingTimer(initialExpiration, intervalDuration, info);
}
/**
* Create a calendar-based timer based on the input schedule expression.
*
* @param schedule
* A schedule expression describing the timeouts for this timer.
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If Schedule represents an invalid schedule expression.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createCalendarTimer(ScheduleExpression schedule)
throws IllegalArgumentException,
IllegalStateException, EJBException
{
return createScheduledTimer(schedule, null);
}
/**
* Create a calendar-based timer based on the input schedule expression.
*
* @param schedule
* A schedule expression describing the timeouts for this timer.
* @param timerConfig
* Timer configuration.
* @return The newly created Timer.
* @throws IllegalArgumentException
* If Schedule represents an invalid schedule expression.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Timer createCalendarTimer(ScheduleExpression schedule,
TimerConfig timerConfig) throws IllegalArgumentException,
IllegalStateException, EJBException
{
Serializable info = null;
if (timerConfig != null)
info = timerConfig.getInfo();
return createScheduledTimer(schedule, info);
}
/**
* Get all the active timers associated with this bean.
*
* @return A collection of javax.ejb.Timer objects.
* @throws IllegalStateException
* If this method is invoked while the instance is in a state that
* does not allow access to this method.
* @throws EJBException
* If this method could not complete due to a system-level failure.
*/
@Override
public Collection<Timer> getTimers() throws IllegalStateException,
EJBException
{
Collection<Timer> timers = new LinkedList<Timer>();
synchronized (_timers) {
for (TimerTask task : _timers) {
timers.add(new EjbTimer(task));
}
}
return timers;
}
/**
* Create a single-action timer that expires at a given point in time.
*
* @param expiration
* The point in time at which the timer must expire.
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
*/
private Timer createOneTimeTimer(long expiration, Serializable info)
{
Trigger trigger = new TimerTrigger(expiration);
return createTimer(trigger, info);
}
/**
* Create a single-action timer that expires at a given point in time.
*
* @param expiration
* The point in time at which the timer must expire.
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
*/
private Timer createTimer(Trigger trigger, Serializable info)
{
EjbTimer timer = new EjbTimer();
TimerTask scheduledTask
= new TimerTask(_timeout, timer, null, trigger, info);
timer.setScheduledTask(scheduledTask);
synchronized (_timers) {
_timers.add(scheduledTask);
}
scheduledTask.start();
return timer;
}
/**
* Create an interval timer whose first expiration occurs at a given point in
* time and whose subsequent expirations occur after a specified interval.
*
* @param expiration
* The point in time at which the first timer expiration must occur.
* @param interval
* The number of milliseconds that must elapse between timer
* expiration notifications.
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
*/
private Timer createRepeatingTimer(Date expiration, long interval,
Serializable info)
{
Trigger trigger = new TimerTrigger(expiration.getTime(), interval);
EjbTimer timer = new EjbTimer();
TimerTask scheduledTask = new TimerTask(_timeout, timer, null, trigger,
info);
timer.setScheduledTask(scheduledTask);
synchronized (_timers) {
_timers.add(scheduledTask);
}
scheduledTask.start();
return timer;
}
/**
* Create a calendar-based timer based on the input schedule expression.
*
* @param schedule
* A schedule expression describing the timeouts for this timer.
* @param info
* Application information to be delivered along with the timer
* expiration. This can be null.
* @return The newly created Timer.
*/
private Timer createScheduledTimer(ScheduleExpression schedule,
Serializable info)
{
CronExpression cronExpression = new CronExpression(schedule.getSecond(),
schedule.getMinute(), schedule.getHour(), schedule.getDayOfWeek(),
schedule.getDayOfMonth(), schedule.getMonth(), schedule.getYear());
TimeZone timezone = null;
if (!schedule.getTimezone().trim().equals("")) {
timezone = TimeZone.getTimeZone(schedule.getTimezone());
}
long start = -1;
long end = -1;
if (schedule.getStart() != null) {
start = schedule.getStart().getTime();
}
if (schedule.getEnd() != null) {
end = schedule.getEnd().getTime();
}
Trigger trigger = new CronTrigger(cronExpression, start, end, timezone);
EjbTimer timer = new EjbTimer();
TimerTask scheduledTask = new TimerTask(_timeout, timer, cronExpression,
trigger, info);
timer.setScheduledTask(scheduledTask);
synchronized (_timers) {
_timers.add(scheduledTask);
}
scheduledTask.start();
return timer;
}
/**
* Returns a string representation of the object.
*
* @return String representation of the object.
*/
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _server + "]";
}
}