azkaban.flow.ComposedExecutableFlow.java Source code

Java tutorial

Introduction

Here is the source code for azkaban.flow.ComposedExecutableFlow.java

Source

/*
 * 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));
            }
        }
    }
}