package org.oddjob.jobs; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; import org.oddjob.FailedToStopException; import org.oddjob.Resetable; import org.oddjob.Stateful; import org.oddjob.Stoppable; import org.oddjob.Structural; import org.oddjob.arooa.deploy.annotations.ArooaComponent; import org.oddjob.arooa.life.ComponentPersistException; import org.oddjob.framework.BasePrimary; import org.oddjob.framework.ComponentBoundry; import org.oddjob.framework.JobDestroyedException; import org.oddjob.framework.StopWait; import org.oddjob.images.IconHelper; import org.oddjob.images.StateIcons; import org.oddjob.persist.Persistable; import org.oddjob.scheduling.Keeper; import org.oddjob.scheduling.LoosingOutcome; import org.oddjob.scheduling.Outcome; import org.oddjob.scheduling.WinningOutcome; import org.oddjob.state.IsAnyState; import org.oddjob.state.IsExecutable; 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.JobStateConverter; import org.oddjob.state.JobStateHandler; import org.oddjob.state.OrderedStateChanger; import org.oddjob.state.State; import org.oddjob.state.StateChanger; import org.oddjob.state.StateCondition; import org.oddjob.state.StateConditions; import org.oddjob.state.StateEvent; import org.oddjob.state.StateListener; import org.oddjob.structural.ChildHelper; import org.oddjob.structural.StructuralListener; /** * @oddjob.description Grab work to do. By competing for work with * other Grabbers this job facilitates distribution of work between * multiple Oddjob processes. * <p> * * @oddjob.example * * See the user guide. * * @author rob * */ public class GrabJob extends BasePrimary implements Runnable, Serializable, Stoppable, Resetable, Stateful, Structural { private static final long serialVersionUID = 2010031800L; /** Handle state. */ private transient volatile JobStateHandler stateHandler; /** Used to notify clients of an icon change. */ private transient volatile IconHelper iconHelper; /** Used for state changes. */ private transient volatile JobStateChanger stateChanger; /** stop flag. */ protected transient volatile boolean stop; /** * Actions on loosing. */ public enum LoosingAction { COMPLETE, INCOMPLETE, WAIT, } /** * @oddjob.property * @oddjob.description The action on loosing. Available actions are: * <dl> * <dt>COMPLETE</dt> * <dd>Set the job state to COMPLETE.</dd> * <dt>INCOMPLETE</dt> * <dd>Set the job state to INCOMPLETE.</dd> * <dt>WAIT</dt> * <dd>Wait until the job completes.</dd> * @oddjob.required No, Defaults to COMPLETE. */ private transient LoosingAction onLoosing; /** Track the child job. */ private transient ChildHelper<Runnable> childHelper; /** * @oddjob.property * @oddjob.description The keeper of work from which this job * attempts to grab work. * @oddjob.required Yes. */ private transient Keeper keeper; /** * @oddjob.property * @oddjob.description This job's identifier which is unique to * the Oddjob process, such as server name. * @oddjob.required Yes. */ private String identifier; /** * @oddjob.property * @oddjob.description The instance of identifier for a single grab. * This is an identifier for each run of the grab jobb and will be * something like the scheduled date/time. * @oddjob.required Yes. */ private Object instance; /** * @oddjob.property * @oddjob.description The identifier of the winner. Will be equal * to this jobs identifier if this job has won. * @oddjob.required R/O. */ private String winner; /** Listens to either the child or the keeper to update * this job's state. */ private transient GrabListener listener; /** * Constructor. */ public GrabJob() { completeConstruction(); } private void completeConstruction() { stateHandler = new JobStateHandler(this); childHelper = new ChildHelper<Runnable>(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; } /* * (non-Javadoc) * @see java.lang.Runnable#run() */ public final void run() { ComponentBoundry.push(loggerName(), this); try { if (!stateHandler.waitToWhen(new IsExecutable(), new Runnable() { public void run() { if (listener != null) { listener.stop(); } getStateChanger().setState(JobState.EXECUTING); } })) { return; } logger().info("Executing."); try { configure(); execute(); } catch (final Throwable e) { logger().error("Job Exception.", e); stateHandler.waitToWhen(new IsAnyState(), new Runnable() { public void run() { getStateChanger().setStateException(e); } }); } logger().info("Execution finished."); } finally { ComponentBoundry.pop(); } } /** * Do the actual executing. */ private void execute() { Runnable childJob = childHelper.getChild(); if (childJob == null) { throw new NullPointerException("No child job."); } Stateful statefulChild = (Stateful) childJob; if (keeper == null) { throw new NullPointerException("No Keeper."); } final Outcome outcome = keeper.grab(identifier, instance); winner = outcome.getWinner(); if (outcome.isWon()) { listener = new ChildWatcher(statefulChild, (WinningOutcome) outcome); childJob.run(); } else { LoosingAction loosingAction = this.onLoosing; if (loosingAction == null) { loosingAction = LoosingAction.COMPLETE; } switch (loosingAction) { case COMPLETE: stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { getStateChanger().setState(JobState.COMPLETE); } }); break; case INCOMPLETE: stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { getStateChanger().setState(JobState.INCOMPLETE); } }); break; case WAIT: listener = new StandBackAndWatch((LoosingOutcome) outcome); break; default: throw new IllegalStateException("Unexpected Action!"); } } } /** * Watch the keeper. */ class StandBackAndWatch implements StateListener, GrabListener { private final LoosingOutcome outcome; StandBackAndWatch(LoosingOutcome outcome) { this.outcome = outcome; outcome.addStateListener(this); } @Override public void jobStateChange(StateEvent event) { final State state = event.getState(); // Should we use ENDED? StateCondition finishedCondition = StateConditions.FINISHED; if (finishedCondition.test(state)) { outcome.removeStateListener(this); listener = null; stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { getStateChanger().setState( new JobStateConverter().toJobState(state)); } }); } } synchronized public void stop() { stopListening(); stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { getStateChanger().setState(JobState.INCOMPLETE); } }); } @Override public void stopListening() { outcome.removeStateListener(this); listener = null; } } interface GrabListener { public void stop(); public void stopListening(); } /** * Watch the child. * */ class ChildWatcher implements GrabListener, StateListener { private final StateChanger<JobState> stateChanger = new OrderedStateChanger<JobState>(getStateChanger(), stateHandler); private final Stateful child; private final WinningOutcome outcome; ChildWatcher(Stateful child, WinningOutcome outcome) { this.child = child; this.outcome = outcome; child.addStateListener(this); } @Override public void jobStateChange(StateEvent event) { State state = event.getState(); Date time = event.getTime(); if (state.isReady()) { stateChanger.setState(JobState.READY, time); checkStop(); } else if (state.isStoppable()) { stateChanger.setState(JobState.EXECUTING, time); } else if (state.isComplete()) { stateChanger.setState(JobState.COMPLETE, time); outcome.complete(); checkStop(); } else if (state.isIncomplete()) { stateChanger.setState(JobState.INCOMPLETE, time); checkStop(); } else if (state.isException()) { stateChanger.setStateException(event.getException(), time); checkStop(); } else { stateChanger.setStateException(new JobDestroyedException(child), time); checkStop(); } } /** * shared check to see if listener should remove itself. */ private void checkStop() { if (stop) { stopListening(); } } @Override public void stopListening() { child.removeStateListener(this); listener = null; } synchronized public void stop() { if (child instanceof GrabListener) { ((GrabListener) child).stop(); } } } @Override public void stop() throws FailedToStopException { stateHandler.assertAlive(); ComponentBoundry.push(loggerName(), this); try { logger().debug("Stop requested."); if (!stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { stop = true; } })) { logger().debug("Not in a stoppable state."); return; } logger().info("Stopping."); iconHelper.changeIcon(IconHelper.STOPPING); try { if (listener != null) { listener.stop(); listener = null; } childHelper.stopChildren(); new StopWait(this).run(); } catch (FailedToStopException e) { iconHelper.changeIcon(IconHelper.EXECUTING); throw e; } logger().info("Stopped."); } finally { ComponentBoundry.pop(); } } /** * Perform a soft reset on the job. */ public boolean softReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler.waitToWhen(new IsSoftResetable(), new Runnable() { public void run() { logger().debug("Propagating Soft Reset to children."); if (listener != null) { listener.stopListening(); } childHelper.softResetChildren(); reset(); getStateChanger().setState(JobState.READY); stop = false; logger().info("Soft reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * Perform a hard reset on the job. */ public boolean hardReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler.waitToWhen(new IsHardResetable(), new Runnable() { public void run() { logger().debug("Propagating Hard Reset to children."); if (listener != null) { listener.stopListening(); } childHelper.hardResetChildren(); reset(); getStateChanger().setState(JobState.READY); stop = false; logger().info("Hard reset complete."); } }); } finally { ComponentBoundry.pop(); } } private void reset() { stop = false; winner = null; } /** * Add a listener. The listener will immediately receive add * notifications for all existing children. * * @param listener The listener. */ public void addStructuralListener(StructuralListener listener) { stateHandler.assertAlive(); childHelper.addStructuralListener(listener); } /** * Remove a listener. * * @param listener The listener. */ public void removeStructuralListener(StructuralListener listener) { childHelper.removeStructuralListener(listener); } /** * The child. * * @oddjob.property job * @oddjob.description The child job. * @oddjob.required No, but pointless if missing. * * @param job A child */ @ArooaComponent public void setJob(Runnable job) { if (job == null) { childHelper.removeChildAt(0); } else { childHelper.insertChild(0, job); } } public void setKeeper(Keeper keeper) { this.keeper = keeper; } public String getIdentifier() { return identifier; } public void setIdentifier(String identifier) { this.identifier = identifier; } public Object getInstance() { return instance; } public void setInstance(Object instance) { this.instance = instance; } public String getWinner() { return winner; } public LoosingAction getOnLoosing() { return onLoosing; } public void setOnLoosing(LoosingAction loosingAction) { this.onLoosing = loosingAction; } /** * Custom serialisation. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(getName()); if (loggerName().startsWith(getClass().getName())) { s.writeObject(null); } else { s.writeObject(loggerName()); } s.writeObject(stateHandler.lastStateEvent().serializable()); } /** * Custom serialisation. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); String name = (String) s.readObject(); logger((String) s.readObject()); StateEvent.SerializableNoSource savedEvent = (StateEvent.SerializableNoSource) s.readObject(); completeConstruction(); setName(name); stateHandler.restoreLastJobStateEvent(savedEvent); iconHelper.changeIcon( StateIcons.iconFor(stateHandler.getState())); } /** * 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("[" + GrabJob.this + "] Failed set state DESTROYED"); } logger().debug("[" + this + "] Destroyed."); } }