/**
* 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;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* A task which can be scheduled to run at specified times by the TaskScheduler <br>
*
* <p>
* The user should extend this class and override the required methods (e.g. run() method).
* <p>
*
* @author Efraim Berkovich
* @version 1.0
*/
public abstract class Task implements Runnable, Externalizable {
// instance variables
/** has the cancel() method been called? */
protected boolean isCancelled = false;
/** The name of the task (for display purposes) */
protected String name = null;
private transient long ID;
private boolean hasBeenScheduled = false;
private long scheduledInitialTime = 0;
private long scheduledInterval = 0; // if <=0 -- means one-time execution
private long lastRunTime = 0;
// package view -- the task scheduler sets this when the task is scheduled with it
TaskScheduler scheduler = null;
// static variables
private static Boolean mutex = new Boolean(true);
private static long nextID = 0;
/**
* Creates a new task and assigns it a unique ID.
*
*/
protected Task() {
synchronized (mutex) {
this.ID = nextID;
nextID++;
if (nextID == Long.MAX_VALUE) nextID = 0;
}
}
/**
* Schedules the specified task for execution at the specified time. If the time
* is in the past, the task is scheduled for immediate execution.
* <p>
* There are two types of scheduling: one time and periodic.
* <p>
* <b>One time</b> <br>
* The task will execute exactly once.
* <p>
* <b>Periodic</b><br>
* The task is scheduled for repeated periodic execution, beginning at the specified time.
* Once a task starts running, the next occurrence of the task will start running <i>interval</i>
* time later. It is possible for one event-firing of the task (if it runs too long) will overlap
* another event-firing of that same task. If this is not desired, then the task should
* synchronize within its run() method.
* <p>
* This method can only be called once. Calling it more than once
* will cause an exception.
* <p>
* @param firstTime long system time at which to run the task.
* @param interval long number of millis between executions of the task, passing
* <=0 will cause the task to be executed once.
*
* @exception IllegalStateException On calling this method more than once.
*/
public synchronized final void setSchedule(long firstTime
, long interval)
throws IllegalStateException {
if (hasBeenScheduled)
throw new IllegalStateException("Cannot set schedule for the task more than once.");
hasBeenScheduled = true;
this.scheduledInitialTime = firstTime;
this.scheduledInterval = interval;
}
/**
* Get the task ID.
* @return unique Task ID.
*/
public long getID() {
return ID;
}
/**
* Get the first scheduled time for the task.
* @return the time the task was first scheduled to run (as long, millis)
*/
public long getScheduledFirstTime() {
return scheduledInitialTime;
}
/**
* Get the interval scheduled for the task.
* @return the interval scheduled for the task. (as long, millis)
*/
public long getScheduledInterval() {
return scheduledInterval;
}
/**
* Was the cancel method called for this task?
* @return true if cancel was called, false if not
*/
public boolean isCancelled() {
return isCancelled;
}
/**
* Get the name for the task
* @return the task name
*/
public String getName() {
if (name == null)
return this.getClass().toString();
return name;
}
/**
* Calling this method notifies the TaskScheduler (if the task has been scheduled)
* and forces it to persist this task's data.
* <p>
* @exception various possible depending on persist strategy of the scheduler
*/
public void persist()
throws Exception {
if (scheduler != null)
scheduler.persist(this);
}
/**
* Cancels this timer task. If the task has been scheduled for one-time execution and
* has not yet run, or has not yet been scheduled, it will never run. If the task has
* been scheduled for repeated execution, it will never run again. (If the task is
* running when this call occurs, the task will run to completion, but will never run again.)
* <p>
* Note that calling this method from within the run method of a repeating timer task
* absolutely guarantees that the timer task will not run again.
* <p>
* This method may be called repeatedly; the second and subsequent calls have no effect.
*
*/
public void cancel() {
isCancelled = true;
if (scheduler != null) {
scheduler.cleanup(this);
}
}
/**
* Returns the scheduled execution time of the most recent actual execution of this task.
* (If this method is invoked while task execution is in progress, the return value is
* the scheduled execution time of the ongoing task execution.)
* <p>
* This method is typically invoked from within a task's run method, to determine whether
* the current execution of the task is sufficiently timely to warrant performing the
* scheduled activity:
* <p>
* <pre>
public void run() {
if (System.currentTimeMillis() - lastExecutionTime() >=
MAX_TARDINESS)
return; // Too late; skip this execution.
// Perform the task
}
</pre>
*
* <p>
* @return the time at which the most recent execution of this task was scheduled to occur,
* in the format returned by Date.getTime(). The return value is undefined if the
* task has yet to commence its first execution.
*/
public long lastExecutionTime() {
return lastRunTime;
}
/**
* Set the last run time. This is only called by the TaskScheduler
* @param lastRunTime. The last run time
*/
void setLastRunTime(long lastRunTime) {
this.lastRunTime = lastRunTime;
}
/**
* This method persists the task. Tasks should override this method
* if they carry instance data. (Make sure to call super.writeExternal()
* if you override).
*
* @param ObjectOutput The stream to write the object to
* @exception IOException On write problems
*/
public void writeExternal(ObjectOutput out)
throws IOException {
out.writeLong(this.scheduledInitialTime);
out.writeLong(this.scheduledInterval);
out.writeLong(this.lastRunTime);
out.writeObject(this.name);
}
/**
* This method instantiates the task from persistent storage.
* Tasks should override this method if they carry instance data.
* (Make sure to call super.writeExternal() if you override).
* <p>
* When a Task is instantiated from deserialization, the scheduledInitialTime
* is set according to the following rules:
* <ul>
* <li>If the scheduledInitialTime is in the future, that time is kept.
* <li>If the scheduledInitialTime is in the past and the Task is a repeating
* task, then the scheduledInitialTime is set to the next time the task would run
* had the Task not been serialized. For example, if a Task runs once a day at 1pm,
* then the scheduledInitialTime will be set to the next 1pm running time. (The
* logic does not distinguish between fixed-rate vs. fixed-delay Tasks).
*
* </ul>
*
*
* @param ObjectInput The stream to write the object to
* @exception IOException On write problems
* @exception ClassNotFoundException On the class not being in the stream.
*/
public void readExternal(ObjectInput in)
throws IOException, java.lang.ClassNotFoundException {
this.scheduledInitialTime = in.readLong();
this.scheduledInterval = in.readLong();
this.lastRunTime = in.readLong();
this.name = (String) in.readObject();
// set scheduledInitialTime to make it in the future
if (scheduledInitialTime < System.currentTimeMillis()) {
long n = System.currentTimeMillis() - scheduledInitialTime;
n = n % scheduledInterval;
this.scheduledInitialTime = System.currentTimeMillis() + n;
}
// assume all deserialized tasks had setSchedule() called
hasBeenScheduled = true;
}
/**
* This method is the action to be performed. This method should be overriden.
* It will be called once at every time the task runs.
* <p>
* If a task encounters an error during run, it may call the cancel() method to
* prevent the task from running again.
* <p>
* If the task modifies its instance data during execution, then it should call
* the Task persist() method. This will notify the TaskScheduler to write the task
* to persistent storage by calling the writeExternal() method.
*
*/
public abstract void run();
/**
* This method should provide a status message regarding the task. Care should
* be taken to make sure the method is thread-safe, as code in the run()
* method and the caller of getStatus() will likely be in different threads.
*
* @return a task-specific status message
*/
public abstract String getStatus();
}