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 azkaban.common.utils.Props; import azkaban.jobs.Status; import org.joda.time.DateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A "composition" of executable flows. This is composition in the functional sense. * * That is, the composition of two functions f(x) and g(x) is equivalent to f(g(x)). * Similarly, the composition of two ExecutableFlows A and B will result in a dependency * graph A -> B, meaning that B will be executed and complete before A. * * If B fails, A never runs. * * You should never really have to create one of these directly. Try to use MultipleDependencyExecutableFlow * instead. */ public class ComposedExecutableFlow implements ExecutableFlow { private final Object sync = new Object(); private final String id; private final ExecutableFlow depender; private final ExecutableFlow dependee; private volatile DateTime startTime; private volatile DateTime endTime; private volatile Status jobState; private volatile Map<String, Throwable> exceptions = new HashMap<String, Throwable>(); private volatile List<FlowCallback> callbacksToCall = new ArrayList<FlowCallback>(); private volatile Props parentProps; public ComposedExecutableFlow(String id, ExecutableFlow depender, ExecutableFlow dependee) { this.id = id; this.depender = depender; this.dependee = dependee; final Status dependerState = depender.getStatus(); switch (dependerState) { case IGNORED: case READY: final Status dependeeState = dependee.getStatus(); switch (dependeeState) { case IGNORED: case READY: jobState = Status.READY; startTime = null; endTime = null; break; case RUNNING: jobState = Status.RUNNING; startTime = dependee.getStartTime(); endTime = null; parentProps = dependee.getParentProps(); dependee.execute(parentProps, new DependeeCallback()); break; case COMPLETED: case SUCCEEDED: jobState = Status.READY; startTime = dependee.getStartTime(); parentProps = dependee.getParentProps(); endTime = null; break; case FAILED: jobState = Status.FAILED; startTime = dependee.getStartTime(); endTime = dependee.getEndTime(); parentProps = dependee.getParentProps(); } break; case RUNNING: jobState = Status.RUNNING; startTime = dependee.getStartTime() == null ? depender.getStartTime() : dependee.getStartTime(); endTime = null; parentProps = dependee.getParentProps(); depender.execute(new Props(parentProps, dependee.getReturnProps()), new DependerCallback()); break; case COMPLETED: case SUCCEEDED: case FAILED: jobState = dependerState; startTime = dependee.getStartTime() == null ? depender.getStartTime() : dependee.getStartTime(); endTime = depender.getEndTime(); parentProps = dependee.getParentProps(); } } @Override public String getId() { return id; } @Override public String getName() { return depender.getName(); } @Override public void execute(Props parentProps, final 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; callbacksToCall.add(callback); break; case RUNNING: callbacksToCall.add(callback); return; case COMPLETED: case SUCCEEDED: callback.completed(Status.SUCCEEDED); return; case FAILED: callback.completed(Status.FAILED); default: return; } } if (startTime == null) { startTime = new DateTime(); } try { dependee.execute(parentProps, new DependeeCallback()); } catch (RuntimeException e) { final List<FlowCallback> callbacks; synchronized (sync) { jobState = Status.FAILED; callbacks = callbacksToCall; } callCallbacks(callbacks, Status.FAILED); throw e; } } @Override public boolean cancel() { final boolean dependerCanceled = depender.cancel(); final boolean dependeeCanceled = dependee.cancel(); return dependerCanceled && dependeeCanceled; } @Override public Status getStatus() { return jobState; } @Override public boolean reset() { boolean retVal; synchronized (sync) { switch (jobState) { case RUNNING: return false; default: jobState = Status.READY; retVal = depender.reset(); callbacksToCall = new ArrayList<FlowCallback>(); parentProps = null; startTime = null; endTime = null; exceptions.clear(); } } return retVal; } @Override public boolean markCompleted() { synchronized (sync) { switch (jobState) { case RUNNING: return false; default: jobState = Status.COMPLETED; parentProps = new Props(); } } return true; } @Override public boolean hasChildren() { return true; } @Override public List<ExecutableFlow> getChildren() { return Arrays.asList(dependee); } @Override public String toString() { return "ComposedExecutableFlow{" + "depender=" + depender + ", dependee=" + dependee + ", jobState=" + jobState + '}'; } @Override public DateTime getStartTime() { return startTime; } @Override public DateTime getEndTime() { return endTime; } @Override public Props getParentProps() { return parentProps; } @Override public Props getReturnProps() { return new Props(dependee.getReturnProps(), depender.getReturnProps()); } @Override public Map<String, Throwable> getExceptions() { return exceptions; } public ExecutableFlow getDepender() { return depender; } public ExecutableFlow getDependee() { return dependee; } private void callCallbacks(final List<FlowCallback> callbackList, final Status status) { if (endTime == null) { endTime = new DateTime(); } for (FlowCallback callback : callbackList) { try { callback.completed(status); } catch (RuntimeException t) { // TODO: Figure out how to use the logger to log that a callback threw an exception. } } } private class DependerCallback implements FlowCallback { @Override public void progressMade() { final List<FlowCallback> callbackList; synchronized (sync) { callbackList = callbacksToCall; } for (FlowCallback flowCallback : callbackList) { flowCallback.progressMade(); } } @Override public void completed(Status status) { final List<FlowCallback> callbackList; synchronized (sync) { jobState = status; exceptions.putAll(depender.getExceptions()); callbackList = callbacksToCall; } callCallbacks(callbackList, status); } } private class DependeeCallback implements FlowCallback { @Override public void progressMade() { final List<FlowCallback> callbackList; synchronized (sync) { callbackList = callbacksToCall; } for (FlowCallback flowCallback : callbackList) { flowCallback.progressMade(); } } @Override public void completed(Status status) { final List<FlowCallback> callbackList; switch (status) { case SUCCEEDED: synchronized (sync) { callbackList = callbacksToCall; } for (FlowCallback flowCallback : callbackList) { flowCallback.progressMade(); } depender.execute(new Props(parentProps, dependee.getReturnProps()), new DependerCallback()); break; case FAILED: synchronized (sync) { jobState = status; exceptions.putAll(dependee.getExceptions()); callbackList = callbacksToCall; } callCallbacks(callbackList, status); break; default: throw new IllegalStateException( String.format("Got unexpected status[%s] back in a callback.", status)); } } } }