org.jboss.as.ejb3.timerservice.TimerImpl Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.jboss.as.ejb3.timerservice;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
import javax.ejb.EJBException;
import javax.ejb.ScheduleExpression;
import javax.ejb.Timer;
import javax.ejb.TimerHandle;
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.ejb3.component.allowedmethods.AllowedMethodsInformation;
import org.jboss.as.ejb3.component.allowedmethods.MethodType;
import org.jboss.as.ejb3.timerservice.spi.TimedObjectInvoker;
/**
* Implementation of EJB3.1 {@link Timer}
*
* @author Carlo de Wolf
* @version $Revision: $
*/
public class TimerImpl implements Timer {
/**
* The output format with the cached final values, after the first toString() invocation
*/
private String toStringTemplate = null;
/**
* Unique id for this timer instance
*/
protected final String id;
/**
* The timer state
*/
protected volatile TimerState timerState;
/**
* The {@link javax.ejb.TimerService} through which this timer was created
*/
protected final TimerServiceImpl timerService;
/**
* The {@link TimedObjectInvoker} to which this timer corresponds
*/
protected final TimedObjectInvoker timedObjectInvoker;
/**
* The info which was passed while creating the timer.
*/
protected final Serializable info;
/**
* Indicates whether the timer is persistent
*/
protected final boolean persistent;
/**
* A {@link javax.ejb.TimerHandle} for this timer
*/
protected final TimerHandleImpl handle;
/**
* The initial (first) expiry date of this timer
*/
protected final Date initialExpiration;
/**
* The duration in milli sec. between timeouts
*/
protected final long intervalDuration;
/**
* If this is an entity bean then this is the primary key
*/
protected final Object primaryKey;
/**
* Next expiry date of this timer
*/
protected volatile Date nextExpiration;
/**
* The date of the previous run of this timer
*/
protected volatile Date previousRun;
private final String timedObjectId;
/**
* In use lock. This is held by timer invocations and cancellation within the scope of a transaction, all changes
* to timer state after creation should be done within this lock, to prevent state being overwritten by multiple
* threads.
*
* If the timer cancelled, but then rolled back we do not want the timer task to see this cancellation.
*
* todo: we can probably just use a sync block here
*/
private final ReentrantLock inUseLock = new ReentrantLock();
/**
* Creates a {@link TimerImpl}
*
* @param builder The builder with the timer information
* @param service The timer service through which this timer was created
*/
protected TimerImpl(Builder builder, TimerServiceImpl service) {
assert builder.id != null : "id is null";
this.id = builder.id;
this.timedObjectId = builder.timedObjectId;
this.info = builder.info;
this.persistent = builder.persistent;
this.initialExpiration = builder.initialDate;
this.intervalDuration = builder.repeatInterval;
if (builder.newTimer && builder.nextDate == null) {
this.nextExpiration = initialExpiration;
} else {
this.nextExpiration = builder.nextDate;
}
this.previousRun = builder.previousRun;
this.primaryKey = builder.primaryKey;
this.timerState = builder.timerState;
this.timerService = service;
this.timedObjectInvoker = service.getInvoker();
this.handle = new TimerHandleImpl(this.id, this.timedObjectInvoker.getTimedObjectId(), service);
}
/**
* {@inheritDoc}
*/
@Override
public void cancel() throws IllegalStateException, EJBException {
timerService.cancelTimer(this);
}
/**
* Returns the id of this timer
*
* @return
*/
public String getId() {
return this.id;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCalendarTimer() throws IllegalStateException, EJBException {
// first check whether this timer has expired or cancelled
this.assertTimerState();
return false;
}
/**
* {@inheritDoc}
*
* @see #getTimerHandle()
*/
@Override
public TimerHandle getHandle() throws IllegalStateException, EJBException {
// make sure it's in correct state
this.assertTimerState();
// for non-persistent timers throws an exception (mandated by EJB3 spec)
if (this.persistent == false) {
throw EjbLogger.EJB3_TIMER_LOGGER.invalidTimerHandlersForPersistentTimers("EJB3.1 Spec 18.2.6");
}
return this.handle;
}
/**
* This method returns the {@link javax.ejb.TimerHandle} corresponding to this {@link TimerImpl}.
* Unlike the {@link #getHandle()} method, this method does not throw an {@link IllegalStateException}
* or {@link javax.ejb.NoSuchObjectLocalException} or {@link javax.ejb.EJBException}, for non-persistent timers.
* Instead this method returns the {@link javax.ejb.TimerHandle} corresponding to that non-persistent
* timer (remember that {@link TimerImpl} creates {@link javax.ejb.TimerHandle} for both persistent and non-persistent timers)
*
* @return
*/
public TimerHandle getTimerHandle() {
return this.handle;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPersistent() throws IllegalStateException, EJBException {
// make sure the call is allowed in the current timer state
this.assertTimerState();
return this.persistent;
}
public boolean isTimerPersistent() {
return this.persistent;
}
/**
* {@inheritDoc}
*
* @see #getTimerInfo()
*/
@Override
public Serializable getInfo() throws IllegalStateException, EJBException {
// make sure this call is allowed
this.assertTimerState();
return this.info;
}
/**
* This method is similar to {@link #getInfo()}, except that this method does not check the timer state
* and hence does not throw either {@link IllegalStateException} or {@link javax.ejb.NoSuchObjectLocalException}
* or {@link javax.ejb.EJBException}.
*
* @return
*/
public Serializable getTimerInfo() {
return this.info;
}
/**
* {@inheritDoc}
*
* @see #getNextExpiration()
*/
@Override
public Date getNextTimeout() throws IllegalStateException, EJBException {
// first check the validity of the timer state
this.assertTimerState();
if (this.nextExpiration == null) {
throw EjbLogger.EJB3_TIMER_LOGGER.noMoreTimeoutForTimer(this);
}
return this.nextExpiration;
}
/**
* This method is similar to {@link #getNextTimeout()}, except that this method does not check the timer state
* and hence does not throw either {@link IllegalStateException} or {@link javax.ejb.NoSuchObjectLocalException}
* or {@link javax.ejb.EJBException}.
*
* @return
*/
public Date getNextExpiration() {
return this.nextExpiration;
}
/**
* Sets the next timeout of this timer
*
* @param next The next scheduled timeout of this timer
*/
public void setNextTimeout(Date next) {
if(next == null) {
setTimerState(TimerState.EXPIRED);
}
this.nextExpiration = next;
}
/**
* {@inheritDoc}
*/
@Override
public ScheduleExpression getSchedule() throws IllegalStateException, EJBException {
this.assertTimerState();
throw EjbLogger.EJB3_TIMER_LOGGER.invalidTimerNotCalendarBaseTimer(this);
}
/**
* {@inheritDoc}
*/
@Override
public long getTimeRemaining() throws IllegalStateException, EJBException {
// TODO: Rethink this implementation
// first check the validity of the timer state
this.assertTimerState();
if (this.nextExpiration == null) {
throw EjbLogger.EJB3_TIMER_LOGGER.noMoreTimeoutForTimer(this);
}
long currentTimeInMillis = System.currentTimeMillis();
long nextTimeoutInMillis = this.nextExpiration.getTime();
// if the next expiration is *not* in future and the repeat interval isn't
// a positive number (i.e. no repeats) then there won't be any more timeouts.
// So throw a NoMoreTimeoutsException.
// NOTE: We check for intervalDuration and not just nextExpiration because,
// it's a valid case where the nextExpiration is in past (maybe the server was
// down when the timeout was expected)
// if (nextTimeoutInMillis < currentTimeInMillis && this.intervalDuration <= 0)
// {
// throw new NoMoreTimeoutsException("No more timeouts for timer " + this);
// }
return nextTimeoutInMillis - currentTimeInMillis;
}
public boolean isAutoTimer() {
return false;
}
/**
* Cancels any scheduled timer task for this timer
*/
protected void cancelTimeout() {
// delegate to the timerservice, so that it can cancel any scheduled Future
// for this timer
this.timerService.cancelTimeout(this);
}
/**
* Returns the initial (first) timeout date of this timer
*
* @return
*/
public Date getInitialExpiration() {
return this.initialExpiration;
}
/**
* Returns the interval (in milliseconds), between timeouts, of this timer.
*
* @return
*/
public long getInterval() {
return this.intervalDuration;
}
/**
* Returns the timed object id to which this timer belongs
*
* @return
*/
public String getTimedObjectId() {
return timedObjectId;
}
/**
* Returns the timer service through which this timer was created
*
* @return
*/
public TimerServiceImpl getTimerService() {
return this.timerService;
}
/**
* Returns true if this timer is active. Else returns false.
*
* A timer is considered to be "active", if its {@link TimerState}
* is neither of the following:
*
* - {@link TimerState#CANCELED}
* - {@link TimerState#EXPIRED}
* - has not been suspended
*
*
* And if the corresponding timer service is still up
*
*
*
* @return
*/
public boolean isActive() {
return timerService.isStarted() && !isCanceled() && !isExpired() && (timerService.isScheduled(getId()) || timerState == TimerState.CREATED);
}
/**
* Returns true if this timer is in {@link TimerState#CANCELED} state. Else returns false.
*
* @return
*/
public boolean isCanceled() {
return timerState == TimerState.CANCELED;
}
/**
* Returns true if this timer is in {@link TimerState#EXPIRED} state. Else returns false
*
* @return
*/
public boolean isExpired() {
return timerState == TimerState.EXPIRED;
}
/**
* Returns true if this timer is in {@link TimerState#RETRY_TIMEOUT}. Else returns false.
*
* @return
*/
public boolean isInRetry() {
return timerState == TimerState.RETRY_TIMEOUT;
}
/**
* Returns the {@link java.util.Date} of the previous timeout of this timer
*
* @return
*/
public Date getPreviousRun() {
return this.previousRun;
}
/**
* Sets the {@link java.util.Date} of the previous timeout of this timer
*
* @param previousRun
*/
public void setPreviousRun(Date previousRun) {
this.previousRun = previousRun;
}
/**
* Returns the current state of this timer
*
* @return
*/
public TimerState getState() {
return this.timerState;
}
/**
* Asserts that the timer is not in any of the following states:
*
* - {@link TimerState#CANCELED}
* - {@link TimerState#EXPIRED}
*
*
* @throws javax.ejb.NoSuchObjectLocalException
* if the txtimer was canceled or has expired
*/
protected void assertTimerState() {
if (timerState == TimerState.EXPIRED)
throw EjbLogger.EJB3_TIMER_LOGGER.timerHasExpired();
if (timerState == TimerState.CANCELED)
throw EjbLogger.EJB3_TIMER_LOGGER.timerWasCanceled();
AllowedMethodsInformation.checkAllowed(MethodType.TIMER_SERVICE_METHOD);
}
/**
* Sets the state of this timer
*
* @param state The state of this timer
*/
public void setTimerState(TimerState state) {
this.timerState = state;
}
/**
* Suspends any currently scheduled task for this timer
*
* Note that, suspend does not cancel the {@link Timer}. Instead,
* it just cancels the next scheduled timeout. So once the {@link Timer}
* is restored (whenever that happens), the {@link Timer} will continue to
* timeout at appropriate times.
*
*/
// TODO: Revisit this method, we probably don't need this any more.
// In terms of implementation, this is just equivalent to cancelTimeout() method
public void suspend() {
// cancel any scheduled timer task (Future) for this timer
this.cancelTimeout();
}
/**
* Triggers timer, outside of normal expiration. Only used when running an explicit management trigger operation.
*
* The tigger operation simply runs the callback, it does not modify the timer state in any way, and there is no
* protection against overlapping events when running it. This is the expected behaviour, as otherwise the semantics
* of dealing with concurrent execution is complex and kinda weird.
*/
public void invokeOneOff() throws Exception {
this.getTimerTask().invokeBeanMethod(this);
}
/**
* Creates and schedules a {@link TimerTask} for the next timeout of this timer
*
* @param newTimer true
if this is a new timer being scheduled, and not a re-schedule due to a timeout
*/
public void scheduleTimeout(boolean newTimer) {
// just delegate to timerservice, for it to do the actual scheduling
this.timerService.scheduleTimeout(this, newTimer);
}
/**
* Returns the task which handles the timeouts of this {@link TimerImpl}
*
* @return
* @see TimerTask
*/
protected TimerTask getTimerTask() {
return new TimerTask(this);
}
public void lock() {
inUseLock.lock();
}
public void unlock() {
inUseLock.unlock();
}
/**
* A {@link javax.ejb.Timer} is equal to another {@link javax.ejb.Timer} if their
* {@link javax.ejb.TimerHandle}s are equal
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this.handle == null) {
return false;
}
if (obj instanceof TimerImpl == false) {
return false;
}
TimerImpl otherTimer = (TimerImpl) obj;
return this.handle.equals(otherTimer.getTimerHandle());
}
@Override
public int hashCode() {
return this.handle.hashCode();
}
/**
* A nice formatted string output for this timer
* {@inheritDoc}
*/
@Override
public String toString() {
if (this.toStringTemplate == null) {
// initialize with the first invocation
StringBuilder sb = new StringBuilder();
sb.append("[id=");
sb.append(this.id);
sb.append(" timedObjectId=");
sb.append(timedObjectId);
sb.append(" auto-timer?:");
sb.append(this.isAutoTimer());
sb.append(" persistent?:");
sb.append(this.persistent);
sb.append(" timerService=");
sb.append(this.timerService);
sb.append(" initialExpiration=");
sb.append(this.initialExpiration);
sb.append(" intervalDuration(in milli sec)=");
sb.append(this.intervalDuration);
this.toStringTemplate = sb.toString();
}
// complete with the dynamic values
StringBuilder sb = new StringBuilder(this.toStringTemplate);
sb.append(" nextExpiration=");
sb.append(this.nextExpiration);
sb.append(" timerState=");
sb.append(this.timerState);
sb.append(" info=");
sb.append(this.info);
sb.append("]");
return sb.toString();
}
public Object getPrimaryKey() {
return primaryKey;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
protected String id;
protected String timedObjectId;
protected Date initialDate;
protected long repeatInterval;
protected Date nextDate;
protected Date previousRun;
protected Serializable info;
protected Object primaryKey;
protected TimerState timerState;
protected boolean persistent;
protected boolean newTimer;
public Builder setId(final String id) {
this.id = id;
return this;
}
public Builder setTimedObjectId(final String timedObjectId) {
this.timedObjectId = timedObjectId;
return this;
}
public Builder setInitialDate(final Date initialDate) {
this.initialDate = initialDate;
return this;
}
public Builder setRepeatInterval(final long repeatInterval) {
this.repeatInterval = repeatInterval;
return this;
}
public Builder setNextDate(final Date nextDate) {
this.nextDate = nextDate;
return this;
}
public Builder setPreviousRun(final Date previousRun) {
this.previousRun = previousRun;
return this;
}
public Builder setInfo(final Serializable info) {
this.info = info;
return this;
}
public Builder setPrimaryKey(final Object primaryKey) {
this.primaryKey = primaryKey;
return this;
}
public Builder setTimerState(final TimerState timerState) {
this.timerState = timerState;
return this;
}
public Builder setPersistent(final boolean persistent) {
this.persistent = persistent;
return this;
}
public Builder setNewTimer(final boolean newTimer) {
this.newTimer = newTimer;
return this;
}
public String getId() {
return id;
}
public String getTimedObjectId() {
return timedObjectId;
}
public Date getInitialDate() {
return initialDate;
}
public long getRepeatInterval() {
return repeatInterval;
}
public Date getNextDate() {
return nextDate;
}
public Date getPreviousRun() {
return previousRun;
}
public Serializable getInfo() {
return info;
}
public Object getPrimaryKey() {
return primaryKey;
}
public TimerState getTimerState() {
return timerState;
}
public boolean isPersistent() {
return persistent;
}
public boolean isNewTimer() {
return newTimer;
}
public TimerImpl build(final TimerServiceImpl timerService) {
return new TimerImpl(this, timerService);
}
}
}