Java tutorial
/* * Copyright 2010 LinkedIn, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package azkaban.flow; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import org.apache.log4j.Logger; import org.joda.time.DateTime; import azkaban.app.JobManager; import azkaban.common.jobs.DelegatingJob; import azkaban.common.jobs.Job; import azkaban.common.utils.Props; import azkaban.jobs.Status; import azkaban.monitor.MonitorImpl; import azkaban.monitor.MonitorInterface.JobState; import azkaban.monitor.MonitorInternalInterface.JobAction; /** * An implemention of the ExecutableFlow interface that just * wraps a single Job. */ public class IndividualJobExecutableFlow implements ExecutableFlow { private static final Logger logger = Logger.getLogger(IndividualJobExecutableFlow.class); private static final AtomicLong threadCounter = new AtomicLong(0); private final Object sync = new Object(); private final String id; private final String name; private final JobManager jobManager; private volatile Status jobState; private volatile List<FlowCallback> callbacksToCall; private volatile DateTime startTime; private volatile DateTime endTime; private volatile Job job; private volatile Map<String, Throwable> exceptions = new HashMap<String, Throwable>(); private volatile Props parentProps; private volatile Props returnProps; public IndividualJobExecutableFlow(String id, String name, JobManager jobManager) { this.id = id; this.name = name; this.jobManager = jobManager; resetState(); } @Override public String getId() { return id; } @Override public String getName() { return name; } @Override public Props getParentProps() { return parentProps; } @Override public Props getReturnProps() { return returnProps; } @Override public void execute(Props parentProps, FlowCallback callback) { if (parentProps == null) { parentProps = new Props(); } synchronized (sync) { if (this.parentProps == null) { this.parentProps = parentProps; } else if (jobState != Status.COMPLETED && !this.parentProps.equalsProps(parentProps)) { throw new IllegalArgumentException(String.format( "%s.execute() called with multiple differing parentProps objects. " + "Call reset() before executing again with a different Props object. this.parentProps[%s], parentProps[%s]", getClass().getSimpleName(), this.parentProps, parentProps)); } switch (jobState) { case READY: jobState = Status.RUNNING; startTime = new DateTime(); callbacksToCall.add(callback); break; case RUNNING: callbacksToCall.add(callback); return; case IGNORED: jobState = Status.COMPLETED; case COMPLETED: case SUCCEEDED: callback.completed(Status.SUCCEEDED); return; case FAILED: callback.completed(Status.FAILED); return; } } try { // Only one thread should ever be able to get to this point because of management of jobState // Thus, this should only ever get called once before the job finishes (at which point it could be reset) job = jobManager.loadJob(getName(), parentProps, true); } catch (Exception e) { logger.warn(String.format("Exception thrown while creating job[%s]", getName()), e); job = null; } if (job == null) { logger.warn(String.format( "Job[%s] doesn't exist, but was supposed to run. Perhaps someone changed the flow?", getName())); final List<FlowCallback> callbackList; synchronized (sync) { jobState = Status.FAILED; callbackList = callbacksToCall; // Get the reference before leaving the synchronized } callCallbacks(callbackList, jobState); return; } Thread theThread = new Thread(new Runnable() { @Override public void run() { final List<FlowCallback> callbackList; MonitorImpl.getInternalMonitorInterface().jobEvent(job, System.currentTimeMillis(), JobAction.START_WORKFLOW_JOB, JobState.NOP); try { job.run(); } catch (Exception e) { synchronized (sync) { jobState = Status.FAILED; returnProps = new Props(); exceptions.put(getName(), e); callbackList = callbacksToCall; // Get the reference before leaving the synchronized } MonitorImpl.getInternalMonitorInterface().jobEvent(job, System.currentTimeMillis(), JobAction.END_WORKFLOW_JOB, JobState.FAILED); callCallbacks(callbackList, jobState); throw new RuntimeException(e); } synchronized (sync) { jobState = Status.SUCCEEDED; returnProps = job.getJobGeneratedProperties(); callbackList = callbacksToCall; // Get the reference before leaving the synchronized } MonitorImpl.getInternalMonitorInterface().jobEvent(job, System.currentTimeMillis(), JobAction.END_WORKFLOW_JOB, JobState.SUCCESSFUL); returnProps.logProperties(String.format("Return props for job[%s]", getName())); callCallbacks(callbackList, jobState); } }, String.format("%s thread-%s", job.getId(), threadCounter.getAndIncrement())); Job currJob = job; while (true) { if (currJob instanceof DelegatingJob) { currJob = ((DelegatingJob) currJob).getInnerJob(); } else { break; } } theThread.start(); } @Override public boolean cancel() { final List<FlowCallback> callbacks; synchronized (sync) { switch (jobState) { case COMPLETED: case SUCCEEDED: case FAILED: case IGNORED: return true; default: jobState = Status.FAILED; callbacks = callbacksToCall; callbacksToCall = new ArrayList<FlowCallback>(); returnProps = new Props(); } } for (FlowCallback callback : callbacks) { callback.completed(Status.FAILED); } try { if (job != null) { job.cancel(); } return true; } catch (Exception e) { throw new RuntimeException(e); } } @Override public Status getStatus() { return jobState; } @Override public boolean reset() { synchronized (sync) { switch (jobState) { case RUNNING: return false; default: resetState(); } } return true; } @Override public boolean markCompleted() { synchronized (sync) { switch (jobState) { case RUNNING: return false; default: jobState = Status.COMPLETED; parentProps = new Props(); returnProps = new Props(); } } return true; } @Override public boolean hasChildren() { return false; } @Override public List<ExecutableFlow> getChildren() { return Collections.emptyList(); } @Override public String toString() { return "IndividualJobExecutableFlow{" + "job=" + job + ", jobState=" + jobState + '}'; } @Override public DateTime getStartTime() { return startTime; } @Override public DateTime getEndTime() { return endTime; } @Override public Map<String, Throwable> getExceptions() { return exceptions; } /** * This use to be package protected, but I've made it public to handle * a more robust case of disabled jobs. This will be replaced with the * new flow code soon. * * @param newStatus * @return */ public IndividualJobExecutableFlow setStatus(Status newStatus) { synchronized (sync) { switch (jobState) { case READY: jobState = newStatus; break; default: throw new IllegalStateException("Can only set status when job is in the READY state."); } } return this; } IndividualJobExecutableFlow setStartTime(DateTime startTime) { this.startTime = startTime; return this; } IndividualJobExecutableFlow setEndTime(DateTime endTime) { this.endTime = endTime; return this; } private void callCallbacks(final List<FlowCallback> callbackList, final Status status) { if (endTime == null) { endTime = new DateTime(); } Thread callbackThread = new Thread(new Runnable() { @Override public void run() { for (FlowCallback callback : callbackList) { try { callback.completed(status); } catch (RuntimeException t) { logger.error(String.format("Exception thrown while calling callback. job[%s]", getName()), t); } } } }, String.format("%s-callback", Thread.currentThread().getName())); callbackThread.start(); } IndividualJobExecutableFlow setParentProperties(Props parentProps) { synchronized (sync) { if (this.parentProps == null) { this.parentProps = parentProps; } else { throw new IllegalStateException("Attempt to override parent properties. " + "This method should only really be called from deserialization code"); } } return this; } private void resetState() { jobState = Status.READY; callbacksToCall = new ArrayList<FlowCallback>(); parentProps = null; returnProps = null; startTime = null; endTime = null; exceptions.clear(); } IndividualJobExecutableFlow setReturnProperties(Props returnProps) { synchronized (sync) { if (this.returnProps == null) { this.returnProps = returnProps; } else { throw new IllegalStateException("Attempt to override return properties. " + "This method should only really be called from deserialization code"); } } return this; } }