package org.oddjob.state;
import java.io.Serializable;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.log4j.Logger;
import org.oddjob.Stateful;
/**
* Wait for a job to start Executing. This is a helper class for Structural
* Jobs to ensure that the child job has moved from the ready state before it
* starts reflecting child states. Without the wait it's very difficult to
* check tests because a late starting asynchronous job would cause this
* job to have states READY-EXECUTING-READY-ACTIVE as opposed to
* READY-EXECUTING-ACTIVE which is what is expected.
*
* @author rob
*
*/
public class AsynchJobWait implements Serializable {
private static final long serialVersionUID = 2015041600L;
private static final Logger logger =
Logger.getLogger(AsynchJobWait.class);
private volatile transient Thread thread;
private volatile transient boolean join;
public void setJoin(boolean join) {
this.join = join;
}
public boolean isJoin() {
return join;
}
/**
* Run the job watching state
*
* @param job
* @return Will return true if the job is asynchronous.
*
*/
public boolean runAndWaitWith(Runnable job) {
final BlockingQueue<State> states = new LinkedBlockingQueue<>();
StateListener listener = new StateListener() {
public void jobStateChange(StateEvent event) {
states.add(event.getState());
}
};
((Stateful) job).addStateListener(listener);
try {
((Runnable) job).run();
if (isJoin()) {
return new JoinStrategy(states).doWait();
}
else {
return new NonJoinStrategy(states).doWait();
}
}
finally {
((Stateful) job).removeStateListener(listener);
}
}
interface WaitStrategy {
boolean doWait();
}
class NonJoinStrategy implements WaitStrategy {
final BlockingQueue<State> states;
public NonJoinStrategy(BlockingQueue<State> states) {
this.states = states;
}
@Override
public boolean doWait() {
State now = states.remove();
// Was probably not reset. Pretend it's asynchronous.
if (!now.isReady()) {
return true;
}
thread = Thread.currentThread();
try {
now = states.take();
}
catch (InterruptedException e){
// An interrupted wait indicates it was asynchronous.
return true;
}
finally {
thread = null;
}
logger.debug("State received "+ now);
if (now.isDestroyed()) {
childDestroyed();
}
while (true) {
now = states.poll();
// Still Executing
if (now == null) {
return true;
}
if (StateConditions.LIVE.test(now)) {
return true;
}
if (now.isComplete()) {
return false;
}
}
}
}
class JoinStrategy implements WaitStrategy {
final BlockingQueue<State> states;
public JoinStrategy(BlockingQueue<State> states) {
this.states = states;
}
@Override
public boolean doWait() {
while (true) {
State now;
thread = Thread.currentThread();
try {
now = states.take();
}
catch (InterruptedException e){
return false;
}
finally {
thread = null;
}
logger.debug("State received "+ now);
if (now.isDestroyed()) {
childDestroyed();
}
if (StateConditions.FINISHED.test(now)) {
return false;
}
}
}
}
protected void childDestroyed() {
throw new IllegalStateException("Job Destroyed.");
}
public void stopWait() {
Thread thread = this.thread;
if (thread != null) {
thread.interrupt();
}
}
}