package org.oddjob.scheduling;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.concurrent.CountDownLatch;
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.Stoppable;
import org.oddjob.Structural;
import org.oddjob.arooa.life.ComponentPersistException;
import org.oddjob.framework.BasePrimary;
import org.oddjob.framework.ComponentBoundry;
import org.oddjob.framework.StopWait;
import org.oddjob.images.IconHelper;
import org.oddjob.images.StateIcons;
import org.oddjob.persist.Persistable;
import org.oddjob.scheduling.state.TimerState;
import org.oddjob.scheduling.state.TimerStateAdapter;
import org.oddjob.scheduling.state.TimerStateChanger;
import org.oddjob.scheduling.state.TimerStateHandler;
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.OrderedStateChanger;
import org.oddjob.state.StateChanger;
import org.oddjob.state.StateConditions;
import org.oddjob.state.StateEvent;
import org.oddjob.state.StateExchange;
import org.oddjob.state.StateOperator;
import org.oddjob.state.StructuralStateHelper;
import org.oddjob.structural.ChildHelper;
import org.oddjob.structural.StructuralListener;
/**
* Common functionality for jobs that schedule things.
*
* @author Rob Gordon
*/
public abstract class ScheduleBase extends BasePrimary
implements
Runnable, Stoppable, Serializable,
Resetable, Stateful, Structural, Forceable {
private static final long serialVersionUID = 2009031500L;
/** Fires state events. */
protected transient volatile TimerStateHandler stateHandler;
/** Used to notify clients of an icon change. */
private transient volatile IconHelper iconHelper;
/** Used to state change states and icons. */
private transient volatile TimerStateChanger stateChanger;
/** Track the child. */
protected transient volatile ChildHelper<Runnable> childHelper;
/** Track structural state changes. */
private transient volatile Stateful structuralState;
protected transient volatile StateExchange<TimerState> childStateReflector;
/** Stop flag. */
protected transient volatile boolean stop;
protected transient volatile CountDownLatch begun;
/**
* Default Constructor.
*/
public ScheduleBase() {
completeConstruction();
}
/**
* Common construction.
*/
private void completeConstruction() {
stateHandler = new TimerStateHandler(this);
childHelper = new ChildHelper<Runnable>(this);
structuralState = new TimerStateAdapter(
new StructuralStateHelper(childHelper, getStateOp()));
iconHelper = new IconHelper(this,
StateIcons.iconFor(stateHandler.getState()));
stateChanger = new TimerStateChanger(stateHandler, iconHelper,
new Persistable() {
@Override
public void persist() throws ComponentPersistException {
save();
}
});
childStateReflector = new StateExchange<TimerState>(structuralState,
new OrderedStateChanger<TimerState>(stateChanger, stateHandler));
}
@Override
protected TimerStateHandler stateHandler() {
return stateHandler;
}
@Override
protected IconHelper iconHelper() {
return iconHelper;
}
protected StateChanger<TimerState> getStateChanger() {
return stateChanger;
}
/**
* Sub classes provide the state operator that is used to calculate the subclasses
* completion state.
*
* @return The operator. Must not be null.
*/
abstract protected StateOperator getStateOp();
/**
* Sub classes must override this to submit the first execution.
*
* @throws ComponentPerisistException If the scheduled time can't be saved.
*/
abstract protected void begin() throws ComponentPersistException;
/**
* 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() {
stop = false;
childStateReflector.stop();
getStateChanger().setState(TimerState.STARTING);
}
})) {
return;
}
logger().info("Executing.");
try {
configure();
// Used to ensure consistent states.
begun = new CountDownLatch(1);
begin();
// rescheduling could already have set the state so only
// change it if we are still executing.
stateHandler.waitToWhen(StateConditions.EXECUTING,
new Runnable() {
@Override
public void run() {
getStateChanger().setState(TimerState.STARTED);
}
});
begun.countDown();
}
catch (final Throwable e) {
logger().warn("Job Exception:", e);
stateHandler.waitToWhen(new IsAnyState(), new Runnable() {
public void run() {
getStateChanger().setStateException(e);
}
});
}
}
finally {
ComponentBoundry.pop();
}
}
/**
* Implementation for a typical stop. Subclasses must implement
* Stoppable to take advantage of it.
* <p>
* This stop implementation doesn't check that the job is
* executing as stop messages must cascade down the hierarchy
* to manually started jobs.
* @throws FailedToStopException
*/
public final void stop() throws FailedToStopException {
stateHandler.assertAlive();
ComponentBoundry.push(loggerName(), this);
try {
final AtomicReference<String> lastIcon = new AtomicReference<String>();
if (stateHandler.waitToWhen(new IsStoppable(), new Runnable() {
@Override
public void run() {
logger().info("Stopping.");
stop = true;
stateHandler.wake();
lastIcon.set(iconHelper.currentId());
iconHelper.changeIcon(IconHelper.STOPPING);
}
})) {
// cancel future executions for timer. remove listener for trigger.
onStop();
// then stop children
try {
childHelper.stopChildren();
postStop();
new StopWait(this).run();
}
catch (FailedToStopException e) {
iconHelper.changeIcon(lastIcon.get());
logger().warn(e);
}
logger().info("Stopped.");
}
else {
childHelper.stopChildren();
}
}
finally {
ComponentBoundry.pop();
}
}
/**
* Subclasses can override to perform stopping operations.
*/
protected void onStop() {
}
/**
* Subclasses can override to perform actions once children have stopped.
*/
protected void postStop() {
}
/**
* 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.");
childStateReflector.stop();
childHelper.softResetChildren();
onReset();
getStateChanger().setState(TimerState.STARTABLE);
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.");
childStateReflector.stop();
childHelper.hardResetChildren();
onReset();
getStateChanger().setState(TimerState.STARTABLE);
logger().info("Hard Reset complete.");
}
});
} finally {
ComponentBoundry.pop();
}
}
/**
* Override by subclasses to reset state.
*/
protected void onReset() {
}
/*
* (non-Javadoc)
* @see org.oddjob.Forceable#force()
*/
@Override
public void force() {
ComponentBoundry.push(loggerName(), this);
try {
stateHandler.waitToWhen(new IsForceable(), new Runnable() {
public void run() {
logger().info("Forcing complete.");
getStateChanger().setState(TimerState.COMPLETE);
}
});
}
finally {
ComponentBoundry.pop();
}
}
/**
* Add a listener. The listener will immediately recieve 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 listner.
*/
public void removeStructuralListener(StructuralListener listener) {
childHelper.removeStructuralListener(listener);
}
/**
* 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()));
}
@Override
protected void onDestroy() {
stateHandler.assertAlive();
super.onDestroy();
ComponentBoundry.push(loggerName(), this);
try {
logger().info("Destroying.");
stateHandler.waitToWhen(new IsAnyState(), new Runnable() {
public void run() {
childStateReflector.stop();
stop = true;
if (stateHandler.getState().isStoppable()) {
onStop();
stateChanger.setState(TimerState.STARTABLE);
}
}
});
}
finally {
ComponentBoundry.pop();
}
}
/**
* Internal method to fire state.
*/
protected void fireDestroyedState() {
if (!stateHandler().waitToWhen(new IsAnyState(), new Runnable() {
public void run() {
stateHandler().setState(TimerState.DESTROYED);
stateHandler().fireEvent();
}
})) {
throw new IllegalStateException("[" + ScheduleBase.this + "] Failed set state DESTROYED");
}
logger().debug("[" + this + "] Destroyed.");
}
}