package org.oddjob.jobs.job; import org.oddjob.FailedToStopException; import org.oddjob.Stateful; import org.oddjob.Stoppable; import org.oddjob.Structural; import org.oddjob.arooa.deploy.annotations.ArooaAttribute; import org.oddjob.arooa.life.ArooaSessionAware; import org.oddjob.arooa.life.ComponentProxyResolver; import org.oddjob.framework.ComponentBoundry; import org.oddjob.framework.StructuralJob; import org.oddjob.state.AnyActiveStateOp; import org.oddjob.state.AsynchJobWait; import org.oddjob.state.DestroyHandlingStateOp; import org.oddjob.state.IsAnyState; import org.oddjob.state.IsHardResetable; import org.oddjob.state.IsSoftResetable; import org.oddjob.state.IsStoppable; import org.oddjob.state.ParentState; import org.oddjob.state.StateOperator; import org.oddjob.structural.StructuralListener; import org.oddjob.util.OddjobConfigException; /** * @oddjob.description A job which runs another job. The other job can be * local or on a server. * <p> * This job reflects the state of the job being executed. * <p> * @oddjob.example * * Examples elsewhere. * <ul> * <li>The {@link org.oddjob.jmx.JMXClientJob} job has an * example that uses <code>run</code> to run a job on a * remote server.</li> * </ul> * * * @author Rob Gordon */ public class RunJob extends StructuralJob<Object> implements Structural, Stoppable { private static final long serialVersionUID = 20050806201204300L; /** * @oddjob.property * @oddjob.description Job to run * @oddjob.required Yes. */ private volatile transient Object job; /** * @oddjob.property * @oddjob.description The reset level. See {@link ResetActions}. * @oddjob.required No, defaults to NONE. */ private volatile transient ResetAction reset; /** * @oddjob.property * @oddjob.description Wait for the target job to finish executing. * @oddjob.required No, defaults to false. */ private volatile boolean join; /** * @oddjob.property * @oddjob.description Add the target job as a child of this job. Allows * the target job to be easily monitored from a UI. * @oddjob.required No, defaults to false. */ private volatile boolean showJob; /** Helper to ensure consistent states. */ private volatile AsynchJobWait jobWait = new AsynchJobWait() { private static final long serialVersionUID = 2015041600L; protected void childDestroyed() { childHelper.removeAllChildren(); super.childDestroyed(); } }; /** * Our state operator must cope with a client node * that has been destroyed because the client has * been stopped. */ class BespokeStateOperator extends DestroyHandlingStateOp { public BespokeStateOperator(StateOperator delegate) { super(delegate); } @Override protected ParentState onDestroyed(int index) { ComponentBoundry.push(loggerName(), RunJob.this); try { childStateReflector.stop(); childHelper.removeAllChildren(); stateHandler().waitToWhen(new IsAnyState(), new Runnable() { @Override public void run() { if (stateHandler().lastStateEvent().getState().isStoppable()) { logger().info("Job Destroyed while active, setting state to COMPLETE"); getStateChanger().setStateException( new RuntimeException("Child Job has been destroyed.")); } else { logger().info("Job Destroyed, leaving job in previous state."); } } }); } finally { ComponentBoundry.pop(); } // This will be unsed as we've stopped the child state reflector. return ParentState.EXCEPTION; } }; /** * Set the stop node directly. * * @param node The job. */ @ArooaAttribute synchronized public void setJob(Object node) { this.job = node; } /** * Get the job. * * @return The node. */ synchronized public Object getJob() { return this.job; } /** * @oddjob.property stateOperator * @oddjob.description Set the way the children's state is * evaluated and reflected by the parent. Values can be WORST, * ACTIVE, or SERVICES. * @oddjob.required No, default is ACTIVE. * * @param stateOperator The state operator to be applied to children's * states to derive our state. */ @ArooaAttribute public void setStateOperator(StateOperator stateOperator) { this.structuralState.setStateOperator( new BespokeStateOperator(stateOperator)); } /** * Getter for State Operator. * * @return */ public StateOperator getStateOperator() { return this.structuralState.getStateOperator(); } @Override protected StateOperator getInitialStateOp() { return new BespokeStateOperator(new AnyActiveStateOp()); } /* * (non-Javadoc) * @see org.oddjob.jobs.AbstractJob#execute() */ protected void execute() throws Exception { if (job == null) { throw new OddjobConfigException("A job to start must be provided."); } Object proxy; if (childHelper.size() == 0) { ComponentProxyResolver resolver = getArooaSession().getComponentProxyResolver(); if (resolver == null) { throw new NullPointerException("No Component Proxy Resolver in Session."); } proxy = resolver.resolve(job, getArooaSession()); // if we created the proxy we need to set the session. // This is required for reset actions that might use descriptor // annotations. We don't set context and so don't have any // way to destroy the proxy. This might need to be addressed // at a later date. if (proxy != job && proxy instanceof ArooaSessionAware) { ((ArooaSessionAware) proxy).setArooaSession( getArooaSession()); } // OddjobComponentResolver should ensure this, so this is // just a sanity check. if (!(proxy instanceof Stateful)) { throw new IllegalStateException("Resolved Proxy is not Stateful."); } if (!(proxy instanceof Runnable)) { throw new IllegalStateException("Resolved Proxy is not Runnable."); } childHelper.addChild(proxy); } else { proxy = childHelper.getChild(); } ResetAction reset = this.reset; if (reset == null) { reset = ResetActions.NONE; } reset.doWith(proxy); jobWait.setJoin(isJoin()); boolean asynchronous = jobWait.runAndWaitWith((Runnable) proxy); // Ensure an asynchronous job always goes to active for the benefit // of consistent state transitions even if it is already complete. if (asynchronous) { stateHandler().waitToWhen(new IsStoppable(), new Runnable() { public void run() { getStateChanger().setState(ParentState.ACTIVE); } }); } } /** * Perform a soft reset on the job. */ public boolean softReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler().waitToWhen(new IsSoftResetable(), new Runnable() { public void run() { logger().debug("Propagating Soft Reset to children."); childStateReflector.stop(); childHelper.removeAllChildren(); stop = false; getStateChanger().setState(ParentState.READY); logger().info("Soft Reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * Perform a hard reset on the job. */ public boolean hardReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler().waitToWhen(new IsHardResetable(), new Runnable() { public void run() { logger().debug("Propagating Hard Reset to children."); childStateReflector.stop(); childHelper.removeAllChildren(); stop = false; getStateChanger().setState(ParentState.READY); logger().info("Hard Reset complete."); } }); } finally { ComponentBoundry.pop(); } } @Override protected void onStop() throws FailedToStopException { jobWait.stopWait(); } @Override public void addStructuralListener(StructuralListener listener) { if (isShowJob()) { super.addStructuralListener(listener); } } public ResetAction getReset() { return reset; } @ArooaAttribute public void setReset(ResetAction reset) { this.reset = reset; } public void setJoin(boolean join) { this.join = join; } public boolean isJoin() { return join; } public void setShowJob(boolean showJob) { this.showJob = showJob; } public boolean isShowJob() { return showJob; } }