package org.jboss.ejb.txtimer;
import org.jboss.ejb.AllowedOperationsAssociation;
import org.jboss.logging.Logger;
import javax.ejb.EJBException;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.TimerHandle;
import javax.management.ObjectName;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import java.io.Serializable;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerImpl implements javax.ejb.Timer, Synchronization
{
private static Logger log = Logger.getLogger(TimerImpl.class);
private static final int CREATED = 0;
private static final int STARTED_IN_TX = 1;
private static final int ACTIVE = 2;
private static final int CANCELED_IN_TX = 3;
private static final int CANCELED = 4;
private static final int EXPIRED = 5;
private static final int IN_TIMEOUT = 6;
private static final int RETRY_TIMEOUT = 7;
private static final String[] TIMER_STATES = {"created", "started_in_tx", "active", "canceled_in_tx",
"canceled", "expired", "in_timeout", "retry_timeout"};
private String timerId;
private TimedObjectId timedObjectId;
private TimedObjectInvoker timedObjectInvoker;
private Date firstTime;
private long periode;
private Serializable info;
private long nextExpire;
private int timerState;
private Timer utilTimer;
private int hashCode;
TimerImpl(String timerId, TimedObjectId timedObjectId, TimedObjectInvoker timedObjectInvoker, Serializable info)
{
this.timerId = timerId;
this.timedObjectId = timedObjectId;
this.timedObjectInvoker = timedObjectInvoker;
this.info = info;
setTimerState(CREATED);
}
void startTimer(Date firstTime, long periode)
{
this.firstTime = firstTime;
this.periode = periode;
nextExpire = firstTime.getTime();
TimerServiceImpl timerService = getTimerService();
timerService.addTimer(this);
registerTimerWithTx();
startInTx();
}
public String getTimerId()
{
return timerId;
}
public TimedObjectId getTimedObjectId()
{
return timedObjectId;
}
public Date getFirstTime()
{
return firstTime;
}
public long getPeriode()
{
return periode;
}
public long getNextExpire()
{
return nextExpire;
}
public Serializable getInfoInternal()
{
return info;
}
public boolean isActive()
{
return !isCanceled() && !isExpired();
}
public boolean isCanceled()
{
return timerState == CANCELED_IN_TX || timerState == CANCELED;
}
public boolean isExpired()
{
return timerState == EXPIRED;
}
public void cancel() throws IllegalStateException, NoSuchObjectLocalException, EJBException
{
assertTimedOut();
assertAllowedOperation("Timer.cancel");
registerTimerWithTx();
cancelInTx();
}
public void killTimer()
{
log.debug("killTimer: " + this);
if (timerState != EXPIRED)
setTimerState(CANCELED);
TimerServiceImpl timerService = getTimerService();
timerService.removeTimer(this);
utilTimer.cancel();
}
public long getTimeRemaining() throws IllegalStateException, NoSuchObjectLocalException, EJBException
{
assertTimedOut();
assertAllowedOperation("Timer.getTimeRemaining");
return nextExpire - System.currentTimeMillis();
}
public Date getNextTimeout() throws IllegalStateException, NoSuchObjectLocalException, EJBException
{
assertTimedOut();
assertAllowedOperation("Timer.getNextTimeout");
return new Date(nextExpire);
}
public Serializable getInfo() throws IllegalStateException, NoSuchObjectLocalException, EJBException
{
assertTimedOut();
assertAllowedOperation("Timer.getInfo");
return info;
}
public TimerHandle getHandle() throws IllegalStateException, NoSuchObjectLocalException, EJBException
{
assertTimedOut();
assertAllowedOperation("Timer.getHandle");
return new TimerHandleImpl(this);
}
public boolean equals(Object obj)
{
if (obj == this) return true;
if (obj instanceof TimerImpl)
{
TimerImpl other = (TimerImpl) obj;
return hashCode() == other.hashCode();
}
return false;
}
public int hashCode()
{
if (hashCode == 0)
{
String hash = "[" + timerId + "," + timedObjectId + "," + firstTime + "," + periode + "]";
hashCode = hash.hashCode();
}
return hashCode;
}
public String toString()
{
long remaining = nextExpire - System.currentTimeMillis();
String retStr = "[id=" + timerId + "target=" + timedObjectId + ",remaining=" + remaining + ",periode=" + periode +
"," + TIMER_STATES[timerState] + "]";
return retStr;
}
private void registerTimerWithTx()
{
TimerServiceImpl timerService = getTimerService();
Transaction tx = timerService.getTransaction();
if (tx != null)
{
try
{
tx.registerSynchronization(this);
}
catch (Exception e)
{
log.error("Cannot register txtimer with Tx: " + this);
}
}
}
private void setTimerState(int state)
{
log.debug("setTimerState: " + TIMER_STATES[state]);
timerState = state;
if (timerState == EXPIRED)
killTimer();
}
private void startInTx()
{
TimerServiceImpl timerService = getTimerService();
utilTimer = new Timer();
if (periode > 0)
utilTimer.schedule(new TimerTaskImpl(this), new Date(nextExpire), periode);
else
utilTimer.schedule(new TimerTaskImpl(this), new Date(nextExpire));
if (timerService.getTransaction() != null)
setTimerState(STARTED_IN_TX);
else
setTimerState(ACTIVE);
}
private void cancelInTx()
{
TimerServiceImpl timerService = getTimerService();
if (timerService.getTransaction() != null)
setTimerState(CANCELED_IN_TX);
else
killTimer();
}
private TimerServiceImpl getTimerService()
{
EJBTimerService ejbTimerService = EJBTimerServiceLocator.getEjbTimerService();
ObjectName containerId = timedObjectId.getContainerId();
Object instancePk = timedObjectId.getInstancePk();
TimerServiceImpl timerService = (TimerServiceImpl) ejbTimerService.getTimerService(containerId, instancePk);
if (timerService == null)
throw new NoSuchObjectLocalException("Cannot find TimerService: " + timedObjectId);
return timerService;
}
private void assertTimedOut()
{
if (timerState == EXPIRED)
throw new NoSuchObjectLocalException("Timer has expired");
if (timerState == CANCELED_IN_TX || timerState == CANCELED)
throw new NoSuchObjectLocalException("Timer was canceled");
}
private void assertAllowedOperation(String timerMethod)
{
AllowedOperationsAssociation.assertAllowedIn(timerMethod,
AllowedOperationsAssociation.IN_BUSINESS_METHOD |
AllowedOperationsAssociation.IN_EJB_TIMEOUT |
AllowedOperationsAssociation.IN_SERVICE_ENDPOINT_METHOD |
AllowedOperationsAssociation.IN_AFTER_BEGIN |
AllowedOperationsAssociation.IN_BEFORE_COMPLETION |
AllowedOperationsAssociation.IN_EJB_POST_CREATE |
AllowedOperationsAssociation.IN_EJB_REMOVE |
AllowedOperationsAssociation.IN_EJB_LOAD |
AllowedOperationsAssociation.IN_EJB_STORE);
}
public void beforeCompletion()
{
}
public void afterCompletion(int status)
{
if (status == Status.STATUS_COMMITTED)
{
log.debug("commit: " + this);
if (timerState == STARTED_IN_TX)
setTimerState(ACTIVE);
else if (timerState == CANCELED_IN_TX)
setTimerState(CANCELED);
else if (timerState == IN_TIMEOUT || timerState == RETRY_TIMEOUT)
setTimerState(periode == 0 ? EXPIRED : ACTIVE);
}
if (status == Status.STATUS_ROLLEDBACK)
{
log.debug("rollback: " + this);
if (timerState == STARTED_IN_TX)
killTimer();
else if (timerState == CANCELED_IN_TX)
setTimerState(ACTIVE);
else if (timerState == IN_TIMEOUT)
{
setTimerState(RETRY_TIMEOUT);
log.debug("retry: " + this);
EJBTimerService ejbTimerService = EJBTimerServiceLocator.getEjbTimerService();
ObjectName containerId = timedObjectId.getContainerId();
Object instancePk = timedObjectId.getInstancePk();
ejbTimerService.retryTimeout(containerId, instancePk, this);
}
else if (timerState == IN_TIMEOUT || timerState == RETRY_TIMEOUT)
setTimerState(periode == 0 ? EXPIRED : ACTIVE);
}
}
private class TimerTaskImpl extends TimerTask
{
private TimerImpl timer;
public TimerTaskImpl(TimerImpl timer)
{
this.timer = timer;
}
public void run()
{
log.debug("run: " + timer);
if (isActive() && periode > 0)
nextExpire += periode;
if (isActive())
{
try
{
setTimerState(IN_TIMEOUT);
timedObjectInvoker.callTimeout(timer);
}
catch (Exception e)
{
log.error("Error invoking ejbTimeout", e);
}
finally
{
if (timerState == IN_TIMEOUT)
{
log.warn("Timer was not registered with Tx, reseting state: " + timer);
setTimerState(periode == 0 ? EXPIRED : ACTIVE);
}
}
}
}
}
}