You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by zm...@apache.org on 2015/08/26 23:00:21 UTC
[31/51] [partial] aurora git commit: Move packages from
com.twitter.common to org.apache.aurora.common
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java b/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java
deleted file mode 100644
index a199d1d..0000000
--- a/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/**
- * 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 com.twitter.common.zookeeper.testing;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.Arrays;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-
-import org.apache.zookeeper.server.NIOServerCnxn;
-import org.apache.zookeeper.server.ZooKeeperServer;
-import org.apache.zookeeper.server.ZooKeeperServer.BasicDataTreeBuilder;
-import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
-
-import com.twitter.common.application.ShutdownRegistry;
-import com.twitter.common.base.Command;
-import com.twitter.common.base.ExceptionalCommand;
-import com.twitter.common.io.FileUtils;
-import com.twitter.common.quantity.Amount;
-import com.twitter.common.quantity.Time;
-import com.twitter.common.zookeeper.ZooKeeperClient;
-import com.twitter.common.zookeeper.ZooKeeperClient.Credentials;
-
-/**
- * A helper class for starting in-process ZooKeeper server and clients.
- *
- * <p>This is ONLY meant to be used for testing.
- */
-public class ZooKeeperTestServer {
-
- /**
- * The default session timeout for clients created by servers constructed with
- * {@link #ZooKeeperTestServer(int, ShutdownRegistry)}.
- */
- public static final Amount<Integer, Time> DEFAULT_SESSION_TIMEOUT =
- Amount.of(100, Time.MILLISECONDS);
-
- protected final ZooKeeperServer zooKeeperServer;
- private final ShutdownRegistry shutdownRegistry;
- private NIOServerCnxn.Factory connectionFactory;
- private int port;
- private final Amount<Integer, Time> defaultSessionTimeout;
-
- /**
- * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port
- * @param shutdownRegistry a registry that will be used to register client and server shutdown
- * commands. It is up to the caller to execute the registered actions at an appropriate time.
- * @throws IOException if there was aproblem creating the server's database
- */
- public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry) throws IOException {
- this(port, shutdownRegistry, DEFAULT_SESSION_TIMEOUT);
- }
-
- /**
- * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port
- * @param shutdownRegistry a registry that will be used to register client and server shutdown
- * commands. It is up to the caller to execute the registered actions at an appropriate time.
- * @param defaultSessionTimeout the default session timeout for clients created with
- * {@link #createClient()}.
- * @throws IOException if there was aproblem creating the server's database
- */
- public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry,
- Amount<Integer, Time> defaultSessionTimeout) throws IOException {
- Preconditions.checkArgument(0 <= port && port <= 0xFFFF);
- this.port = port;
- this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
- this.defaultSessionTimeout = Preconditions.checkNotNull(defaultSessionTimeout);
-
- zooKeeperServer =
- new ZooKeeperServer(
- new FileTxnSnapLog(createTempDir(), createTempDir()),
- new BasicDataTreeBuilder()) {
-
- // TODO(John Sirois): Introduce a builder to configure the in-process server if and when
- // some folks need JMX for in-process tests.
- @Override protected void registerJMX() {
- // noop
- }
- };
- }
-
- /**
- * Starts zookeeper up on the configured port. If the configured port is the ephemeral port
- * (@{code 0}), then the actual chosen port is returned.
- */
- public final int startNetwork() throws IOException, InterruptedException {
- connectionFactory = new NIOServerCnxn.Factory(new InetSocketAddress(port));
- connectionFactory.startup(zooKeeperServer);
- shutdownRegistry.addAction(new Command() {
- @Override public void execute() {
- shutdownNetwork();
- }
- });
- port = zooKeeperServer.getClientPort();
- return port;
- }
-
- /**
- * Starts zookeeper back up on the last used port.
- */
- public final void restartNetwork() throws IOException, InterruptedException {
- checkEphemeralPortAssigned();
- Preconditions.checkState(!connectionFactory.isAlive());
- startNetwork();
- }
-
- /**
- * Shuts down the in-process zookeeper network server.
- */
- public final void shutdownNetwork() {
- if (connectionFactory != null && connectionFactory.isAlive()) {
- connectionFactory.shutdown();
- }
- }
-
- /**
- * Expires the active session for the given client. The client should be one returned from
- * {@link #createClient}.
- *
- * @param zkClient the client to expire
- * @throws ZooKeeperClient.ZooKeeperConnectionException if a problem is encountered connecting to
- * the local zk server while trying to expire the session
- * @throws InterruptedException if interrupted while requesting expiration
- */
- public final void expireClientSession(ZooKeeperClient zkClient)
- throws ZooKeeperClient.ZooKeeperConnectionException, InterruptedException {
- zooKeeperServer.closeSession(zkClient.get().getSessionId());
- }
-
- /**
- * Returns the current port to connect to the in-process zookeeper instance.
- */
- public final int getPort() {
- checkEphemeralPortAssigned();
- return port;
- }
-
- /**
- * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server
- * with the default session timeout.
- */
- public final ZooKeeperClient createClient() {
- return createClient(defaultSessionTimeout);
- }
-
- /**
- * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server
- * with the default session timeout and a custom {@code chrootPath}.
- */
- public final ZooKeeperClient createClient(String chrootPath) {
- return createClient(defaultSessionTimeout, Credentials.NONE, Optional.of(chrootPath));
- }
-
- /**
- * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with
- * the default session timeout.
- */
- public final ZooKeeperClient createClient(Credentials credentials) {
- return createClient(defaultSessionTimeout, credentials, Optional.<String>absent());
- }
-
- /**
- * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server
- * with a custom {@code sessionTimeout}.
- */
- public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout) {
- return createClient(sessionTimeout, Credentials.NONE, Optional.<String>absent());
- }
-
- /**
- * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with
- * a custom {@code sessionTimeout}.
- */
- public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout,
- Credentials credentials) {
- return createClient(sessionTimeout, credentials, Optional.<String>absent());
- }
-
- /**
- * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with
- * a custom {@code sessionTimeout} and a custom {@code chrootPath}.
- */
- public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout,
- Credentials credentials, Optional<String> chrootPath) {
- final ZooKeeperClient client = new ZooKeeperClient(sessionTimeout, credentials,
- chrootPath, Arrays.asList(InetSocketAddress.createUnresolved("127.0.0.1", port)));
- shutdownRegistry.addAction(new ExceptionalCommand<InterruptedException>() {
- @Override public void execute() {
- client.close();
- }
- });
- return client;
- }
-
- private void checkEphemeralPortAssigned() {
- Preconditions.checkState(port > 0, "startNetwork must be called first");
- }
-
- private File createTempDir() {
- final File tempDir = FileUtils.createTempDir();
- shutdownRegistry.addAction(new ExceptionalCommand<IOException>() {
- @Override public void execute() throws IOException {
- org.apache.commons.io.FileUtils.deleteDirectory(tempDir);
- }
- });
- return tempDir;
- }
-}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java b/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java
new file mode 100644
index 0000000..afb3691
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java
@@ -0,0 +1,29 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.util.Collections;
+
+import com.google.inject.Module;
+
+/**
+ * A base application class that provides empty implementations of all but the {@link #run()}
+ * method.
+ */
+public abstract class AbstractApplication implements Application {
+ @Override
+ public Iterable<? extends Module> getModules() {
+ return Collections.emptyList();
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java b/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java
new file mode 100644
index 0000000..c842f42
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java
@@ -0,0 +1,202 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Stage;
+import com.google.inject.util.Modules;
+
+import org.apache.aurora.common.application.modules.AppLauncherModule;
+import org.apache.aurora.common.application.modules.LifecycleModule;
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.ArgFilters;
+import org.apache.aurora.common.args.ArgScanner;
+import org.apache.aurora.common.args.ArgScanner.ArgScanException;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.args.constraints.NotNull;
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+/**
+ * An application launcher that sets up a framework for pluggable binding modules. This class
+ * should be called directly as the main class, with a command line argument {@code -app_class}
+ * which is the canonical class name of the application to execute.
+ *
+ * If your application uses command line arguments all {@link Arg} fields annotated with
+ * {@link CmdLine} will be discovered and command line arguments will be validated against this set,
+ * parsed and applied.
+ *
+ * A bootstrap module will be automatically applied ({@link AppLauncherModule}), which provides
+ * overridable default bindings for things like quit/abort hooks and a health check function.
+ * A {@link LifecycleModule} is also automatically applied to perform startup and shutdown
+ * actions.
+ */
+public final class AppLauncher {
+
+ private static final Logger LOG = Logger.getLogger(AppLauncher.class.getName());
+
+ private static final String APP_CLASS_NAME = "app_class";
+ @NotNull
+ @CmdLine(name = APP_CLASS_NAME,
+ help = "Fully-qualified name of the application class, which must implement Runnable.")
+ private static final Arg<Class<? extends Application>> APP_CLASS = Arg.create();
+
+ @CmdLine(name = "guice_stage",
+ help = "Guice development stage to create injector with.")
+ private static final Arg<Stage> GUICE_STAGE = Arg.create(Stage.DEVELOPMENT);
+
+ private static final Predicate<Field> SELECT_APP_CLASS =
+ ArgFilters.selectCmdLineArg(AppLauncher.class, APP_CLASS_NAME);
+
+ @Inject @StartupStage private ExceptionalCommand startupCommand;
+ @Inject private Lifecycle lifecycle;
+
+ private AppLauncher() {
+ // This should not be invoked directly.
+ }
+
+ private void run(Application application) {
+ try {
+ configureInjection(application);
+
+ LOG.info("Executing startup actions.");
+ // We're an app framework and this is the outer shell - it makes sense to handle all errors
+ // before exiting.
+ // SUPPRESS CHECKSTYLE:OFF IllegalCatch
+ try {
+ startupCommand.execute();
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, "Startup action failed, quitting.", e);
+ throw Throwables.propagate(e);
+ }
+ // SUPPRESS CHECKSTYLE:ON IllegalCatch
+
+ try {
+ application.run();
+ } finally {
+ LOG.info("Application run() exited.");
+ }
+ } finally {
+ if (lifecycle != null) {
+ lifecycle.shutdown();
+ }
+ }
+ }
+
+ private void configureInjection(Application application) {
+ Iterable<Module> modules = ImmutableList.<Module>builder()
+ .add(new LifecycleModule())
+ .add(new AppLauncherModule())
+ .addAll(application.getModules())
+ .build();
+
+ Injector injector = Guice.createInjector(GUICE_STAGE.get(), Modules.combine(modules));
+ injector.injectMembers(this);
+ injector.injectMembers(application);
+ }
+
+ public static void main(String... args) throws IllegalAccessException, InstantiationException {
+ // TODO(John Sirois): Support a META-INF/MANIFEST.MF App-Class attribute to allow java -jar
+ parseArgs(ArgFilters.SELECT_ALL, Arrays.asList(args));
+ new AppLauncher().run(APP_CLASS.get().newInstance());
+ }
+
+ /**
+ * A convenience for main wrappers. Equivalent to:
+ * <pre>
+ * AppLauncher.launch(appClass, ArgFilters.SELECT_ALL, Arrays.asList(args));
+ * </pre>
+ *
+ * @param appClass The application class to instantiate and launch.
+ * @param args The command line arguments to parse.
+ * @see ArgFilters
+ */
+ public static void launch(Class<? extends Application> appClass, String... args) {
+ launch(appClass, ArgFilters.SELECT_ALL, Arrays.asList(args));
+ }
+
+ /**
+ * A convenience for main wrappers. Equivalent to:
+ * <pre>
+ * AppLauncher.launch(appClass, argFilter, Arrays.asList(args));
+ * </pre>
+ *
+ * @param appClass The application class to instantiate and launch.
+ * @param argFilter A filter that selects the {@literal @CmdLine} {@link Arg}s to enable for
+ * parsing.
+ * @param args The command line arguments to parse.
+ * @see ArgFilters
+ */
+ public static void launch(Class<? extends Application> appClass, Predicate<Field> argFilter,
+ String... args) {
+ launch(appClass, argFilter, Arrays.asList(args));
+ }
+
+ /**
+ * Used to launch an application with a restricted set of {@literal @CmdLine} {@link Arg}s
+ * considered for parsing. This is useful if the classpath includes annotated fields you do not
+ * wish arguments to be parsed for.
+ *
+ * @param appClass The application class to instantiate and launch.
+ * @param argFilter A filter that selects the {@literal @CmdLine} {@link Arg}s to enable for
+ * parsing.
+ * @param args The command line arguments to parse.
+ * @see ArgFilters
+ */
+ public static void launch(Class<? extends Application> appClass, Predicate<Field> argFilter,
+ List<String> args) {
+ Preconditions.checkNotNull(appClass);
+ Preconditions.checkNotNull(argFilter);
+ Preconditions.checkNotNull(args);
+
+ parseArgs(Predicates.<Field>and(Predicates.not(SELECT_APP_CLASS), argFilter), args);
+ try {
+ new AppLauncher().run(appClass.newInstance());
+ } catch (InstantiationException e) {
+ throw new IllegalStateException(e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void parseArgs(Predicate<Field> filter, List<String> args) {
+ try {
+ if (!new ArgScanner().parse(filter, args)) {
+ System.exit(0);
+ }
+ } catch (ArgScanException e) {
+ exit("Failed to scan arguments", e);
+ } catch (IllegalArgumentException e) {
+ exit("Failed to apply arguments", e);
+ }
+ }
+
+ private static void exit(String message, Exception error) {
+ LOG.log(Level.SEVERE, message + "\n" + error, error);
+ System.exit(1);
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/Application.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/Application.java b/commons/src/main/java/org/apache/aurora/common/application/Application.java
new file mode 100644
index 0000000..ebe01e8
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/Application.java
@@ -0,0 +1,29 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import com.google.inject.Module;
+
+/**
+ * An application that supports a limited lifecycle and optional binding of guice modules.
+ */
+public interface Application extends Runnable {
+
+ /**
+ * Returns binding modules for the application.
+ *
+ * @return Application binding modules.
+ */
+ Iterable<? extends Module> getModules();
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java b/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java
new file mode 100644
index 0000000..5d22a2a
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java
@@ -0,0 +1,94 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+import org.apache.aurora.common.base.Command;
+
+/**
+ * Application lifecycle manager, which coordinates orderly shutdown of an application. This class
+ * is responsible for executing shutdown commands, and can also be used to allow threads to await
+ * application shutdown.
+ *
+ * @author William Farner
+ */
+public class Lifecycle {
+
+ private static final Logger LOG = Logger.getLogger(Lifecycle.class.getName());
+
+ // Monitor and state for suspending and terminating execution.
+ private final Object waitMonitor = new Object();
+ private boolean destroyed = false;
+
+ private final Command shutdownRegistry;
+
+ @Inject
+ public Lifecycle(@ShutdownStage Command shutdownRegistry,
+ UncaughtExceptionHandler exceptionHandler) {
+
+ this.shutdownRegistry = shutdownRegistry;
+ Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
+ }
+
+ /**
+ * Checks whether this lifecycle is still considered alive. The lifecycle is still alive until
+ * {@link #shutdown()} has been called and all of the actions registered with the shutdown
+ * controller have completed.
+ *
+ * @return {@code true} if the lifecycle is alive, {@code false} otherwise.
+ *
+ */
+ public final boolean isAlive() {
+ synchronized (waitMonitor) {
+ return !destroyed;
+ }
+ }
+
+ /**
+ * Allows a caller to wait forever; typically used when all work is done in daemon threads.
+ * Will exit on interrupts.
+ */
+ public final void awaitShutdown() {
+ LOG.info("Awaiting shutdown");
+ synchronized (waitMonitor) {
+ while (!destroyed) {
+ try {
+ waitMonitor.wait();
+ } catch (InterruptedException e) {
+ LOG.info("Exiting on interrupt");
+ shutdown();
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Initiates an orderly shutdown of the lifecycle's registered shutdown hooks.
+ */
+ public final void shutdown() {
+ synchronized (waitMonitor) {
+ if (!destroyed) {
+ destroyed = true;
+ LOG.info("Shutting down application");
+ shutdownRegistry.execute();
+ waitMonitor.notifyAll();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java
new file mode 100644
index 0000000..b440e7e
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java
@@ -0,0 +1,99 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+/**
+ * A shutdown action controller. It executes actions in the reverse order they were registered, and
+ * logs a warning for every shutdown action that fails, but doesn't prevent completion of subsequent
+ * actions or the normal completion of the {@code execute()} method.
+ *
+ * @author Attila Szegedi
+ */
+public interface ShutdownRegistry {
+
+ /**
+ * Adds an action to the shutdown registry.
+ *
+ * @param action Action to register.
+ * @param <E> Exception type thrown by the action.
+ * @param <T> Type of command.
+ */
+ <E extends Exception, T extends ExceptionalCommand<E>> void addAction(T action);
+
+ /**
+ * Implementation of a shutdown registry.
+ */
+ public static class ShutdownRegistryImpl implements ShutdownRegistry, Command {
+ private static final Logger LOG = Logger.getLogger(ShutdownRegistry.class.getName());
+
+ private final List<ExceptionalCommand<? extends Exception>> actions = Lists.newLinkedList();
+
+ private boolean completed = false;
+
+ /**
+ * Registers an action to execute during {@link #execute()}. It is an error to call this method
+ * after calling {@link #execute()}.
+ *
+ * @param action the action to add to the list of actions to execute during execution
+ */
+ @Override
+ public synchronized <E extends Exception, T extends ExceptionalCommand<E>> void addAction(
+ T action) {
+ Preconditions.checkState(!completed);
+ actions.add(action);
+ }
+
+ /**
+ * Executes an application shutdown stage by executing all registered actions. This method can
+ * be called multiple times but will only execute the registered actions the first time.
+ *
+ * This sends output to System.out because logging is unreliable during JVM shutdown, which
+ * this class may be used for.
+ */
+ @Override
+ public synchronized void execute() {
+ if (!completed) {
+ LOG.info(String.format("Executing %d shutdown commands.", actions.size()));
+ completed = true;
+ try {
+ for (ExceptionalCommand<? extends Exception> action : Lists.reverse(actions)) {
+ // Part of our contract is ensuring each shutdown action executes so we must catch all
+ // exceptions.
+ // SUPPRESS CHECKSTYLE:OFF IllegalCatch
+ try {
+ action.execute();
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "Shutdown action failed.", e);
+ }
+ // SUPPRESS CHECKSTYLE:ON IllegalCatch
+ }
+ } finally {
+ actions.clear();
+ }
+ } else {
+ LOG.info("Action controller has already completed, subsequent calls ignored.");
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java b/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java
new file mode 100644
index 0000000..95f9c58
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java
@@ -0,0 +1,31 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+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;
+
+/**
+ * Binding annotation used for the shutdown registry.
+ */
+@BindingAnnotation
+@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
+public @interface ShutdownStage { }
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java
new file mode 100644
index 0000000..997ee77
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java
@@ -0,0 +1,52 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+/**
+ * A registry that executes a set of commands. The registry will synchronously execute commands
+ * when {@link #execute()} is invoked, returning early if any action throws an exception.
+ * Only one call to {@link #execute()} will have an effect, all subsequent calls will be ignored.
+ */
+public class StartupRegistry implements ExceptionalCommand<Exception> {
+
+ private static final Logger LOG = Logger.getLogger(StartupRegistry.class.getName());
+
+ private final Set<ExceptionalCommand> startupActions;
+ private final AtomicBoolean started = new AtomicBoolean(false);
+
+ @Inject
+ public StartupRegistry(@StartupStage Set<ExceptionalCommand> startupActions) {
+ this.startupActions = Preconditions.checkNotNull(startupActions);
+ }
+
+ @Override
+ public void execute() throws Exception {
+ if (!started.compareAndSet(false, true)) {
+ LOG.warning("Startup actions cannot be executed more than once, ignoring.");
+ }
+
+ for (ExceptionalCommand<?> startupAction : startupActions) {
+ startupAction.execute();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java b/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java
new file mode 100644
index 0000000..8050901
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java
@@ -0,0 +1,31 @@
+/**
+ * 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 org.apache.aurora.common.application;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+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;
+
+/**
+ * Binding annotation used for the startup registry.
+ */
+@BindingAnnotation
+@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
+public @interface StartupStage { }
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java b/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java
new file mode 100644
index 0000000..6a48b84
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java
@@ -0,0 +1,43 @@
+/**
+ * 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 org.apache.aurora.common.application.http;
+
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+import org.apache.aurora.common.application.Lifecycle;
+
+/**
+ * The default quit handler to use, which invokes {@link Lifecycle#shutdown()}.
+ *
+ * @author William Farner
+ */
+public class DefaultQuitHandler implements Runnable {
+
+ private static final Logger LOG = Logger.getLogger(DefaultQuitHandler.class.getName());
+
+ private final Lifecycle lifecycle;
+
+ @Inject
+ public DefaultQuitHandler(Lifecycle lifecycle) {
+ this.lifecycle = lifecycle;
+ }
+
+ @Override
+ public void run() {
+ LOG.info("Instructing lifecycle to destroy.");
+ lifecycle.shutdown();
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java b/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java
new file mode 100644
index 0000000..f786f9e
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java
@@ -0,0 +1,50 @@
+/**
+ * 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 org.apache.aurora.common.application.http;
+
+import com.google.inject.Binder;
+
+/**
+ * A utility class to register the file resources for the graph viewer.
+ */
+public final class GraphViewer {
+
+ private GraphViewer() {
+ // Utility class.
+ }
+
+ private static void registerJs(Binder binder, String assetName) {
+ Registration.registerHttpAsset(
+ binder,
+ "/graphview/" + assetName,
+ GraphViewer.class,
+ "graphview/" + assetName,
+ "application/javascript",
+ true);
+ }
+
+ /**
+ * Registers required resources with the binder.
+ *
+ * @param binder Binder to register with.
+ */
+ public static void registerResources(Binder binder) {
+ registerJs(binder, "dygraph-combined.js");
+ registerJs(binder, "dygraph-extra.js");
+ registerJs(binder, "grapher.js");
+ registerJs(binder, "parser.js");
+ Registration.registerHttpAsset(binder,
+ "/graphview", GraphViewer.class, "graphview/graphview.html", "text/html", false);
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java
new file mode 100644
index 0000000..e2160e7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java
@@ -0,0 +1,51 @@
+/**
+ * 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 org.apache.aurora.common.application.http;
+
+import java.net.URL;
+
+import com.google.common.io.Resources;
+
+import org.apache.aurora.common.net.http.handlers.AssetHandler;
+import org.apache.aurora.common.net.http.handlers.AssetHandler.StaticAsset;
+
+import static org.apache.aurora.common.base.MorePreconditions.checkNotBlank;
+
+/**
+ * Configuration for a static HTTP-served asset.
+ *
+ * TODO(William Farner): Move this to a more appropriate package after initial AppLauncher check-in.
+ *
+ * @author William Farner
+ */
+public class HttpAssetConfig {
+ public final String path;
+ public final AssetHandler handler;
+ public final boolean silent;
+
+ /**
+ * Creates a new asset configuration.
+ *
+ * @param path HTTP path the asset should be accessible from.
+ * @param asset Asset resource URL.
+ * @param contentType HTTP content-type to report for the asset.
+ * @param silent Whether the asset should be visible on the default index page.
+ */
+ public HttpAssetConfig(String path, URL asset, String contentType, boolean silent) {
+ this.path = checkNotBlank(path);
+ this.handler = new AssetHandler(
+ new StaticAsset(Resources.newInputStreamSupplier(asset), contentType, true));
+ this.silent = silent;
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java
new file mode 100644
index 0000000..d096317
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java
@@ -0,0 +1,39 @@
+/**
+ * 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 org.apache.aurora.common.application.http;
+
+import javax.servlet.Filter;
+
+import org.apache.aurora.common.base.MorePreconditions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Configuration tuple for an HTTP filter.
+ */
+public class HttpFilterConfig {
+ public final Class<? extends Filter> filterClass;
+ public final String pathSpec;
+
+ /**
+ * Creates a new filter configuration.
+ *
+ * @param filterClass Filter class.
+ * @param pathSpec Path spec that the filter should match.
+ */
+ public HttpFilterConfig(Class<? extends Filter> filterClass, String pathSpec) {
+ this.pathSpec = MorePreconditions.checkNotBlank(pathSpec);
+ this.filterClass = checkNotNull(filterClass);
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java
new file mode 100644
index 0000000..4444e4c
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java
@@ -0,0 +1,65 @@
+/**
+ * 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 org.apache.aurora.common.application.http;
+
+import javax.servlet.http.HttpServlet;
+
+import com.google.common.collect.ImmutableMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import static org.apache.aurora.common.base.MorePreconditions.checkNotBlank;
+
+/**
+ * An {@link javax.servlet.http.HttpServlet} configuration used to mount HTTP handlers via
+ * {@link Registration#registerServlet(com.google.inject.Binder, HttpServletConfig)}.
+ *
+ * TODO(William Farner): Move this to a more appropriate package after initial AppLauncher check-in.
+ *
+ */
+public class HttpServletConfig {
+ public final String path;
+ public final Class<? extends HttpServlet> handlerClass;
+ public final ImmutableMap<String, String> params;
+ public final boolean silent;
+
+ /**
+ * Creates a new servlet config.
+ *
+ * @param path the absolute path to mount the handler on
+ * @param servletClass the type of servlet that will render pages at {@code path}
+ * @param silent whether or not to display a link for this handler on the landing page
+ */
+ public HttpServletConfig(String path, Class<? extends HttpServlet> servletClass,
+ boolean silent) {
+ this(path, servletClass, ImmutableMap.<String, String>of(), silent);
+ }
+
+ /**
+ * Registers a new servlet config with servlet initialization parameters.
+ *
+ * @param path the absolute path to mount the handler on
+ * @param servletClass the type of servlet that will render pages at {@code path}
+ * @param params a map of servlet init parameters to initialize the servlet with
+ * @param silent whether or not to display a link for this handler on the landing page
+ */
+ public HttpServletConfig(String path, Class<? extends HttpServlet> servletClass,
+ ImmutableMap<String, String> params, boolean silent) {
+
+ this.path = checkNotBlank(path);
+ this.handlerClass = checkNotNull(servletClass);
+ this.params = checkNotNull(params);
+ this.silent = silent;
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java b/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java
new file mode 100644
index 0000000..b66c8c8
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java
@@ -0,0 +1,155 @@
+/**
+ * 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 org.apache.aurora.common.application.http;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.net.URL;
+
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServlet;
+
+import com.google.common.io.Resources;
+import com.google.inject.Binder;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.multibindings.Multibinder;
+
+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;
+
+/**
+ * Utility class for registering HTTP servlets and assets.
+ */
+public final class Registration {
+
+ private Registration() {
+ // Utility class.
+ }
+
+ /**
+ * Equivalent to
+ * {@code registerServlet(binder, new HttpServletConfig(path, servletClass, silent))}.
+ */
+ public static void registerServlet(Binder binder, String path,
+ Class<? extends HttpServlet> servletClass, boolean silent) {
+ registerServlet(binder, new HttpServletConfig(path, servletClass, silent));
+ }
+
+ /**
+ * Registers a binding for an {@link javax.servlet.http.HttpServlet} to be exported at a specified
+ * path.
+ *
+ * @param binder a guice binder to register the handler with
+ * @param config a servlet mounting specification
+ */
+ public static void registerServlet(Binder binder, HttpServletConfig config) {
+ Multibinder.newSetBinder(binder, HttpServletConfig.class).addBinding().toInstance(config);
+ }
+
+ /**
+ * A binding annotation applied to the set of additional index page links bound via
+ * {@link #Registration#registerEndpoint()}
+ */
+ @BindingAnnotation
+ @Target({FIELD, PARAMETER, METHOD})
+ @Retention(RUNTIME)
+ public @interface IndexLink { }
+
+ /**
+ * Gets the multibinder used to bind links on the root servlet.
+ * The resulting {@link java.util.Set} is bound with the {@link IndexLink} annotation.
+ *
+ * @param binder a guice binder to associate the multibinder with.
+ * @return The multibinder to bind index links against.
+ */
+ public static Multibinder<String> getEndpointBinder(Binder binder) {
+ return Multibinder.newSetBinder(binder, String.class, IndexLink.class);
+ }
+
+ /**
+ * Registers a link to display on the root servlet.
+ *
+ * @param binder a guice binder to register the link with.
+ * @param endpoint Endpoint URI to include.
+ */
+ public static void registerEndpoint(Binder binder, String endpoint) {
+ getEndpointBinder(binder).addBinding().toInstance(endpoint);
+ }
+
+ /**
+ * Registers a binding for a URL asset to be served by the HTTP server, with an optional
+ * entity tag for cache control.
+ *
+ * @param binder a guice binder to register the handler with
+ * @param servedPath Path to serve the resource from in the HTTP server.
+ * @param asset Resource to be served.
+ * @param assetType MIME-type for the asset.
+ * @param silent Whether the server should hide this asset on the index page.
+ */
+ public static void registerHttpAsset(Binder binder, String servedPath, URL asset,
+ String assetType, boolean silent) {
+ Multibinder.newSetBinder(binder, HttpAssetConfig.class).addBinding().toInstance(
+ new HttpAssetConfig(servedPath, asset, assetType, silent));
+ }
+
+ /**
+ * Registers a binding for a classpath resource to be served by the HTTP server, using a resource
+ * path relative to a class.
+ *
+ * @param binder a guice binder to register the handler with
+ * @param servedPath Path to serve the asset from in the HTTP server.
+ * @param contextClass Context class for defining the relative path to the asset.
+ * @param assetRelativePath Path to the served asset, relative to {@code contextClass}.
+ * @param assetType MIME-type for the asset.
+ * @param silent Whether the server should hide this asset on the index page.
+ */
+ public static void registerHttpAsset(
+ Binder binder,
+ String servedPath,
+ Class<?> contextClass,
+ String assetRelativePath,
+ String assetType,
+ boolean silent) {
+
+ registerHttpAsset(binder, servedPath, Resources.getResource(contextClass, assetRelativePath),
+ assetType, silent);
+ }
+
+ /**
+ * Gets the multibinder used to bind HTTP filters.
+ *
+ * @param binder a guice binder to associate the multibinder with.
+ * @return The multibinder to bind HTTP filter configurations against.
+ */
+ public static Multibinder<HttpFilterConfig> getFilterBinder(Binder binder) {
+ return Multibinder.newSetBinder(binder, HttpFilterConfig.class);
+ }
+
+ /**
+ * Registers an HTTP servlet filter.
+ *
+ * @param binder a guice binder to register the filter with.
+ * @param filterClass Filter class to register.
+ * @param pathSpec Path spec that the filter should be activated on.
+ */
+ public static void registerServletFilter(
+ Binder binder,
+ Class<? extends Filter> filterClass,
+ String pathSpec) {
+
+ getFilterBinder(binder).addBinding().toInstance(new HttpFilterConfig(filterClass, pathSpec));
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java
new file mode 100644
index 0000000..65e9250
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java
@@ -0,0 +1,52 @@
+/**
+ * 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 org.apache.aurora.common.application.modules;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+
+import org.apache.aurora.common.stats.Stats;
+import org.apache.aurora.common.util.BuildInfo;
+
+import org.apache.aurora.common.application.AppLauncher;
+
+/**
+ * Binding module for the bare minimum requirements for the
+ * {@link AppLauncher}.
+ *
+ * @author William Farner
+ */
+public class AppLauncherModule extends AbstractModule {
+
+ private static final Logger LOG = Logger.getLogger(AppLauncherModule.class.getName());
+ private static final AtomicLong UNCAUGHT_EXCEPTIONS = Stats.exportLong("uncaught_exceptions");
+
+ @Override
+ protected void configure() {
+ bind(BuildInfo.class).in(Singleton.class);
+ bind(UncaughtExceptionHandler.class).to(LoggingExceptionHandler.class);
+ }
+
+ public static class LoggingExceptionHandler implements UncaughtExceptionHandler {
+ @Override public void uncaughtException(Thread t, Throwable e) {
+ UNCAUGHT_EXCEPTIONS.incrementAndGet();
+ LOG.log(Level.SEVERE, "Uncaught exception from " + t + ":" + e, e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java
new file mode 100644
index 0000000..b3e59e7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java
@@ -0,0 +1,194 @@
+/**
+ * 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 org.apache.aurora.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 org.apache.aurora.common.application.Lifecycle;
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.aurora.common.application.ShutdownStage;
+import org.apache.aurora.common.application.StartupRegistry;
+import org.apache.aurora.common.application.StartupStage;
+
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.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(ShutdownRegistry.ShutdownRegistryImpl.class);
+ bind(Key.get(Command.class, ShutdownStage.class)).to(ShutdownRegistry.ShutdownRegistryImpl.class);
+ bind(ShutdownRegistry.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.
+ */
+ LocalServiceRegistry.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 LocalServiceRegistry.LocalService service) {
+ runnerBinder(binder).addBinding().toInstance(
+ new ServiceRunner() {
+ @Override public LocalServiceRegistry.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();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java
new file mode 100644
index 0000000..027e4a7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java
@@ -0,0 +1,258 @@
+/**
+ * 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 org.apache.aurora.common.application.modules;
+
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import org.apache.aurora.common.application.modules.LifecycleModule.LaunchException;
+import org.apache.aurora.common.application.modules.LifecycleModule.Service;
+import org.apache.aurora.common.application.modules.LifecycleModule.ServiceRunner;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.base.MorePreconditions;
+import org.apache.aurora.common.net.InetSocketAddressHelper;
+
+/**
+ * Registry for services that should be exported from the application.
+ *
+ * Example of announcing and registering a port:
+ * <pre>
+ * class MyLauncher implements Provider<LocalService> {
+ * public LocalService get() {
+ * // Launch service.
+ * }
+ * }
+ *
+ * class MyServiceModule extends AbstractModule {
+ * public void configure() {
+ * LifeCycleModule.bindServiceLauncher(binder(), MyLauncher.class);
+ * }
+ * }
+ * </pre>
+ */
+public class LocalServiceRegistry {
+
+ private static final Predicate<LocalService> IS_PRIMARY = new Predicate<LocalService>() {
+ @Override public boolean apply(LocalService service) {
+ return service.primary;
+ }
+ };
+
+ private static final Function<LocalService, InetSocketAddress> SERVICE_TO_SOCKET =
+ new Function<LocalService, InetSocketAddress>() {
+ @Override public InetSocketAddress apply(LocalService service) {
+ try {
+ return InetSocketAddressHelper.getLocalAddress(service.port);
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("Failed to resolve local address for " + service, e);
+ }
+ }
+ };
+
+ private static final Function<LocalService, String> GET_NAME =
+ new Function<LocalService, String>() {
+ @Override public String apply(LocalService service) {
+ return Iterables.getOnlyElement(service.names);
+ }
+ };
+
+ private final ShutdownRegistry shutdownRegistry;
+ private final Provider<Set<ServiceRunner>> runnerProvider;
+
+ private Optional<InetSocketAddress> primarySocket = null;
+ private Map<String, InetSocketAddress> auxiliarySockets = null;
+
+ /**
+ * Creates a new local service registry.
+ *
+ * @param runnerProvider provider of registered local services.
+ * @param shutdownRegistry Shutdown registry to tear down launched services.
+ */
+ @Inject
+ public LocalServiceRegistry(@Service Provider<Set<ServiceRunner>> runnerProvider,
+ ShutdownRegistry shutdownRegistry) {
+ this.runnerProvider = Preconditions.checkNotNull(runnerProvider);
+ this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
+ }
+
+ private static final Function<LocalService, Iterable<LocalService>> AUX_NAME_BREAKOUT =
+ new Function<LocalService, Iterable<LocalService>>() {
+ @Override public Iterable<LocalService> apply(final LocalService service) {
+ Preconditions.checkArgument(!service.primary);
+ Function<String, LocalService> oneNameService = new Function<String, LocalService>() {
+ @Override public LocalService apply(String name) {
+ return LocalService.auxiliaryService(name, service.port, service.shutdownCommand);
+ }
+ };
+ return Iterables.transform(service.names, oneNameService);
+ }
+ };
+
+ /**
+ * Launches the local services if not already launched, otherwise this is a no-op.
+ */
+ void ensureLaunched() {
+ if (primarySocket == null) {
+ ImmutableList.Builder<LocalService> builder = ImmutableList.builder();
+
+ for (ServiceRunner runner : runnerProvider.get()) {
+ try {
+ LocalService service = runner.launch();
+ builder.add(service);
+ shutdownRegistry.addAction(service.shutdownCommand);
+ } catch (LaunchException e) {
+ throw new IllegalStateException("Failed to launch " + runner, e);
+ }
+ }
+
+ List<LocalService> localServices = builder.build();
+ Iterable<LocalService> primaries = Iterables.filter(localServices, IS_PRIMARY);
+ switch (Iterables.size(primaries)) {
+ case 0:
+ primarySocket = Optional.absent();
+ break;
+
+ case 1:
+ primarySocket = Optional.of(SERVICE_TO_SOCKET.apply(Iterables.getOnlyElement(primaries)));
+ break;
+
+ default:
+ throw new IllegalArgumentException("More than one primary local service: " + primaries);
+ }
+
+ Iterable<LocalService> auxSinglyNamed = Iterables.concat(
+ FluentIterable.from(localServices)
+ .filter(Predicates.not(IS_PRIMARY))
+ .transform(AUX_NAME_BREAKOUT));
+
+ Map<String, LocalService> byName;
+ try {
+ byName = Maps.uniqueIndex(auxSinglyNamed, GET_NAME);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Auxiliary services with identical names.", e);
+ }
+
+ auxiliarySockets = ImmutableMap.copyOf(Maps.transformValues(byName, SERVICE_TO_SOCKET));
+ }
+ }
+
+ /**
+ * Gets the mapping from auxiliary port name to socket.
+ *
+ * @return Auxiliary port mapping.
+ */
+ public synchronized Map<String, InetSocketAddress> getAuxiliarySockets() {
+ ensureLaunched();
+ return auxiliarySockets;
+ }
+
+ /**
+ * Gets the optional primary socket address, and returns an unresolved local socket address
+ * representing that port.
+ *
+ * @return Local socket address for the primary port.
+ * @throws IllegalStateException If the primary port was not set.
+ */
+ public synchronized Optional<InetSocketAddress> getPrimarySocket() {
+ ensureLaunched();
+ return primarySocket;
+ }
+
+ /**
+ * An individual local service.
+ */
+ public static final class LocalService {
+ private final boolean primary;
+ private final Set<String> names;
+ private final int port;
+ private final Command shutdownCommand;
+
+ private LocalService(boolean primary, Set<String> names, int port,
+ Command shutdownCommand) {
+ this.primary = primary;
+ this.names = names;
+ this.port = port;
+ this.shutdownCommand = Preconditions.checkNotNull(shutdownCommand);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("primary", primary)
+ .append("name", names)
+ .append("port", port)
+ .toString();
+ }
+
+ /**
+ * Creates a primary local service.
+ *
+ * @param port Service port.
+ * @param shutdownCommand A command that will shut down the service.
+ * @return A new primary local service.
+ */
+ public static LocalService primaryService(int port, Command shutdownCommand) {
+ return new LocalService(true, ImmutableSet.<String>of(), port, shutdownCommand);
+ }
+
+ /**
+ * Creates a named auxiliary service.
+ *
+ * @param name Service name.
+ * @param port Service port.
+ * @param shutdownCommand A command that will shut down the service.
+ * @return A new auxiliary local service.
+ */
+ public static LocalService auxiliaryService(String name, int port, Command shutdownCommand) {
+ return auxiliaryService(ImmutableSet.of(name), port, shutdownCommand);
+ }
+
+ /**
+ * Creates an auxiliary service identified by multiple names.
+ *
+ * @param names Service names.
+ * @param port Service port.
+ * @param shutdownCommand A command that will shut down the service.
+ * @return A new auxiliary local service.
+ */
+ public static LocalService auxiliaryService(
+ Set<String> names,
+ int port,
+ Command shutdownCommand) {
+
+ MorePreconditions.checkNotBlank(names);
+ return new LocalService(false, names, port, shutdownCommand);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java
new file mode 100644
index 0000000..107e3aa
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java
@@ -0,0 +1,117 @@
+/**
+ * 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 org.apache.aurora.common.application.modules;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.args.constraints.CanRead;
+import org.apache.aurora.common.args.constraints.Exists;
+import org.apache.aurora.common.args.constraints.IsDirectory;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.logging.LogUtil;
+import org.apache.aurora.common.logging.RootLogConfig;
+import org.apache.aurora.common.logging.RootLogConfig.Configuration;
+import org.apache.aurora.common.net.http.handlers.LogPrinter;
+import org.apache.aurora.common.stats.StatImpl;
+import org.apache.aurora.common.stats.Stats;
+
+/**
+ * Binding module for logging-related bindings, such as the log directory.
+ *
+ * This module uses a single optional command line argument 'log_dir'. If unset, the logging
+ * directory will be auto-discovered via:
+ * {@link LogUtil#getLogManagerLogDir()}.
+ *
+ * Bindings provided by this module:
+ * <ul>
+ * <li>{@code @Named(LogPrinter.LOG_DIR_KEY) File} - Log directory.
+ * <li>{@code Optional<RootLogConfig.Configuraton>} - If glog is enabled the configuration
+ * used.
+ * </ul>
+ *
+ * Default bindings that may be overridden:
+ * <ul>
+ * <li>Log directory: directory where application logs are written. May be overridden by binding
+ * to: {@code bind(File.class).annotatedWith(Names.named(LogPrinter.LOG_DIR_KEY))}.
+ * </ul>
+ *
+ * @author William Farner
+ */
+public class LogModule extends AbstractModule {
+
+ private static final Logger LOG = Logger.getLogger(LogModule.class.getName());
+
+ @Exists
+ @CanRead
+ @IsDirectory
+ @CmdLine(name = "log_dir",
+ help = "The directory where application logs are written.")
+ private static final Arg<File> LOG_DIR = Arg.create(null);
+
+ @CmdLine(name = "use_glog",
+ help = "True to use the new glog-based configuration for the root logger.")
+ private static final Arg<Boolean> USE_GLOG = Arg.create(true);
+
+ @Override
+ protected void configure() {
+ // Bind the default log directory.
+ bind(File.class).annotatedWith(Names.named(LogPrinter.LOG_DIR_KEY)).toInstance(getLogDir());
+
+ LifecycleModule.bindStartupAction(binder(), ExportLogDir.class);
+
+ Configuration configuration = null;
+ if (USE_GLOG.get()) {
+ configuration = RootLogConfig.configurationFromFlags();
+ configuration.apply();
+ }
+ bind(new TypeLiteral<Optional<Configuration>>() { })
+ .toInstance(Optional.fromNullable(configuration));
+ }
+
+ private File getLogDir() {
+ File logDir = LOG_DIR.get();
+ if (logDir == null) {
+ logDir = LogUtil.getLogManagerLogDir();
+ LOG.info("From logging properties, parsed log directory " + logDir.getAbsolutePath());
+ }
+ return logDir;
+ }
+
+ public static final class ExportLogDir implements Command {
+ private final File logDir;
+
+ @Inject ExportLogDir(@Named(LogPrinter.LOG_DIR_KEY) final File logDir) {
+ this.logDir = Preconditions.checkNotNull(logDir);
+ }
+
+ @Override public void execute() {
+ Stats.exportStatic(new StatImpl<String>("logging_dir") {
+ @Override public String read() {
+ return logDir.getAbsolutePath();
+ }
+ });
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java
new file mode 100644
index 0000000..ba93d4c
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java
@@ -0,0 +1,85 @@
+/**
+ * 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 org.apache.aurora.common.application.modules;
+
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.base.Closure;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Time;
+import org.apache.aurora.common.stats.NumericStatExporter;
+
+/**
+ * Module to enable periodic exporting of registered stats to an external service.
+ *
+ * This modules supports a single command line argument, {@code stat_export_interval}, which
+ * controls the export interval (defaulting to 1 minute).
+ *
+ * Bindings required by this module:
+ * <ul>
+ * <li>{@code @ShutdownStage ShutdownRegistry} - Shutdown action registry.
+ * </ul>
+ *
+ * @author William Farner
+ */
+public class StatsExportModule extends AbstractModule {
+
+ @CmdLine(name = "stat_export_interval",
+ help = "Amount of time to wait between stat exports.")
+ private static final Arg<Amount<Long, Time>> EXPORT_INTERVAL =
+ Arg.create(Amount.of(1L, Time.MINUTES));
+
+ @Override
+ protected void configure() {
+ requireBinding(Key.get(new TypeLiteral<Closure<Map<String, ? extends Number>>>() { }));
+ LifecycleModule.bindStartupAction(binder(), StartCuckooExporter.class);
+ }
+
+ public static final class StartCuckooExporter implements Command {
+
+ private final Closure<Map<String, ? extends Number>> statSink;
+ private final ShutdownRegistry shutdownRegistry;
+
+ @Inject StartCuckooExporter(
+ Closure<Map<String, ? extends Number>> statSink,
+ ShutdownRegistry shutdownRegistry) {
+
+ this.statSink = Preconditions.checkNotNull(statSink);
+ this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
+ }
+
+ @Override public void execute() {
+ ThreadFactory threadFactory =
+ new ThreadFactoryBuilder().setNameFormat("CuckooExporter-%d").setDaemon(true).build();
+
+ final NumericStatExporter exporter = new NumericStatExporter(statSink,
+ Executors.newScheduledThreadPool(1, threadFactory), EXPORT_INTERVAL.get());
+
+ exporter.start(shutdownRegistry);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java
new file mode 100644
index 0000000..78d9512
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java
@@ -0,0 +1,146 @@
+/**
+ * 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 org.apache.aurora.common.application.modules;
+
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import com.google.common.base.Supplier;
+import com.google.common.primitives.Longs;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Time;
+import org.apache.aurora.common.stats.JvmStats;
+import org.apache.aurora.common.stats.Stat;
+import org.apache.aurora.common.stats.StatImpl;
+import org.apache.aurora.common.stats.StatRegistry;
+import org.apache.aurora.common.stats.Stats;
+import org.apache.aurora.common.stats.TimeSeriesRepository;
+import org.apache.aurora.common.stats.TimeSeriesRepositoryImpl;
+import org.apache.aurora.common.util.BuildInfo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Binding module for injections related to the in-process stats system.
+ *
+ * This modules supports two command line arguments:
+ * <ul>
+ * <li>{@code stat_sampling_interval} - Statistic value sampling interval.
+ * <li>{@code stat_retention_period} - Time for a stat to be retained in memory before expring.
+ * </ul>
+ *
+ * Bindings required by this module:
+ * <ul>
+ * <li>{@code ShutdownRegistry} - Shutdown hook registry.
+ * <li>{@code BuildInfo} - Build information for the application.
+ * </ul>
+ *
+ * @author William Farner
+ */
+public class StatsModule extends AbstractModule {
+
+ @CmdLine(name = "stat_sampling_interval", help = "Statistic value sampling interval.")
+ private static final Arg<Amount<Long, Time>> SAMPLING_INTERVAL =
+ Arg.create(Amount.of(1L, Time.SECONDS));
+
+ @CmdLine(name = "stat_retention_period",
+ help = "Time for a stat to be retained in memory before expiring.")
+ private static final Arg<Amount<Long, Time>> RETENTION_PERIOD =
+ Arg.create(Amount.of(1L, Time.HOURS));
+
+ public static Amount<Long, Time> getSamplingInterval() {
+ return SAMPLING_INTERVAL.get();
+ }
+
+ @Override
+ protected void configure() {
+ requireBinding(ShutdownRegistry.class);
+ requireBinding(BuildInfo.class);
+
+ // Bindings for TimeSeriesRepositoryImpl.
+ bind(StatRegistry.class).toInstance(Stats.STAT_REGISTRY);
+ bind(new TypeLiteral<Amount<Long, Time>>() { })
+ .annotatedWith(Names.named(TimeSeriesRepositoryImpl.SAMPLE_RETENTION_PERIOD))
+ .toInstance(RETENTION_PERIOD.get());
+ bind(new TypeLiteral<Amount<Long, Time>>() { })
+ .annotatedWith(Names.named(TimeSeriesRepositoryImpl.SAMPLE_PERIOD))
+ .toInstance(SAMPLING_INTERVAL.get());
+ bind(TimeSeriesRepository.class).to(TimeSeriesRepositoryImpl.class).in(Singleton.class);
+
+ bind(new TypeLiteral<Supplier<Iterable<Stat<?>>>>() { }).toInstance(
+ new Supplier<Iterable<Stat<?>>>() {
+ @Override public Iterable<Stat<?>> get() {
+ return Stats.getVariables();
+ }
+ }
+ );
+
+ LifecycleModule.bindStartupAction(binder(), StartStatPoller.class);
+ }
+
+ public static final class StartStatPoller implements Command {
+ private static final Logger LOG = Logger.getLogger(StartStatPoller.class.getName());
+ private final ShutdownRegistry shutdownRegistry;
+ private final BuildInfo buildInfo;
+ private final TimeSeriesRepository timeSeriesRepository;
+
+ @Inject StartStatPoller(
+ ShutdownRegistry shutdownRegistry,
+ BuildInfo buildInfo,
+ TimeSeriesRepository timeSeriesRepository) {
+
+ this.shutdownRegistry = checkNotNull(shutdownRegistry);
+ this.buildInfo = checkNotNull(buildInfo);
+ this.timeSeriesRepository = checkNotNull(timeSeriesRepository);
+ }
+
+ @Override public void execute() {
+ Properties properties = buildInfo.getProperties();
+ LOG.info("Build information: " + properties);
+ for (String name : properties.stringPropertyNames()) {
+ final String stringValue = properties.getProperty(name);
+ if (stringValue == null) {
+ continue;
+ }
+ final Long longValue = Longs.tryParse(stringValue);
+ if (longValue != null) {
+ Stats.exportStatic(new StatImpl<Long>(Stats.normalizeName(name)) {
+ @Override public Long read() {
+ return longValue;
+ }
+ });
+ } else {
+ Stats.exportString(new StatImpl<String>(Stats.normalizeName(name)) {
+ @Override public String read() {
+ return stringValue;
+ }
+ });
+ }
+ }
+
+ JvmStats.export();
+ timeSeriesRepository.start(shutdownRegistry);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java
new file mode 100644
index 0000000..55236df
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java
@@ -0,0 +1,41 @@
+/**
+ * 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 org.apache.aurora.common.application.modules;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
+
+import org.apache.aurora.common.application.http.Registration;
+import org.apache.aurora.common.net.http.handlers.ThriftServlet;
+import org.apache.aurora.common.net.monitoring.TrafficMonitor;
+
+/**
+ * Binding module for thrift traffic monitor servlets, to ensure an empty set is available for
+ * the thrift traffic monitor servlet.
+ *
+ * @author William Farner
+ */
+public class ThriftModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ // Make sure that there is at least an empty set bound to client andserver monitors.
+ Multibinder.newSetBinder(binder(), TrafficMonitor.class,
+ Names.named(ThriftServlet.THRIFT_CLIENT_MONITORS));
+ Multibinder.newSetBinder(binder(), TrafficMonitor.class,
+ Names.named(ThriftServlet.THRIFT_SERVER_MONITORS));
+
+ Registration.registerServlet(binder(), "/thrift", ThriftServlet.class, false);
+ }
+}