summaryrefslogtreecommitdiffstats
path: root/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java')
-rw-r--r--api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java284
1 files changed, 284 insertions, 0 deletions
diff --git a/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java b/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java
new file mode 100644
index 0000000..cc837ba
--- /dev/null
+++ b/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java
@@ -0,0 +1,284 @@
+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 = 5l * 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 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 String parentTask = null;
+ /**
+ * If the parent task failed to execute, don't run this task and fail immediately.
+ */
+ @Expose
+ private 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 TaskStatus status = TaskStatus.ts_waiting;
+ /**
+ * Reference to parent task
+ */
+ private AbstractTask parent = null;
+
+ /**
+ * 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 )
+ {
+ if ( this.initDone ) {
+ LOG.fatal( "init() called twice on " + this.getClass().getSimpleName() );
+ System.exit( 1 );
+ }
+ 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();
+
+ /*
+ * Final methods
+ */
+
+ /**
+ * Get id of parent task.
+ *
+ * @return id of parent task, null if no parent set
+ *
+ */
+ public 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.parentTask != 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;
+ 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.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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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.toString() );
+ ret = false;
+ }
+ if ( ret ) {
+ this.status.statusCode = StatusCode.TASK_FINISHED;
+ } else {
+ this.status.statusCode = StatusCode.TASK_ERROR;
+ }
+ }
+
+ /**
+ * 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()
+ {
+
+ }
+}