package org.oddjob.framework; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.oddjob.FailedToStopException; import org.oddjob.Forceable; import org.oddjob.Resetable; import org.oddjob.Stateful; import org.oddjob.arooa.life.ComponentPersistException; import org.oddjob.images.IconHelper; import org.oddjob.images.StateIcons; import org.oddjob.persist.Persistable; import org.oddjob.state.IsAnyState; import org.oddjob.state.IsExecutable; import org.oddjob.state.IsForceable; import org.oddjob.state.IsHardResetable; import org.oddjob.state.IsSoftResetable; import org.oddjob.state.IsStoppable; import org.oddjob.state.JobState; import org.oddjob.state.JobStateChanger; import org.oddjob.state.JobStateHandler; import org.oddjob.state.StateChanger; /** * An abstract implementation of a job which provides common functionality to * concrete sub classes. * * @author Rob Gordon */ public abstract class SimpleJob extends BasePrimary implements Runnable, Resetable, Stateful, Forceable { /** Handle state. */ private final JobStateHandler stateHandler; /** Used to notify clients of an icon change. */ private final IconHelper iconHelper; /** Perform the state change. */ private final JobStateChanger stateChanger; /** * @oddjob.property * @oddjob.desction This flag is set by the stop method and should * be examined by any Stoppable jobs in their processing loops. * @oddjob.required Read Only. */ protected transient volatile boolean stop; protected SimpleJob() { stateHandler = new JobStateHandler(this); iconHelper = new IconHelper(this, StateIcons.iconFor(stateHandler.getState())); stateChanger = new JobStateChanger(stateHandler, iconHelper, new Persistable() { @Override public void persist() throws ComponentPersistException { save(); } }); } @Override protected JobStateHandler stateHandler() { return stateHandler; } @Override protected IconHelper iconHelper() { return iconHelper; } protected StateChanger<JobState> getStateChanger() { return stateChanger; } /** * Execute this job. * * @return 0 if the job is complete, anything else otherwise. * @throws Exception If the unexpected occurs. */ abstract protected int execute() throws Throwable; /** * Implement the main execute method for a job. This surrounds the * doExecute method of the sub class and sets state for the job. */ public final void run() { ComponentBoundry.push(loggerName(), this); try { if (!stateHandler.waitToWhen(new IsExecutable(), new Runnable() { public void run() { getStateChanger().setState(JobState.EXECUTING); } })) { return; } logger().info("Executing."); final AtomicInteger result = new AtomicInteger(); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); try { configure(); result.set(execute()); logger().info("Finished, result " + result.get()); } catch (Throwable e) { logger().error("Exception executing job.", e); exception.set(e); } stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { if (exception.get() != null) { getStateChanger().setStateException(exception.get()); } else if (result.get() == 0) { getStateChanger().setState(JobState.COMPLETE); } else { getStateChanger().setState(JobState.INCOMPLETE); } } }); } finally { ComponentBoundry.pop(); } } /** * Utility method to sleep a certain time. * * @param waitTime Milliseconds to sleep for. */ protected void sleep(final long waitTime) { stateHandler().assertAlive(); if (!stateHandler().waitToWhen(new IsStoppable(), new Runnable() { public void run() { if (stop) { logger().debug("Stop request detected. Not sleeping."); return; } logger().debug("Sleeping for " + ( waitTime == 0 ? "ever" : "[" + waitTime + "] milli seconds") + "."); iconHelper.changeIcon(IconHelper.SLEEPING); try { stateHandler().sleep(waitTime); } catch (InterruptedException e) { logger().debug("Sleep interupted."); Thread.currentThread().interrupt(); } // Stop should already have set Icon to Stopping. if (!stop) { iconHelper.changeIcon(IconHelper.EXECUTING); } } })) { throw new IllegalStateException("Can't sleep unless EXECUTING."); } } /** * Allow subclasses to indicate they are * stopping. The subclass must still implement * Stoppable. * * @throws FailedToStopException */ public final void stop() throws FailedToStopException { stateHandler.assertAlive(); ComponentBoundry.push(loggerName(), this); try { if (!stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { logger().info("Stopping."); stop = true; stateHandler.wake(); iconHelper.changeIcon(IconHelper.STOPPING); } })) { return; } FailedToStopException failedToStopException = null; try { onStop(); new StopWait(this).run(); logger().info("Stopped."); } catch (RuntimeException e) { failedToStopException = new FailedToStopException(this, e); } catch (FailedToStopException e) { failedToStopException = e; } if (failedToStopException != null) { stateHandler().waitToWhen(new IsStoppable(), new Runnable() { public void run() { iconHelper.changeIcon(IconHelper.EXECUTING); } }); throw failedToStopException; } } finally { ComponentBoundry.pop(); } } /** * Allow sub classes to do something on stop. */ protected void onStop() throws FailedToStopException { } /** * Getter for stop flag. * * @return */ public boolean isStop() { return stop; } /** * Perform a soft reset on the job. */ @Override public boolean softReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler.waitToWhen(new IsSoftResetable(), new Runnable() { public void run() { onReset(); getStateChanger().setState(JobState.READY); stop = false; logger().info("Soft Reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * Perform a hard reset on the job. */ @Override public boolean hardReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler.waitToWhen(new IsHardResetable(), new Runnable() { public void run() { onReset(); getStateChanger().setState(JobState.READY); stop = false; logger().info("Hard Reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * Allow sub classes to do something on reset. */ protected void onReset() { } /** * Force the job to COMPLETE. */ @Override public void force() { ComponentBoundry.push(loggerName(), this); try { stateHandler.waitToWhen(new IsForceable(), new Runnable() { public void run() { logger().info("Forcing complete."); getStateChanger().setState(JobState.COMPLETE); } }); } finally { ComponentBoundry.pop(); } } @Override protected void onDestroy() { super.onDestroy(); try { stop(); } catch (FailedToStopException e) { logger().warn(e); } } /** * Internal method to fire state. */ protected void fireDestroyedState() { if (!stateHandler().waitToWhen(new IsAnyState(), new Runnable() { public void run() { stateHandler().setState(JobState.DESTROYED); stateHandler().fireEvent(); } })) { throw new IllegalStateException("[" + SimpleJob.this + "] Failed set state DESTROYED"); } logger().debug("[" + this + "] Destroyed."); } }