/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.scheduling; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentLinkedQueue; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.entities.settings.events.LocalSettingsChangeListener; import nl.strohalm.cyclos.entities.settings.events.LocalSettingsEvent; import nl.strohalm.cyclos.scheduling.tasks.ScheduledTask; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.utils.logging.LoggingHandler; import nl.strohalm.cyclos.utils.lucene.IndexHandler; import nl.strohalm.cyclos.utils.tasks.TaskRunner; import org.apache.commons.lang.time.DateUtils; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.transaction.support.TransactionTemplate; /** * Handles execution of scheduled jobs * @author luis */ public class SchedulingHandler implements InitializingBean, DisposableBean, LocalSettingsChangeListener { /** * A {@link TimerTask} that dispatches the execution to a {@link HourlyScheduledTasks} if no other {@link HourlyScheduledTasks} is currently being * executed, or enqueues it for later execution * @author luis */ private class SchedulingTimerTask extends TimerTask { @Override public void run() { Calendar time = DateUtils.truncate(Calendar.getInstance(), Calendar.HOUR_OF_DAY); HourlyScheduledTasks runner = new HourlyScheduledTasks(SchedulingHandler.this, time); if (queue.isEmpty()) { // Nothing is currently being executed. Execute it right away runner.start(); } else { // Tasks of a previous hour are being executed. Enqueue this execution queue.offer(runner); } } } private Timer timer; private SettingsServiceLocal settingsService; private LoggingHandler loggingHandler; private TransactionTemplate transactionTemplate; private IndexHandler indexHandler; private TaskRunner taskRunner; private Queue<HourlyScheduledTasks> queue; private Map<String, ScheduledTask> tasks; private Integer lastScheduledMinute; @Override public void afterPropertiesSet() throws Exception { queue = new ConcurrentLinkedQueue<HourlyScheduledTasks>(); settingsService.addListener(this); } @Override public void destroy() throws Exception { shutdown(); if (queue != null) { queue.clear(); queue = null; } } public IndexHandler getIndexHandler() { return indexHandler; } public LoggingHandler getLoggingHandler() { return loggingHandler; } public SettingsServiceLocal getSettingsServiceLocal() { return settingsService; } public ScheduledTask getTask(final String name) { return tasks.get(name); } public TaskRunner getTaskRunner() { return taskRunner; } public List<ScheduledTask> getTasks() { return new ArrayList<ScheduledTask>(tasks.values()); } public TransactionTemplate getTransactionTemplate() { return transactionTemplate; } @Override public void onLocalSettingsUpdate(final LocalSettingsEvent event) { updateTime(); } public void runNextTasks() { HourlyScheduledTasks nextToRun = queue.poll(); if (nextToRun != null) { nextToRun.start(); } } public void setIndexHandler(final IndexHandler indexHandler) { this.indexHandler = indexHandler; } public void setLoggingHandler(final LoggingHandler loggingHandler) { this.loggingHandler = loggingHandler; } public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) { this.settingsService = settingsService; } public void setTaskRunner(final TaskRunner taskRunner) { this.taskRunner = taskRunner; } public void setTasks(final List<ScheduledTask> tasks) { this.tasks = new LinkedHashMap<String, ScheduledTask>(tasks.size()); for (ScheduledTask task : tasks) { ScheduledTask old = this.tasks.put(task.getName(), task); if (old != null) { throw new IllegalStateException("Trying to add 2 tasks with the same name '" + task.getName() + "': " + old + " and " + task); } } } public void setTransactionTemplate(final TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } /** * Starts running scheduled jobs */ public void start() { initializeTimer(); } /** * Should be called when the time the tasks run should be modified */ public synchronized void updateTime() { if (timer == null) { return; } final LocalSettings localSettings = settingsService.getLocalSettings(); if (lastScheduledMinute == null || lastScheduledMinute != localSettings.getSchedulingMinute()) { shutdown(); initializeTimer(); } } private synchronized void initializeTimer() { final LocalSettings localSettings = settingsService.getLocalSettings(); timer = new Timer("Scheduled tasks handler for " + localSettings.getApplicationName()); timer.scheduleAtFixedRate(new SchedulingTimerTask(), startsTaskAt(), DateUtils.MILLIS_PER_HOUR); lastScheduledMinute = localSettings.getSchedulingMinute(); } /** * Stops all running tasks */ private synchronized void shutdown() { if (timer != null) { timer.cancel(); timer = null; } } /** * Returns the date where the task will start running. It should be on the next hour, settings the minute to the <code>minute</code> property */ private Date startsTaskAt() { final LocalSettings localSettings = settingsService.getLocalSettings(); final Calendar startAt = Calendar.getInstance(); startAt.add(Calendar.HOUR_OF_DAY, 1); startAt.set(Calendar.MINUTE, localSettings.getSchedulingMinute()); startAt.set(Calendar.SECOND, 30); return startAt.getTime(); } }