package org.oddjob.state;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
import org.oddjob.Stateful;
import org.oddjob.arooa.deploy.annotations.ArooaAttribute;
import org.oddjob.arooa.deploy.annotations.ArooaComponent;
import org.oddjob.arooa.deploy.annotations.ArooaHidden;
import org.oddjob.framework.ExecutionWatcher;
import org.oddjob.framework.StructuralJob;
import org.oddjob.jobs.structural.ParallelJob;
import org.oddjob.jobs.structural.SequentialJob;
/**
* @oddjob.description
*
* A job which triggers the next job after the previous one completes.
* This job differs from a {@link SequentialJob} task in that the latter
* follows the thread of execution, and only checks state to ensure
* it can continue. This job's thread of execution passes onwards after the
* cascade has been set up. This job will complete asynchronously once all
* it's children have completed.
*
* <h4>State Operator</h4>
* This job doesn't currently expose a State Operator property as
* {@link SequentialJob} does. The state behaviour is equivalent to the
* WORST state operator, which is what is desired in most situations. A
* <code>stateOperator</code> property may be added in future versions
* if needed.
*
* @oddjob.example
*
* A cascade of two jobs.
*
* {@oddjob.xml.resource org/oddjob/state/CascadeExample.xml}
*
* @oddjob.example
*
* Showing cascade being used with {@link ParallelJob}. The cascade will
* wait for the parallel job to finish before running the third job.
*
* {@oddjob.xml.resource org/oddjob/state/CascadeWithParallelExample.xml}
*
* @author Rob Gordon
*/
public class CascadeJob extends StructuralJob<Object> {
private static final long serialVersionUID = 2010081100L;
/** The executor to use. */
private volatile transient ExecutorService executors;
/** The Future for the currently executing job so that it can be
* cancelled on stop. */
private transient volatile Future<?> future;
private volatile StateCondition cascadeOn;
private volatile StateCondition haltOn;
/*
* (non-Javadoc)
* @see org.oddjob.OddjobAware#setOddjobServices(org.oddjob.OddjobServices)
*/
@Inject
@ArooaHidden
public void setExecutorService(ExecutorService executor) {
this.executors = executor;
}
/**
* Add a child job.
*
* @oddjob.property <i>jobs</i>
* @oddjob.description The child jobs.
* @oddjob.required No, but pointless if missing.
*
* @param child A child
*/
@ArooaComponent
public synchronized void setJobs(int index, Object child) {
if (child == null) {
childHelper.removeChildAt(index);
}
else {
if (child instanceof Runnable ^ child instanceof Stateful) {
throw new IllegalArgumentException(
"If a job is Runnable or Stateful then it must be both.");
}
childHelper.insertChild(index, child);
}
}
/*
* (non-Javadoc)
* @see org.oddjob.jobs.AbstractJob#execute()
*/
protected void execute() throws InterruptedException {
if (executors == null) {
throw new NullPointerException("No Executor! Were services set?");
}
final StateCondition cascadeOn;
if (this.cascadeOn == null) {
cascadeOn = StateConditions.DONE;
}
else {
cascadeOn = this.cascadeOn;
}
final StateCondition haltOn;
if (this.haltOn == null) {
haltOn = StateConditions.FAILURE;
}
else {
haltOn = this.haltOn;
}
final Iterator<Object> children = childHelper.iterator();
final ExecutionWatcher executionWatcher =
new ExecutionWatcher(new Runnable() {
public void run() {
stop = false;
CascadeJob.super.startChildStateReflector();
}
});
// Used to flag the first execution. This is used
// to set the state to active.
final AtomicBoolean first = new AtomicBoolean(true);
// This runnable is run each time the state of the currently
// started child is complete.
new Runnable() {
@Override
public void run() {
// Find the next runnable, ignoring folders.
Runnable next = null;
while (children.hasNext()) {
Object child = children.next();
if (child instanceof Runnable) {
next = (Runnable) child;
break;
}
}
if (next == null || stop) {
executionWatcher.start();
return;
}
final Runnable _this = this;
((Stateful) next).addStateListener(new StateListener() {
public void jobStateChange(StateEvent event) {
State state = event.getState();
if (haltOn.test(state)) {
event.getSource().removeStateListener(this);
executionWatcher.start();
}
else if (cascadeOn.test(state)) {
event.getSource().removeStateListener(this);
_this.run();
}
}
});
Runnable wrapper = executionWatcher.addJob(next);
if (first.get()) {
first.set(false);
stateHandler().waitToWhen(new IsStoppable(), new Runnable() {
public void run() {
getStateChanger().setState(ParentState.ACTIVE);
}
});
}
future = executors.submit(wrapper);
}
}.run();
}
@Override
protected void onStop() {
Future<?> future = null;
synchronized (this) {
future = this.future;
this.future = null;
}
if (future != null) {
future.cancel(false);
}
super.startChildStateReflector();
}
@Override
protected StateOperator getInitialStateOp() {
return new WorstStateOp();
}
@Override
protected void startChildStateReflector() {
// This is started by us so override and do nothing.
}
public StateCondition getCascadeOn() {
return cascadeOn;
}
@ArooaAttribute
public void setCascadeOn(StateCondition cascadeOn) {
this.cascadeOn = cascadeOn;
}
public StateCondition getHaltOn() {
return haltOn;
}
@ArooaAttribute
public void setHaltOn(StateCondition haltOn) {
this.haltOn = haltOn;
}
}