// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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 com.twitter.common.application.modules; import java.lang.annotation.Retention; import java.lang.annotation.Target; import com.google.common.annotations.VisibleForTesting; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.BindingAnnotation; import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.Singleton; import com.google.inject.multibindings.Multibinder; import com.twitter.common.application.Lifecycle; import com.twitter.common.application.ShutdownRegistry; import com.twitter.common.application.ShutdownRegistry.ShutdownRegistryImpl; import com.twitter.common.application.ShutdownStage; import com.twitter.common.application.StartupRegistry; import com.twitter.common.application.StartupStage; import com.twitter.common.application.modules.LocalServiceRegistry.LocalService; import com.twitter.common.base.Command; import com.twitter.common.base.ExceptionalCommand; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static com.google.common.base.Preconditions.checkNotNull; /** * Binding module for startup and shutdown controller and registries. * * Bindings provided by this module: * <ul> * <li>{@code @StartupStage ExceptionalCommand} - Command to execute all startup actions. * <li>{@code ShutdownRegistry} - Registry for adding shutdown actions. * <li>{@code @ShutdownStage Command} - Command to execute all shutdown commands. * </ul> * * If you would like to register a startup action that starts a local network service, please * consider using {@link LocalServiceRegistry}. * * @author William Farner */ public class LifecycleModule extends AbstractModule { /** * Binding annotation used for local services. * This is used to ensure the LocalService bindings are visibile within the package only, to * prevent injection inadvertently triggering a service launch. */ @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) @interface Service { } @Override protected void configure() { bind(Lifecycle.class).in(Singleton.class); bind(Key.get(ExceptionalCommand.class, StartupStage.class)).to(StartupRegistry.class); bind(StartupRegistry.class).in(Singleton.class); bind(ShutdownRegistry.class).to(ShutdownRegistryImpl.class); bind(Key.get(Command.class, ShutdownStage.class)).to(ShutdownRegistryImpl.class); bind(ShutdownRegistryImpl.class).in(Singleton.class); bindStartupAction(binder(), ShutdownHookRegistration.class); bind(LocalServiceRegistry.class).in(Singleton.class); // Ensure that there is at least an empty set for the service runners. runnerBinder(binder()); bindStartupAction(binder(), LocalServiceLauncher.class); } /** * Thrown when a local service fails to launch. */ public static class LaunchException extends Exception { public LaunchException(String msg) { super(msg); } public LaunchException(String msg, Throwable cause) { super(msg, cause); } } /** * Responsible for starting and stopping a local service. */ public interface ServiceRunner { /** * Launches the local service. * * @return Information about the launched service. * @throws LaunchException If the service failed to launch. */ LocalService launch() throws LaunchException; } @VisibleForTesting static Multibinder<ServiceRunner> runnerBinder(Binder binder) { return Multibinder.newSetBinder(binder, ServiceRunner.class, Service.class); } /** * Binds a service runner that will start and stop a local service. * * @param binder Binder to bind against. * @param launcher Launcher class for a service. */ public static void bindServiceRunner(Binder binder, Class<? extends ServiceRunner> launcher) { runnerBinder(binder).addBinding().to(launcher); binder.bind(launcher).in(Singleton.class); } /** * Binds a local service instance, without attaching an explicit lifecycle. * * @param binder Binder to bind against. * @param service Local service instance to bind. */ public static void bindLocalService(Binder binder, final LocalService service) { runnerBinder(binder).addBinding().toInstance( new ServiceRunner() { @Override public LocalService launch() { return service; } }); } /** * Adds a startup action to the startup registry binding. * * @param binder Binder to bind against. * @param actionClass Class to bind (and instantiate via guice) for execution at startup. */ public static void bindStartupAction(Binder binder, Class<? extends ExceptionalCommand> actionClass) { Multibinder.newSetBinder(binder, ExceptionalCommand.class, StartupStage.class) .addBinding().to(actionClass); } /** * Startup command to register the shutdown registry as a process shutdown hook. */ private static class ShutdownHookRegistration implements Command { private final Command shutdownCommand; @Inject ShutdownHookRegistration(@ShutdownStage Command shutdownCommand) { this.shutdownCommand = checkNotNull(shutdownCommand); } @Override public void execute() { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { shutdownCommand.execute(); } }, "ShutdownRegistry-Hook")); } } /** * Startup command that ensures startup and shutdown of local services. */ private static class LocalServiceLauncher implements Command { private final LocalServiceRegistry serviceRegistry; @Inject LocalServiceLauncher(LocalServiceRegistry serviceRegistry) { this.serviceRegistry = checkNotNull(serviceRegistry); } @Override public void execute() { serviceRegistry.ensureLaunched(); } } }