/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt 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.mobicents.slee.runtime.facilities;
import java.util.concurrent.CountDownLatch;
import javax.slee.facilities.TimerOptions;
import javax.slee.facilities.TimerPreserveMissed;
import org.apache.log4j.Logger;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.activity.ActivityContext;
import org.mobicents.slee.container.activity.ActivityContextFactory;
import org.mobicents.slee.container.facilities.TimerFacility;
import org.restcomm.timers.TimerTask;
public class TimerFacilityTimerTask extends TimerTask {
private static final Logger logger = Logger
.getLogger(TimerFacilityTimerTask.class);
private final TimerFacilityTimerTaskData data;
private final static SleeContainer sleeContainer = SleeContainer
.lookupFromJndi();
private CountDownLatch countDownLatch = null;
public TimerFacilityTimerTask(TimerFacilityTimerTaskData data) {
super(data);
this.data = data;
super.autoRemoval = false;
}
public TimerFacilityTimerTaskData getTimerFacilityTimerTaskData() {
return data;
}
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public void runTask() {
if(countDownLatch != null) {
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error("failed to wait on tx commit confirmation", e);
}
}
runInternal();
}
private void runInternal() {
if (logger.isDebugEnabled()) {
logger.debug("Executing task with timer ID "
+ getData().getTaskID());
}
try {
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 TimerFacility 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.isTraceEnabled()) {
logger
.trace("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.isTraceEnabled()) {
logger
.trace("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.isTraceEnabled()) {
logger.trace("TimerPreserveMissed.NONE");
}
if (!lateEvent) {
postIt = true;
if (logger.isTraceEnabled()) {
logger.trace("Event is NOT late so I'm posting it");
}
} else {
// Event is late so NOT posting it
if (logger.isTraceEnabled()) {
logger.trace("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.isTraceEnabled()) {
logger.trace("TimerPreserveMissed.LAST");
}
if (remainingRepetitions > 1 && lateEvent) {
// Event is not the last one and event is late
if (logger.isTraceEnabled()) {
logger
.trace("Event is late and NOT the last one so I'm NOT posting it");
}
data.incrementMissedRepetitions();
} else {
if (logger.isTraceEnabled()) {
logger
.trace("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 (timerEnded && period > 0) {
// periodic timer that ended, cancel it's execution in scheduler
cancel();
}
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);
data.setMissedRepetitions(0);
// Post the timer event to the queue.
data.setLastTick(System.currentTimeMillis());
final ActivityContextFactory acFactory = sleeContainer
.getActivityContextFactory();
final ActivityContext ac = acFactory.getActivityContext(data
.getActivityContextHandle());
// the AC can be null if the activity is
// removed concurrently with the task execution
if (ac == null) {
logger.warn("Cannot fire timer event with id "
+ data.getTaskID()
+ " , because the underlying aci with id "
+ data.getActivityContextHandle() + " is gone.");
remove();
} else {
if (logger.isTraceEnabled()) {
logger
.trace("Posting timer event on event router queue. Activity context: "
+ ac.getActivityContextHandle()
+ " remainingRepetitions: "
+ data.getRemainingRepetitions());
}
// if the timer ended we use the event processing callbacks
// to cancel the timer after the event is routed
final CancelTimerEventProcessingCallbacks cancelTimerCallback = timerEnded ? new CancelTimerEventProcessingCallbacks(
this)
: null;
ac.fireEvent(TimerEventImpl.EVENT_TYPE_ID, timerEvent, data
.getAddress(), null, null, null,
cancelTimerCallback);
}
} else {
if (timerEnded) {
// if event is not posted and ended then we cancel it
// so it's removed
remove();
}
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
protected void remove() {
// remove from scheduler
super.removeFromScheduler();
// detach this timer from the ac
final ActivityContext ac = sleeContainer.getActivityContextFactory()
.getActivityContext(data.getActivityContextHandle());
if (ac != null) {
ac.detachTimer(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);
}
}