/* * Copyright (c) 2005, Rob Gordon. */ package org.oddjob.framework; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.beanutils.DynaBean; import org.oddjob.FailedToStopException; import org.oddjob.Forceable; import org.oddjob.Resetable; import org.oddjob.Stateful; import org.oddjob.Stoppable; import org.oddjob.arooa.ArooaSession; 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.IsForceable; import org.oddjob.state.IsHardResetable; import org.oddjob.state.IsSoftResetable; import org.oddjob.state.IsStoppable; import org.oddjob.state.JobState; import org.oddjob.state.JobStateChanger; import org.oddjob.state.JobStateHandler; import org.oddjob.state.StateEvent; /** * Creates a proxy for any {@link java.lang.Runnable} to allow it to be controlled and * monitored within Oddjob. * * @author Rob Gordon. */ public class RunnableWrapper extends BaseWrapper implements ComponentWrapper, Serializable, Forceable { private static final long serialVersionUID = 20012052320051231L; /** Handle state. */ private transient volatile JobStateHandler stateHandler; /** Used to notify clients of an icon change. */ private transient volatile IconHelper iconHelper; /** Perform the state change. */ private transient volatile JobStateChanger stateChanger; /** The wrapped Runnable. */ private volatile Object wrapped; /** * The DynaBean that takes its properties of the wrapped Runnable. */ private transient volatile DynaBean dynaBean; /** The thread our job is executing on. */ private transient volatile Thread thread; /** * The proxy we create that represents our wrapped Runnable within Oddjob. */ private final Object proxy; /** Reset with annotations adaptor. */ private transient volatile Resetable resetableAdaptor; /** * Constructor. * */ public RunnableWrapper(Object wrapped, Object proxy) { this.wrapped = wrapped; this.proxy = proxy; completeConstruction(); } /** * Complete construction. Called by constructor and post * deserialisation. */ private void completeConstruction() { this.dynaBean = new WrapDynaBean(wrapped); stateHandler = new JobStateHandler((Stateful) proxy); iconHelper = new IconHelper(this, StateIcons.iconFor(stateHandler.getState())); stateChanger = new JobStateChanger(stateHandler, iconHelper, new Persistable() { @Override public void persist() throws ComponentPersistException { save(); } }); } @Override public void setArooaSession(ArooaSession session) { super.setArooaSession(session); resetableAdaptor = new ResetableAdaptorFactory().resetableFor( wrapped, session); } @Override protected IconHelper iconHelper() { return iconHelper; } @Override protected JobStateHandler stateHandler() { return stateHandler; } protected JobStateChanger getStateChanger() { return stateChanger; } @Override protected Object getWrapped() { return wrapped; } @Override protected DynaBean getDynaBean() { return dynaBean; } @Override protected Object getProxy() { return proxy; } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ @Override public void run() { ComponentBoundry.push(loggerName(), wrapped); try { if (!stateHandler.waitToWhen(new IsExecutable(), new Runnable() { public void run() { getStateChanger().setState(JobState.EXECUTING); } })) { return; } logger().info("Executing."); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); final AtomicReference<Object> callableResult = new AtomicReference<Object>(); thread = Thread.currentThread(); try { configure(); Object result; if (wrapped instanceof Callable<?>) { result = ((Callable<?>) wrapped).call(); } else { ((Runnable) wrapped).run(); result = null; } callableResult.set(result); } catch (Throwable t) { logger().error("Exception:", t); exception.set(t); } finally { stateHandler.callLocked(new Callable<Void>() { @Override public Void call() { if (Thread.interrupted()) { logger().debug("Clearing thread interrupted flag."); } thread = null; return null; } }); } logger().info("Finished."); stateHandler.waitToWhen(new IsStoppable(), new Runnable() { public void run() { if (exception.get() != null) { getStateChanger().setStateException(exception.get()); } else { int result; try { result = getResult(callableResult.get()); if (result == 0) { getStateChanger().setState(JobState.COMPLETE); } else { getStateChanger().setState(JobState.INCOMPLETE); } } catch (Exception e) { getStateChanger().setStateException(e); } } } }); } finally { ComponentBoundry.pop(); } } @Override public void onStop() throws FailedToStopException { if (wrapped instanceof Stoppable) { ((Stoppable) wrapped).stop(); } else { stateHandler.callLocked(new Callable<Void>() { @Override public Void call() { Thread t = thread; if (t != null){ logger().info("Interrupting Thread [" + t.getName() + "] to attempt to stop job."); t.interrupt(); } else { logger().info("No Thread to interrupt. Hopefully Job has just stopped."); } return null; } }); } } /** * Perform a soft reset on the job. */ @Override public boolean softReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler.waitToWhen(new IsSoftResetable(), new Runnable() { public void run() { if (resetableAdaptor == null) { throw new NullPointerException( "ResetableAdaptor hasn't been set, " + "setArooaSession() must be called on the proxy."); } resetableAdaptor.softReset(); getStateChanger().setState(JobState.READY); logger().info("Soft Reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * Perform a hard reset on the job. */ @Override public boolean hardReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler.waitToWhen(new IsHardResetable(), new Runnable() { public void run() { if (resetableAdaptor == null) { throw new NullPointerException( "ResetableAdaptor hasn't been set, " + "setArooaSession() must be called on the proxy."); } resetableAdaptor.hardReset(); getStateChanger().setState(JobState.READY); logger().info("Hard Reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * Force the job to COMPLETE. */ @Override public void force() { ComponentBoundry.push(loggerName(), this); try { stateHandler.waitToWhen(new IsForceable(), new Runnable() { public void run() { logger().info("Forcing complete."); getStateChanger().setState(JobState.COMPLETE); } }); } finally { ComponentBoundry.pop(); } } /** * Custom serialisation. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(stateHandler.lastStateEvent().serializable()); } /** * Custom serialisation. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); StateEvent.SerializableNoSource savedEvent = (StateEvent.SerializableNoSource) s.readObject(); completeConstruction(); stateHandler.restoreLastJobStateEvent(savedEvent); iconHelper.changeIcon(StateIcons.iconFor(stateHandler.getState())); } /** * 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("[" + RunnableWrapper.this + "] Failed set state DESTROYED"); } logger().debug("[" + this + "] Destroyed."); } }