package org.sef4j.core.helpers.tasks; import java.io.Closeable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.sef4j.core.util.AsyncUtils; import org.sef4j.core.util.IStartableSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helper class for periodically executing a Runnable task * * sample usage: * <code> * PeriodicTask task = new PeriodicTask(....); * task.start(); * task.stop(); * </code> * */ public class PeriodicTask implements Closeable, IStartableSupport { private static final Logger LOG = LoggerFactory.getLogger(PeriodicTask.class); private String displayName; private Runnable delegateRunnableTask; private int period; private TimeUnit periodTimeUnit; private ScheduledExecutorService scheduledExecutor; private ScheduledFuture<?> scheduledFuture; private long nextScheduledFutureTime; private Object lock = new Object(); private Runnable wrapperRunnableTimerTask = new Runnable() { public void run() { onTimerTask(); } }; // ------------------------------------------------------------------------ public PeriodicTask(String displayName, Runnable runnableTask, int period, TimeUnit periodTimeUnit, ScheduledExecutorService scheduledExecutor) { super(); this.displayName = displayName; this.delegateRunnableTask = runnableTask; this.period = period; this.periodTimeUnit = periodTimeUnit; if (scheduledExecutor == null) { scheduledExecutor = AsyncUtils.defaultScheduledThreadPool(); } this.scheduledExecutor = scheduledExecutor; } public void close() { synchronized(lock) { if (isStarted()) { stop(); } // clear all fields this.delegateRunnableTask = null; this.scheduledExecutor = null; } } // ------------------------------------------------------------------------ public boolean isStarted() { synchronized(lock) { return scheduledFuture != null; } } public void start() { synchronized(lock) { if (scheduledFuture == null) { LOG.info("start periodic task " + displayName); scheduledFuture = scheduledExecutor.scheduleWithFixedDelay(wrapperRunnableTimerTask, 0, period, periodTimeUnit); } else { LOG.info("periodic task " + displayName + " already started, do nothing"); } } } public void stop() { synchronized(lock) { if (scheduledFuture != null) { LOG.info("stop periodic task " + displayName); scheduledFuture.cancel(false); scheduledFuture = null; nextScheduledFutureTime = 0; } else { LOG.info("periodic task " + displayName + " already stopped, do nothing"); } } } public void poll() { delegateRunnableTask.run(); } public void setPeriod(int period, TimeUnit periodTimeUnit) { synchronized(lock) { boolean running = isStarted(); if (running) { stop(); } this.period = period; this.periodTimeUnit = periodTimeUnit; if (running) { start(); } } } public long getNextScheduledFutureTime() { synchronized(lock) { return nextScheduledFutureTime; } } public long getNextScheduledInMillis() { synchronized(lock) { return nextScheduledFutureTime - System.currentTimeMillis(); } } protected void onTimerTask() { try { delegateRunnableTask.run(); } catch(Exception ex) { LOG.warn("Failed to execute periodic task " + displayName + " ... from timer Thread Pool... ignore, no rethrow!", ex); } synchronized(lock) { nextScheduledFutureTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(period, periodTimeUnit); } } // ------------------------------------------------------------------------ @Override public String toString() { String info; synchronized(lock) { if (scheduledFuture != null) { long nextMillis = nextScheduledFutureTime - System.currentTimeMillis(); info = "started, next schedule in " + nextMillis + " ms"; } else { info = "stopped"; } } return "PeriodicTask [displayName=" + displayName + " " + info + "]"; } // ------------------------------------------------------------------------ public static class Builder { public static final int DEFAULT_PERIOD_SECONDS = 3*60; private String displayName = "PeriodicTask"; private ScheduledExecutorService scheduledExecutor; private int period = DEFAULT_PERIOD_SECONDS; private TimeUnit periodTimeUnit = TimeUnit.SECONDS; private Runnable task; public Builder() { } public Builder(int period, TimeUnit periodTimeUnit) { this.period = period; this.periodTimeUnit = periodTimeUnit; } public PeriodicTask build() { if (task == null) { throw new IllegalStateException(); } return new PeriodicTask(displayName, task, period, periodTimeUnit, scheduledExecutor); } public PeriodicTask build(Runnable task) { withTask(task); return build(); } public Builder withScheduledExecutor(ScheduledExecutorService scheduledExecutor) { this.scheduledExecutor = scheduledExecutor; return this; } public Builder withPeriod(int period) { this.period = period; return this; } public Builder withTask(Runnable task) { this.task = task; return this; } public Builder withOptionalPeriodicityDef(PeriodicityDef def) { if (def != null) { this.period = def.getPeriod(); this.periodTimeUnit = def.getPeriodUnit(); this.scheduledExecutor = (def.getSchedulerExecutorName() != null && def.getSchedulerExecutorName().equals("default"))? AsyncUtils.defaultScheduledThreadPool() : null; } return this; } public Builder withDefaults(int period, TimeUnit periodTimeUnit, ScheduledExecutorService scheduledExecutor) { if (period <= 0) { this.period = period; this.periodTimeUnit = periodTimeUnit; } if (this.scheduledExecutor == null) { this.scheduledExecutor = scheduledExecutor; } return this; } } }