package org.oddjob.state;
import org.oddjob.Resetable;
import org.oddjob.Stateful;
import org.oddjob.Stoppable;
import org.oddjob.arooa.ArooaConfigurationException;
import org.oddjob.arooa.deploy.annotations.ArooaAttribute;
import org.oddjob.arooa.life.ComponentPersistException;
import org.oddjob.framework.BasePrimary;
import org.oddjob.framework.ComponentBoundry;
import org.oddjob.framework.JobDestroyedException;
import org.oddjob.images.IconHelper;
import org.oddjob.images.StateIcons;
import org.oddjob.persist.Persistable;
/**
* @oddjob.description
*
* When run this job mirrors the state of the given job. It continues
* to do so until it's stopped.
*
* @author Rob Gordon
*/
public class MirrorState extends BasePrimary
implements Runnable, Stoppable, Resetable {
/** Handle state. */
private final JobStateHandler stateHandler;
/** Used to notify clients of an icon change. */
private final IconHelper iconHelper;
/** Used to change state. */
private final JobStateChanger stateChanger;
private volatile Stateful job;
private volatile StateListener listener;
public MirrorState() {
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;
}
/**
* @oddjob.property job
* @oddjob.description A reference to the job to mirror.
* @oddjob.required Yes.
*/
@ArooaAttribute
public synchronized void setJob(Stateful job) {
this.job = job;
}
synchronized public void run() {
ComponentBoundry.push(loggerName(), this);
try {
stateHandler.waitToWhen(new IsExecutable(), new Runnable() {
public void run() {
if (listener != null) {
// still mirroring.
return;
}
logger().info("Starting.");
try {
configure();
} catch (ArooaConfigurationException e) {
getStateChanger().setStateException(
e);
logger().error("Exception configuring.", e);
return;
}
if (job == null) {
getStateChanger().setStateException(
new NullPointerException("No Job."));
return;
}
logger().info("Starting to mirror [" + job + "]");
listener = new MirrorListener();
job.addStateListener(listener);
}
});
} finally {
ComponentBoundry.pop();
}
}
class MirrorListener implements StateListener {
@Override
public synchronized void jobStateChange(
final StateEvent event) {
logger().info("Mirroring [" + event.getState() + "], time [" +
event.getTime() + "]");
stateHandler.waitToWhen(new IsAnyState(),
new Runnable() {
public void run() {
State state = event.getState();
if (state.isDestroyed()) {
logger().info("Target Destroyed! Raising Exception.");
getStateChanger().setStateException(
new JobDestroyedException(job));
stop();
}
else {
if (state.isException()){
getStateChanger().setStateException(
event.getException(), event.getTime());
}
else {
getStateChanger().setState(
new JobStateConverter().toJobState(
state), event.getTime());
}
}
}
});
}
}
public synchronized void stop() {
ComponentBoundry.push(loggerName(), this);
try {
if (listener != null) {
job.removeStateListener(listener);
listener = null;
logger().info("Stopped mirroring [" + job + "]");
stateHandler.waitToWhen(new IsStoppable(),
new Runnable() {
public void run() {
getStateChanger().setState(JobState.READY);
}
});
job = null;
}
} finally {
ComponentBoundry.pop();
}
}
private synchronized boolean reset() {
stop();
return stateHandler.waitToWhen(new StateCondition() {
@Override
public boolean test(State state) {
return JobState.READY != state;
}
},
new Runnable() {
public void run() {
getStateChanger().setState(JobState.READY);
}
});
}
public boolean hardReset() {
return reset();
}
public boolean softReset() {
return reset();
}
@Override
public void onDestroy() {
stop();
super.onDestroy();
}
/**
* 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("[" + MirrorState.this + "] Failed to set state DESTROYED");
}
logger().debug("[" + this + "] Destroyed.");
}
}