summaryrefslogblamecommitdiffstats
path: root/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java
blob: 18cbe760f6136fe2b9ad1c26bb9eb7aa8fa3a2e4 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                         
                                                                    









                                                                                 
                                          





                                                                                                      
                                                  



                                                                                          
                                                         






















                                                                                                    
                                                                   


                                   





                                                                   




                                                                                           
















                                                                       
                                                                                       




                                                                                                 











                                                                                                  






























                                                                                                       












                                                                                       









                                                           
                                             























                                                                                     
                                                             







                                                                               
                                                              
                                                                                          

                                                                                                                 

























































                                                                                                                    
                                                                                                                                              





                                                                                                                                 








                                                                            













                                                                                                                                












                                                                                         

















                                                                                                                                                 
                                                                                                                     



                                                                          

                                                                                    


                                                                       
                                                                                                                                                  

                                                           

         
package org.openslx.taskmanager.api;

import java.util.UUID;

import org.apache.log4j.Logger;
import org.openslx.taskmanager.api.TaskStatus.StatusCode;

import com.google.gson.annotations.Expose;

public abstract class AbstractTask implements Runnable
{

	private static final long RELEASE_DELAY = 10l * 60l * 1000l;
	private static final Logger LOG = Logger.getLogger( AbstractTask.class );

	/*
	 * To be set from task invocation (json data)
	 */

	/**
	 * The id of the task instance.
	 */
	@Expose
	private volatile String id = null;
	/**
	 * Parent task. This task won't be started as long as the parent task currently
	 * waiting for execution or is being executed. Otherwise this task is available for execution.
	 * Note that MAX_INSTANCES is still being taken into account. Set to null to ignore.
	 */
	@Expose
	private volatile String parentTask = null;
	/**
	 * If the parent task failed to execute, don't run this task and fail immediately.
	 */
	@Expose
	private volatile boolean failOnParentFail = true;

	/*
	 * Variables we're working with - these should never be set from incoming (json) data
	 */

	/**
	 * Maximum age of a task, if it's not freed explicitly.
	 */
	private static final long MAX_TASK_AGE = 24l * 3600l * 1000l;
	/**
	 * timeMillis when this task will be removed. This will be set automatically to something
	 * reasonable like 24 hours, once the job has finished. This is to prevent clogging the task
	 * manager with finished jobs over time. Note that you can explicitly remove a job using the
	 * "release" command.
	 */
	private volatile long removalDeadline = System.currentTimeMillis() + MAX_TASK_AGE;
	/**
	 * True if init() has been called. Task will not start if this is false.
	 */
	private volatile boolean initDone = false;
	/**
	 * Status of Task
	 */
	private volatile TaskStatus status = TaskStatus.ts_waiting;
	/**
	 * Reference to parent task
	 */
	private volatile AbstractTask parent = null;
	/**
	 * Reference to "owner" so we can tell it to check for more
	 * work when we're done.
	 */
	private volatile FinishCallback finishCallback = null;
	
	/**
	 * Set to true as soon as we try to start this task using the thread pool executor.
	 */
	private volatile boolean triedToStart = false;

	/**
	 * Default constructor which should not be overridden
	 */
	public AbstractTask()
	{
		this.id = UUID.randomUUID().toString();
	}

	/*
	 * Overridable methods
	 */

	/**
	 * Initialize the task; method used by the {@link Taskmanager}.
	 * Put your own initialization code in initTask()
	 */
	public final boolean init( AbstractTask parent, FinishCallback finishCallback )
	{
		if ( this.initDone ) {
			LOG.fatal( "init() called twice on " + this.getClass().getSimpleName() );
			System.exit( 1 );
		}
		if ( this.parentTask != null && this.parentTask.isEmpty() )
			this.parentTask = null;
		if ( this.parentTask != null && parent == null ) {
			if ( this.failOnParentFail ) {
				this.status = new TaskStatus( StatusCode.PARENT_FAILED, this.id );
				return false;
			}
			this.parentTask = null;
		}
		if ( this.parentTask == null && parent != null )
			parent = null;
		this.finishCallback = finishCallback;
		this.parent = parent;
		this.status = new TaskStatus( StatusCode.TASK_WAITING, this.id );
		this.initDone = true;
		boolean ret;
		try {
			ret = this.initTask();
		} catch ( Throwable t ) {
			ret = false;
		}
		if ( !ret ) {
			this.status.statusCode = StatusCode.TASK_ERROR;
		}
		return ret;
	}

	/**
	 * Your own initialization code. This is run synchronously within the network
	 * handling thread, so do NOT put anything here that might take longer than a few milliseconds!
	 * You should usually only validate the input to the task here, and return false if it is
	 * invalid.
	 * 
	 * @return - true if the task should be executed by the scheduler (ie init was successful)
	 *         - false if init was not successful, or you don't need to do any more processing
	 */
	protected abstract boolean initTask();

	/**
	 * This is where you put your huge work stuff that takes ages.
	 */
	protected abstract boolean execute();

	/**
	 * This is called when a client requests the status of this task. In case you
	 * want to return complex structures like Lists, which are not thread safe, you
	 * might want to keep that list outside the status class you return, and only
	 * create a copy of it for the status class in this function.
	 * If you only return more or less atomic data, you don't need to override
	 * this function
	 */
	protected void updateStatus()
	{

	}

	/*
	 * Final methods
	 */

