/** * Copyright (C) 2004 Orbeon, Inc. * * This program 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 program 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. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.util.task; // imports import org.apache.log4j.Logger; import org.orbeon.oxf.util.LoggerFactory; import org.orbeon.oxf.externalcontext.WebAppContext; import org.orbeon.oxf.externalcontext.WebAppListener; import java.util.ArrayList; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * Class for running Tasks with scheduling/thread pooling etc. <br> * * <p> * The tasks are Task objects. A user would extend the Task class and override * the abstract methods ( e.g. its run() method ). * </p> * <p> * Persistence of the Task objects allows the TaskScheduler to be shutdown and * restarted with all the scheduled tasks read from storage. * </p> * * @author Efraim Berkovich * @version 1.0 * */ public class TaskScheduler { private static Logger logger = LoggerFactory.createLogger(TaskScheduler.class); // instance variables private Timer schedulerThread; private ArrayList taskList; private long initTime; private TaskPersistStrategy persistStrategy = null; /** * Create a task scheduler with a default thread pool size. */ private TaskScheduler() { schedulerThread = new Timer(false); initTime = System.currentTimeMillis(); taskList = new ArrayList(); } /** * Set the particular methodology for persisting Tasks * @param strategy The TaskPersistStrategy to use */ public void setPersistStrategy(TaskPersistStrategy strategy) { synchronized (this) { persistStrategy = strategy; } } /** * Get the time the TaskScheduler was started. * @return the time (in millis) when the Scheduler was started. */ public long getSchedulerStartTime() { return initTime; } /** * Schedules the specified task for execution according to the task's * scheduling properties. * @param task The task to schedule for execution. * * @exception IllegalStateException if task was already scheduled or cancelled, timer * was cancelled, or timer thread terminated. * @exception Exception if cannot persist task */ public void schedule(Task task) throws IllegalStateException , Exception { synchronized (this) { cleanupAll(); if (taskList.contains(task)) throw new IllegalStateException("Task was already scheduled"); Date time = new Date(task.getScheduledFirstTime()); RunTask runTask = new RunTask(task); if (task.getScheduledInterval() <= 0) { schedulerThread.schedule(runTask, time); } else { schedulerThread.scheduleAtFixedRate(runTask, time, task.getScheduledInterval()); } task.scheduler = this; taskList.add(task); this.persist(task); } } /** * Cancel all executing tasks and timer threads. * @param withRestart if true, all timer threads will restart; however, all scheduled task * will have been cancelled. * */ public void cancelAll(boolean withRestart) { synchronized (this) { taskList.clear(); schedulerThread.cancel(); if (withRestart) schedulerThread = new Timer(false); } } /** * Fetch all non-cancelled tasks at the time the method is called. If tasks are * cancelling during this time, some cancelled tasks may be returned. * * @return array of Tasks which are not cancelled */ public Task[] getRunningTasks() { ArrayList list = new ArrayList(); cleanupAll(); synchronized (this) { for (int i = 0; i < taskList.size(); i++) { Task task = (Task) taskList.get(i); if (!task.isCancelled()) { list.add(task); } } } Task[] out = new Task[list.size()]; for (int i = 0; i < out.length; i++) out[i] = (Task) list.get(i); return out; } /** * Find a particular task by its ID * @param taskID The task to find * @return Task for this ID or null if not found */ public Task findTaskByID(long taskID) { synchronized (this) { for (int i = 0; i < taskList.size(); i++) { Task task = (Task) taskList.get(i); if (task.getID() == taskID) { return task; } } } return null; } /** * Check and remove a particular task * @param task the potentially cancelled task to remove */ void cleanup(Task task) { synchronized (this) { if (taskList.contains(task)) { if (task.isCancelled()) { taskList.remove(task); if (persistStrategy != null) persistStrategy.delete(task); } } } } /** * Persists the task by telling the persist strategy to do so * @exception various possible depending on strategy */ void persist(Task task) throws Exception { synchronized (this) { if (persistStrategy != null) persistStrategy.write(task); } } /** * Go through the taskList and eliminate cancelled tasks */ private void cleanupAll() { synchronized (this) { for (int i = 0; i < taskList.size(); i++) { Task task = (Task) taskList.get(i); if (task.isCancelled()) { taskList.remove(i); if (persistStrategy != null) persistStrategy.delete(task); i--; } } } } /** * Get a singleton TaskScheduler. This can be used if the application wants * to share one task scheduler across the whole JVM. */ public static TaskScheduler getInstance(WebAppContext webAppContext) { synchronized (webAppContext) { final TaskScheduler existingTaskScheduler = (TaskScheduler) webAppContext.getAttributesMap().get("task-scheduler"); if (existingTaskScheduler != null) { return existingTaskScheduler; } else { final TaskScheduler newTaskScheduler = new TaskScheduler(); webAppContext.getAttributesMap().put("task-scheduler", newTaskScheduler); webAppContext.addListener(new WebAppListener() { public void webAppDestroyed() { newTaskScheduler.cancelAll(false); } }); return newTaskScheduler; } } } /** * RunTask class is scheduler to on the Timer * and is associated with a Task which it runs. */ private class RunTask extends TimerTask { // instance variables private Task task; /** * Create a new RunTask * @param task The Task to run */ public RunTask(Task task) { this.task = task; } /** * Run the Task */ public void run() { if (task.isCancelled()) { this.cancel(); return; } // NOTE: TBD // For now we just launch a thread to do the task. // This is potentially not scaleable. In the future, // we will pick threads from a pool. Thread runner = new Thread(task); runner.start(); task.setLastRunTime(System.currentTimeMillis()); } } }