package org.mobicents.slee.runtime.facilities;
import javax.slee.facilities.TimerOptions;
import javax.slee.facilities.TimerPreserveMissed;
import javax.slee.resource.EventFlags;
import org.apache.log4j.Logger;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.runtime.activity.ActivityContext;
import org.mobicents.slee.runtime.transaction.SleeTransactionManager;
import org.mobicents.timers.TimerTask;
public class TimerFacilityTimerTask extends TimerTask {
private static final Logger logger = Logger.getLogger(TimerFacilityTimerTask.class);
private final TimerFacilityTimerTaskData data;
private static final SleeContainer sleeContainer = SleeContainer.lookupFromJndi();
public TimerFacilityTimerTask(TimerFacilityTimerTaskData data) {
super(data);
this.data = data;
}
public TimerFacilityTimerTaskData getTimerFacilityTimerTaskData() {
return data;
}
/*
*
* see SLEE spec 1.0 Sec. 13.1.6
*
* it doesn't look the same but this follows the pseudo code in
* 13.1.6! It's just simplified since the java.util.Timer class takes care
* of the scheduling stuff. This allows us to avoid having to wake up every
* x milliseconds and check our Timers to see if there's anything to fire,
* even when there's nothing to do. If we use the java.util.Timer class with
* scheduleAtFixedRate then it optimises the scheduling to avoid unnecessary
* polling.
*
* @see java.lang.Runnable#run()
*/
public void run() {
if (logger.isDebugEnabled()) {
logger.debug("Executing task with timer ID "+getData().getTaskID());
}
try {
runInternal();
} catch (Throwable t) {
logger.error(t.getMessage(),t);
}
}
private void runInternal() {
int remainingRepetitions = data.getRemainingRepetitions();
// till the actual task cancellation (in the scheduler) a periodic timer can still try to execute the task again after all executions been done
if (remainingRepetitions > 0) {
final TimerFacilityImpl timerFacility = sleeContainer.getTimerFacility();
long tRes = timerFacility.getResolution();
long tSys = System.currentTimeMillis();
long tDto = timerFacility.getDefaultTimeout();
boolean postIt = false;
final TimerOptions timerOptions = data.getTimerOptions();
final long period = data.getPeriod();
final long scheduledTime = data.getScheduledTime();
final long delayTillEvent = tSys - scheduledTime;
if (timerOptions.getPreserveMissed() == TimerPreserveMissed.ALL) {
/*
* Always post the event. Remember, this method will get called for
* late events since this TimerTask was scheduled to run at fixed
* rate. see Timer.scheduleAtFixedRate()
*/
postIt = true;
if (logger.isDebugEnabled()) {
logger.debug("TimerPreserveMissed.ALL so posting the event");
}
} else {
long timeOut;
if (timerOptions.getTimeout() == 0) {
timeOut = tDto;
} else {
timeOut = timerOptions.getTimeout();
}
timeOut = Math.min(Math.max(timeOut, tRes), period);
if (logger.isDebugEnabled()) {
logger
.debug("I'm using "
+ timeOut
+ " for the timeout to work out whether the event's late");
}
boolean lateEvent = delayTillEvent + timeOut < 0;
if (timerOptions.getPreserveMissed() == TimerPreserveMissed.NONE) {
//If events are late we NEVER want to post them
if (logger.isDebugEnabled()) {
logger.debug("TimerPreserveMissed.NONE");
}
if (!lateEvent) {
postIt = true;
if (logger.isDebugEnabled()) {
logger.debug("Event is NOT late so I'm posting it");
}
} else {
//Event is late so NOT posting it
if (logger.isDebugEnabled()) {
logger.debug("Event is late so I'm NOT posting it");
}
data.incrementMissedRepetitions();
}
} else if (timerOptions.getPreserveMissed() == TimerPreserveMissed.LAST) {
//Count missed events.
//Preserve the last missed event
if (logger.isDebugEnabled()) {
logger.debug("TimerPreserveMissed.LAST");
}
if (remainingRepetitions > 1
&& lateEvent) {
//Event is not the last one and event is late
if (logger.isDebugEnabled()) {
logger
.debug("Event is late and NOT the last one so I'm NOT posting it");
}
data.incrementMissedRepetitions();
} else {
if (logger.isDebugEnabled()) {
logger
.debug("Event is either NOT late, or late and is the last event so I'm posting it");
}
postIt = true;
}
}
}
// increment executions and recalculate remaining ones
data.incrementExecutions();
remainingRepetitions = data.getRemainingRepetitions();
if (logger.isDebugEnabled()) {
logger.debug("Delay till execution is " + delayTillEvent);
logger.debug("Remaining executions:" + remainingRepetitions);
}
// we need to know if the timer ended so we can warn the event router,
// if needed, that's the last event that the timer posts
boolean timerEnded = remainingRepetitions == 0;
if (postIt) {
//Post the timer event
TimerEventImpl timerEvent = new TimerEventImpl(data.getTimerID(), scheduledTime, tSys, (period < 0 ? Long.MAX_VALUE : period),
data.getNumRepetitions(), remainingRepetitions,
data.getMissedRepetitions(), this,timerEnded);
data.setMissedRepetitions(0);
final SleeTransactionManager txmgr = sleeContainer.getTransactionManager();
boolean terminateTx = txmgr.requireTransaction();
boolean doRollback = true;
try {
//Post the timer event to the queue.
data.setLastTick(System.currentTimeMillis());
final ActivityContext ac = sleeContainer.getActivityContextFactory()
.getActivityContext(data.getActivityContextHandle());
// the AC can be null in the edge case when the activity was removed while the basic timer is firing an event
// and thus the timer cancelation came a bit late
if (ac == null) {
logger.warn("Cannot fire timer event with id "+data.getTaskID()+" , because the underlying aci with id "+data.getActivityContextHandle()+" is gone.");
timerFacility.cancelTimer(data.getTimerID());
} else {
if (logger.isDebugEnabled()) {
logger
.debug("Posting timer event on event router queue. Activity context: "
+ ac.getActivityContextHandle()
+ " remainingRepetitions: "
+ data.getRemainingRepetitions());
}
ac.fireEvent(TimerEventImpl.EVENT_TYPE_ID,timerEvent,data.getAddress(),null,EventFlags.NO_FLAGS);
}
doRollback = false;
} finally {
try {
txmgr.requireTransactionEnd(terminateTx, doRollback);
} catch (Throwable e) {
logger.error(e.getMessage(),e);
}
}
}
else {
if (timerEnded) {
// if event is not posted and ended then we cancel it
// so it's removed
timerFacility.cancelTimer(data.getTimerID());
}
}
}
}
@SuppressWarnings("deprecation")
@Override
public void beforeRecover() {
long period = data.getPeriod();
long startTime = data.getStartTime();
long now = System.currentTimeMillis();
if (data.getTimerOptions().isPersistent()) {
long lastTick = data.getLastTick();
if (lastTick + period < now)
startTime = now;
else
startTime = lastTick + period;
}
data.setStartTime(startTime);
}
}