package org.oddjob.framework;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
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.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.IsHardResetable;
import org.oddjob.state.IsSoftResetable;
import org.oddjob.state.IsStoppable;
import org.oddjob.state.OrderedStateChanger;
import org.oddjob.state.ParentState;
import org.oddjob.state.ParentStateChanger;
import org.oddjob.state.ParentStateHandler;
import org.oddjob.state.State;
import org.oddjob.state.StateChanger;
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;
/**
* An abstract implementation of a job which provides common functionality to
* concrete sub classes.
*
* @author Rob Gordon
*/
public abstract class StructuralJob<E> extends BasePrimary
implements
Runnable, Serializable,
Stoppable, Resetable, Stateful, Forceable, Structural {
private static final long serialVersionUID = 2009031500L;
/** Handle state. */
private transient volatile ParentStateHandler stateHandler;
/** Used to notify clients of an icon change. */
private transient volatile IconHelper iconHelper;
/** Track changes to children an notify listeners. */
protected transient volatile ChildHelper<E> childHelper;
/** Calculate our state based on children. */
protected transient volatile StructuralStateHelper structuralState;
/** Reflect state of children. */
protected transient volatile
StateExchange<ParentState> childStateReflector;
private transient volatile ParentStateChanger stateChanger;
/**
* @oddjob.property
* @oddjob.description Read only view of the internal stop flag.
* This flag is cleared with a reset.
* @oddjob.required Read only.
*/
protected transient volatile boolean stop;
protected transient volatile boolean destroy;
/**
* Constructor.
*/
public StructuralJob() {
completeConstruction();
}
private void completeConstruction() {
stateHandler = new ParentStateHandler(this);
childHelper = new ChildHelper<E>(this);
structuralState = new StructuralStateHelper(childHelper,
getInitialStateOp());
iconHelper = new IconHelper(this,
StateIcons.iconFor(stateHandler.getState()));
stateChanger = new ParentStateChanger(stateHandler, iconHelper,
new Persistable() {
@Override
public void persist() throws ComponentPersistException {
save();
}
});
childStateReflector = new StateExchange<ParentState>(structuralState,
new OrderedStateChanger<ParentState>(stateChanger, stateHandler));
}
@Override
protected ParentStateHandler stateHandler() {
return stateHandler;
}
@Override
protected IconHelper iconHelper() {
return iconHelper;
}
protected final StateChanger<ParentState> getStateChanger() {
return stateChanger;
}
/**
* Subclasses must provide the {@link StateOperator} that will decide
* how to evaluate the children's state.
*
* @return A State Operator. Must not be null.
*/
abstract protected StateOperator getInitialStateOp();
/**
* Execute this job.
*
* @throws Exception If the unexpected occurs.
*/
abstract protected void 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() {
// it's possible to reset children and then execute again so this
// is just in case there was no reset.
childStateReflector.stop();
getStateChanger().setState(ParentState.EXECUTING);
}
})) {
return;
}
logger().info("Executing.");
try {
if (!stop) {
configure();
}
if (!stop) {
execute();
}
// we ignore state while executing but now we need to update.
// dependent on our child states.
startChildStateReflector();
}
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();
}
}
/**
* Start the child state reflector. Sub classes override this if they
* need to start the child state reflector at a different time.
*/
protected void startChildStateReflector() {
if (!destroy) {
childStateReflector.start();
logger().debug("Child State Reflector Started.");
}
}
/**
* Implementation for a typical stop.
* <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 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);
}
})) {
// Order is here for SimultaneousStructural to cancel jobs first.
onStop();
childHelper.stopChildren();
new StopWait(this).run();
stop = false;
logger().info("Stopped.");
}
else {
childHelper.stopChildren();
}
}
finally {
ComponentBoundry.pop();
}
}
/**
* Allow sub classes to do something on stop.
*/
protected void onStop() throws FailedToStopException { }
/**
* 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();
stop = false;
onSoftReset();
getStateChanger().setState(ParentState.READY);
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();
stop = false;
onHardReset();
getStateChanger().setState(ParentState.READY);
logger().info("Hard Reset complete.");
}
});
} finally {
ComponentBoundry.pop();
}
}
/**
* Allow sub classes to do something on HARD reset.
*/
protected void onHardReset() {
onReset();
}
/**
* Allow sub classes to do something on SOFT reset.
*/
protected void onSoftReset() {
onReset();
}
/**
* 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 IsSoftResetable(), new Runnable() {
public void run() {
logger().info("Forcing complete.");
childStateReflector.stop();
getStateChanger().setState(ParentState.COMPLETE);
}
});
} finally {
ComponentBoundry.pop();
}
}
/**
* 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);
}
/**
* Expose the internal stop flag as a read only property.
*
* @return the stop flag.
*/
public boolean isStop() {
return stop;
}
/**
* 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() {
super.onDestroy();
logger().info("Destroying.");
stateHandler.waitToWhen(new IsAnyState(), new Runnable() {
public void run() {
destroy = true;
stop = true;
childStateReflector.stop();
stateHandler.wake();
State state = stateHandler.getState();
// This is here to allow Asynchronous jobs to cancel
// pending tasks.
if (state.isStoppable()) {
try {
onStop();
}
catch (FailedToStopException e) {
logger().warn("Failed to stop during destroy.", e);
}
}
}
});
}
/**
* Internal method to fire state.
*/
protected void fireDestroyedState() {
if (!stateHandler().waitToWhen(new IsAnyState(), new Runnable() {
public void run() {
stateHandler().setState(ParentState.DESTROYED);
stateHandler().fireEvent();
}
})) {
throw new IllegalStateException("[" + StructuralJob.this + "] Failed set state DESTROYED");
}
logger().debug("[" + this + "] Destroyed.");
}
}