package org.oddjob.state;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
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.ArooaAttribute;
import org.oddjob.arooa.deploy.annotations.ArooaComponent;
import org.oddjob.arooa.deploy.annotations.ArooaHidden;
import org.oddjob.arooa.parsing.ArooaContext;
import org.oddjob.arooa.registry.ServiceFinder;
import org.oddjob.framework.AsyncExecutionSupport;
import org.oddjob.framework.StructuralJob;
/**
* @oddjob.description
*
* This job implements an if/then/else logic based on job state. This job can
* contain any number of child jobs. The first provides the state for
* the condition.
* If this state matches the given state, the second job is
* executed. If it doesn't, then the third job is executed, (if it exists).
* <p>
* The completion state is that of the then or else job. If either don't
* exist then the Job is flagged as complete.
* <p>
* If any more than three jobs are provided the extra jobs are ignored.
* <p>
* If the first job enters an ACTIVE state then condition will not be
* evaluated until the first job leaves the ACTIVE state. This job will
* not block while this is happening. The thread of execution will pass
* to its next sibling and this job will also enter the ACTIVE state.
*
* @oddjob.example
*
* If a file exists.
*
* {@oddjob.xml.resource org/oddjob/state/IfFileExistsExample.xml}
*
* @oddjob.example
*
* An example showing lots of if's. All these if's go to COMPLETE state
* when run.
*
* {@oddjob.xml.resource org/oddjob/state/if.xml}
*
* @oddjob.example
*
* Asynchronous evaluation. Only when the first job moves beyond it's ACTIVE
* state will the condition be evaluated and the then job (second job)
* be executed. The execution of the second job is also asynchronous.
*
* {@oddjob.xml.resource org/oddjob/state/IfJobAsyncThen.xml}
*
* @author Rob Gordon
*/
public class IfJob extends StructuralJob<Object>
implements Runnable, Stateful, Resetable, Structural, Stoppable {
private static final long serialVersionUID = 20050806;
/** The condition state. */
private StateCondition state = StateConditions.COMPLETE;
/** @oddjob.property
* @oddjob.description Used for an asynchronous evaluation of the if.
* @oddjob.required No. Will be provided by the framework.
*/
private volatile transient ExecutorService executorService;
/** Used to find Executor Service if none provided. */
private volatile transient ServiceFinder serviceFinder;
/** Support asynchronous ifs. */
private volatile transient AsyncExecutionSupport asyncSupport;
@Override
@ArooaHidden
public void setArooaContext(ArooaContext context) {
super.setArooaContext(context);
serviceFinder = context.getSession().getTools(
).getServiceHelper().serviceFinderFor(context);
}
/**
* Getter for state.
*
* @return The state.
*/
public StateCondition getState() {
return state;
}
/**
* @oddjob.property state
* @oddjob.description The state condition to check against.
* See the Oddjob User guide for a full list of state conditions.
* @oddjob.required No, defaults to COMPLETE.
*/
@ArooaAttribute
public void setState(StateCondition state) {
this.state = state;
}
/**
* @oddjob.property jobs
* @oddjob.description The child jobs.
* @oddjob.required At least one.
*/
@ArooaComponent
public void setJobs(int index, Object job) {
if (job == null) {
childHelper.removeChildAt(index);
}
else {
childHelper.insertChild(index, job);
}
}
@Override
protected StateOperator getInitialStateOp() {
return new StateOperator() {
public ParentState evaluate(State... states) {
if (states.length < 1) {
return ParentState.READY;
}
boolean then = state.test(states[0]);
if (then) {
if (states.length > 1) {
return new StandardParentStateConverter(
).toStructuralState(states[1]);
}
}
else {
if (states.length > 2) {
return new StandardParentStateConverter(
).toStructuralState(states[2]);
}
}
return ParentState.COMPLETE;
}
};
}
protected void execute() {
if (childHelper.size() < 1) {
return;
}
Object child = childHelper.getChildAt(0);
if (!(child instanceof Stateful)) {
logger().info("Child [" + child +
"] is not Stateful - ignoring.");
return;
}
if (!(child instanceof Runnable)) {
logger().info("Child [" + child +
"] is not Runnable - ignoring.");
return;
}
final Stateful depends = (Stateful) child;
final class ThenAction implements Runnable {
@Override
public void run() {
if (childHelper.size() < 2) {
logger().info("No job for then.");
return;
}
else {
logger().info("Running job for then.");
Runnable job = (Runnable) childHelper.getChildAt(1);
job.run();
}
}
}
class ElseAction implements Runnable {
@Override
public void run() {
if (childHelper.size() < 3) {
logger().info("No job for else.");
return;
}
else {
logger().info("Running job for else.");
Runnable job = (Runnable) childHelper.getChildAt(2);
job.run();
}
}
}
class AsyncAction implements Runnable {
@Override
public void run() {
asyncSupport = new AsyncExecutionSupport(new Runnable() {
@Override
public void run() {
stop = false;
IfJob.super.startChildStateReflector();
}
});
stateHandler().waitToWhen(new IsAnyState(), new Runnable() {
public void run() {
getStateChanger().setState(ParentState.ACTIVE);
}
});
depends.addStateListener(new StateListener() {
@Override
public void jobStateChange(StateEvent event) {
State dependsState = event.getState();
StateCondition condition = StateConditions.LIVE;
if (condition.test(dependsState)) {
return;
}
ExecutorService executorService =
ensureExecutorService();
if (!stop) {
if (IfJob.this.state.test(dependsState)) {
logger().info("State of [" + dependsState +
"], triggering 'then' action.");
asyncSupport.submitJob(executorService,
new ThenAction());
}
else {
logger().info("State of [" + dependsState +
"], triggering 'else' action.");
asyncSupport.submitJob(executorService,
new ElseAction());
}
}
depends.removeStateListener(this);
asyncSupport.startWatchingJobs();
}
});
}
}
final AtomicReference<Runnable> action =
new AtomicReference<Runnable>(new Runnable() {
@Override
public void run() {
State dependsState =
depends.lastStateEvent().getState();
if (state.test(dependsState)) {
new ThenAction().run();
}
else {
new ElseAction().run();
}
}
});
StateListener listenForActive = new StateListener() {
@Override
public void jobStateChange(StateEvent event) {
StateCondition condition = StateConditions.ACTIVE;
if (condition.test(event.getState())) {
logger().info("Setting asynchronous mode.");
action.set(new AsyncAction());
}
}
};
depends.addStateListener(listenForActive);
try {
((Runnable) depends).run();
}
finally {
depends.removeStateListener(listenForActive);
}
if (stop) {
stop = false;
return;
}
action.get().run();
}
@Override
protected void onStop() throws FailedToStopException {
super.onStop();
if (asyncSupport != null) {
asyncSupport.cancelAllPendingJobs();
}
}
@Override
protected void startChildStateReflector() {
// In asynchronous mode the child reflector is only started when all
// the jobs complete.
if (asyncSupport == null) {
super.startChildStateReflector();
}
}
protected ExecutorService ensureExecutorService() {
if (executorService == null) {
if (serviceFinder == null) {
throw new IllegalStateException(
"No Service Finder - Need to set An Arooa Context.");
}
executorService = serviceFinder.find(ExecutorService.class, null);
if (executorService == null) {
throw new IllegalStateException("No Executor Service");
}
}
return executorService;
}
public ExecutorService getExecutorService() {
return executorService;
}
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
}