Java tutorial
/* * Copyright 2014 Cask Data, 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 co.cask.tigon.internal.app.runtime; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import org.apache.twill.api.RunId; import org.apache.twill.common.Cancellable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; /** * */ public abstract class AbstractProgramController implements ProgramController { private static final Logger LOG = LoggerFactory.getLogger(ProgramController.class); private final AtomicReference<State> state; private final String programName; private final RunId runId; private final ConcurrentMap<ListenerCaller, Cancellable> listeners; private final Listener caller; protected AbstractProgramController(String programName, RunId runId) { this.state = new AtomicReference<State>(State.STARTING); this.programName = programName; this.runId = runId; this.listeners = Maps.newConcurrentMap(); this.caller = new MultiListenerCaller(); } @Override public RunId getRunId() { return runId; } @Override public final ListenableFuture<ProgramController> suspend() { if (!state.compareAndSet(State.ALIVE, State.SUSPENDING)) { return Futures .immediateFailedFuture(new IllegalStateException("Suspension not allowed").fillInStackTrace()); } final SettableFuture<ProgramController> result = SettableFuture.create(); executor(State.SUSPENDING).execute(new Runnable() { @Override public void run() { try { caller.suspending(); doSuspend(); state.set(State.SUSPENDED); result.set(AbstractProgramController.this); caller.suspended(); } catch (Throwable t) { error(t, result); } } }); return result; } @Override public final ListenableFuture<ProgramController> resume() { if (!state.compareAndSet(State.SUSPENDED, State.RESUMING)) { return Futures .immediateFailedFuture(new IllegalStateException("Resumption not allowed").fillInStackTrace()); } final SettableFuture<ProgramController> result = SettableFuture.create(); executor(State.RESUMING).execute(new Runnable() { @Override public void run() { try { caller.resuming(); doResume(); state.set(State.ALIVE); result.set(AbstractProgramController.this); caller.alive(); } catch (Throwable t) { error(t, result); } } }); return result; } @Override public final ListenableFuture<ProgramController> stop() { if (!state.compareAndSet(State.STARTING, State.STOPPING) && !state.compareAndSet(State.ALIVE, State.STOPPING) && !state.compareAndSet(State.SUSPENDED, State.STOPPING)) { return Futures .immediateFailedFuture(new IllegalStateException("Resumption not allowed").fillInStackTrace()); } final SettableFuture<ProgramController> result = SettableFuture.create(); executor(State.STOPPING).execute(new Runnable() { @Override public void run() { try { caller.stopping(); doStop(); state.set(State.STOPPED); result.set(AbstractProgramController.this); caller.stopped(); } catch (Throwable t) { error(t, result); } } }); return result; } @Override public final Cancellable addListener(Listener listener, Executor executor) { Preconditions.checkNotNull(listener, "Listener shouldn't be null."); Preconditions.checkNotNull(executor, "Executor shouldn't be null."); final ListenerCaller caller = new ListenerCaller(listener, executor); Cancellable cancellable = new Cancellable() { @Override public void cancel() { listeners.remove(caller); } }; Cancellable result = listeners.putIfAbsent(caller, cancellable); if (result != null) { return result; } caller.init(state.get()); return cancellable; } @Override public final ListenableFuture<ProgramController> command(final String name, final Object value) { final SettableFuture<ProgramController> result = SettableFuture.create(); executor("command").execute(new Runnable() { @Override public void run() { try { doCommand(name, value); result.set(AbstractProgramController.this); } catch (Throwable t) { error(t, result); } } }); return result; } @Override public final State getState() { return state.get(); } protected final void error(Throwable t) { error(t, null); } /** * Force this controller into error state. * @param t The */ protected final <V> void error(Throwable t, SettableFuture<V> future) { state.set(State.ERROR); if (future != null) { future.setException(t); } caller.error(t); } /** * Children call this method to signal the program is started. */ protected final void started() { if (!state.compareAndSet(State.STARTING, State.ALIVE)) { LOG.info("Program already started {} {}", programName, runId); return; } LOG.info("Program started: {} {}", programName, runId); executor(State.ALIVE).execute(new Runnable() { @Override public void run() { state.set(State.ALIVE); caller.alive(); } }); } /** * Creates a new executor that execute using new thread everytime. */ protected Executor executor(final String name) { return new Executor() { @Override public void execute(@Nonnull Runnable command) { Thread t = new Thread(command, programName + "-" + state); t.setDaemon(true); t.start(); } }; } protected abstract void doSuspend() throws Exception; protected abstract void doResume() throws Exception; protected abstract void doStop() throws Exception; protected abstract void doCommand(String name, Object value) throws Exception; private Executor executor(State state) { return executor(state.name()); } private final class MultiListenerCaller implements Listener { @Override public void init(State currentState) { for (ListenerCaller caller : listeners.keySet()) { caller.init(currentState); } } @Override public void suspending() { for (ListenerCaller caller : listeners.keySet()) { caller.suspending(); } } @Override public void suspended() { for (ListenerCaller caller : listeners.keySet()) { caller.suspended(); } } @Override public void resuming() { for (ListenerCaller caller : listeners.keySet()) { caller.resuming(); } } @Override public void alive() { for (ListenerCaller caller : listeners.keySet()) { caller.alive(); } } @Override public void stopping() { for (ListenerCaller caller : listeners.keySet()) { caller.stopping(); } } @Override public void stopped() { for (ListenerCaller caller : listeners.keySet()) { caller.stopped(); } } @Override public void error(Throwable cause) { for (ListenerCaller caller : listeners.keySet()) { caller.error(cause); } } } private static final class ListenerCaller implements Listener { private final Listener listener; private final Executor executor; private ListenerCaller(Listener listener, Executor executor) { this.listener = listener; this.executor = executor; } @Override public void init(final State currentState) { executor.execute(new Runnable() { @Override public void run() { try { listener.init(currentState); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void suspending() { executor.execute(new Runnable() { @Override public void run() { try { listener.suspending(); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void suspended() { executor.execute(new Runnable() { @Override public void run() { try { listener.suspended(); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void resuming() { executor.execute(new Runnable() { @Override public void run() { try { listener.resuming(); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void alive() { executor.execute(new Runnable() { @Override public void run() { try { listener.alive(); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void stopping() { executor.execute(new Runnable() { @Override public void run() { try { listener.stopping(); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void stopped() { executor.execute(new Runnable() { @Override public void run() { try { listener.stopped(); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public void error(final Throwable cause) { executor.execute(new Runnable() { @Override public void run() { try { listener.error(cause); } catch (Throwable t) { LOG.info(t.getMessage(), t); } } }); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } // Only compare with the listener ListenerCaller other = (ListenerCaller) o; return Objects.equal(listener, other.listener); } @Override public int hashCode() { return Objects.hashCode(listener); } } }