	/**
	 * Get id of parent task.
	 * 
	 * @return id of parent task, null if no parent set
	 * 
	 */
	public final String getParentTaskId()
	{
		return this.parentTask;
	}

	/**
	 * Set the custom status data object to be returned on status requests.
	 * For simple tasks it should be sufficient to set this once before executing
	 * the task starts, and then just update fields in that class while the
	 * task is running. For cases where you have complicated data structures or
	 * multiple values that need to stay in sync you could create a new instance
	 * every time, fill it with values, and then call this method.
	 * 
	 * @param obj the object containing you specific task data
	 */
	protected final void setStatusObject( Object obj )
	{
		status.setStatusObject( obj );
	}

	/**
	 * Get current status of task.
	 */
	public final TaskStatus getStatus()
	{
		if ( this.initDone && this.parent != null ) {
			final StatusCode parentStatus = parent.getStatusCode();
			switch ( parentStatus ) {
			case DUPLICATE_ID:
			case NO_SUCH_INSTANCE:
			case NO_SUCH_TASK:
			case NO_SUCH_CONSTRUCTOR:
			case PARENT_FAILED:
			case TASK_ERROR:
				if ( this.failOnParentFail ) {
					this.status.statusCode = StatusCode.PARENT_FAILED;
					LOG.debug( "Parent " + this.parentTask + " of " + this.id + " failed." );
				}
				this.parentTask = null;
				break;
			default:
				break;
			}
		}
		return this.status;
	}

	/**
	 * Get status code if task.
	 * 
	 * @return
	 */
	public final StatusCode getStatusCode()
	{
		return getStatus().getStatusCode();
	}

	/**
	 * Get id of task
	 */
	public final String getId()
	{
		return this.id;
	}

	/**
	 * Getter for failOnParentTask
	 */
	public final boolean getFailOnParentFail()
	{
		return this.failOnParentFail;
	}

	/**
	 * Release the task: If the task is still pending/running, it will be removed from the task
	 * manager as soon as it finishes. So you can't query for its result later. If the task has
	 * already finished (either successful or failed), it will be removed (almost) immediately.
	 */
	public final void release()
	{
		this.removalDeadline = Math.min( this.removalDeadline, System.currentTimeMillis() + RELEASE_DELAY );
	}

	/**
	 * Can this task be started? This checks all the conditions:
	 * - Has this task been initialized yet?
	 * - Is it actually waiting for execution?
	 * - Does it depend on a parent task?
	 * -- If so, did the parent finish?
	 * -- Or did it fail?
	 * --- If so, should this task only run if it didn't fail?
	 * 
	 * @return true iff this task can be started
	 */
	public final boolean canStart()
	{
		if ( this.triedToStart || !this.initDone || this.id == null || this.getStatus().getStatusCode() != StatusCode.TASK_WAITING ) {
			return false;
		}
		if ( this.parent == null )
			return true;
		return parent.getStatusCode() != StatusCode.TASK_WAITING && parent.getStatusCode() != StatusCode.TASK_PROCESSING;
	}
	
	/**
	 * Mark this task as being started to prevent a race condition where
	 * the task would be submitted to the thread pool more than once.
	 */
	public final void markAsStarting()
	{
		this.triedToStart = true;
	}

	/**
	 * Checks whether this task can be removed from the task manager.
	 * A task can be removed if it is not executing or waiting for execution, and
	 * its removal deadline has been reached.
	 * 
	 * @return true if it can be released
	 */
	public final boolean canBeReleased()
	{
		if ( this.status.statusCode == StatusCode.TASK_WAITING || this.status.statusCode == StatusCode.TASK_PROCESSING )
			return false;
		return this.removalDeadline != 0 && System.currentTimeMillis() > this.removalDeadline;
	}
	
	/**
	 * Check whether the given task is finished, i.e. either failed or succeeded,
	 * or couldn't be started because its parent failed,
	 * but is not running or still waiting for execution.
	 * 
	 * @return true if task finished running or was not able to start
	 */
	public final boolean isFinished()
	{
		StatusCode sc = getStatusCode();
		return sc != StatusCode.TASK_WAITING && sc != StatusCode.TASK_PROCESSING;
	}

	/**
	 * Execute the task, wrapped in some sanity checks.
	 */
	@Override
	public final void run()
	{
		synchronized ( this ) {
			if ( this.status.statusCode != StatusCode.TASK_WAITING )
				throw new RuntimeException( "Tried to launch task " + this.getClass().getSimpleName() + " twice!" );
			if ( !this.initDone )
				throw new RuntimeException( "Tried to launch " + this.getClass().getSimpleName() + " without initializing it!" );
			this.status.statusCode = StatusCode.TASK_PROCESSING;
		}
		boolean ret;
		try {
			ret = execute();
		} catch ( Throwable t ) {
			LOG.warn( "Task " + this.getClass().getSimpleName() + " failed with uncaught exception", t );
			ret = false;
		}
		if ( ret ) {
			this.status.statusCode = StatusCode.TASK_FINISHED;
		} else if ( this.status.statusCode == StatusCode.TASK_CANCELLING ) {
			this.status.statusCode = StatusCode.TASK_CANCELLED;
		} else {
			this.status.statusCode = StatusCode.TASK_ERROR;
		}
		LOG.debug( "Finished task " + this.getClass().getSimpleName() + ": " + this.status.statusCode.toString() + " (" + this.id + ")" );
		if ( this.finishCallback != null )
			this.finishCallback.taskFinished();
	}
}