aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zma...@apache.org
Subject [36/37] aurora git commit: Import of Twitter Commons.
Date Tue, 25 Aug 2015 18:19:50 GMT
http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java
----------------------------------------------------------------------
diff --git a/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java b/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java
new file mode 100644
index 0000000..ab1f255
--- /dev/null
+++ b/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java
@@ -0,0 +1,680 @@
+// =================================================================================================
+// 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.args.apt;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleAnnotationValueVisitor6;
+import javax.lang.model.util.SimpleTypeVisitor6;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.ArgParser;
+import com.twitter.common.args.CmdLine;
+import com.twitter.common.args.Parser;
+import com.twitter.common.args.Positional;
+import com.twitter.common.args.Verifier;
+import com.twitter.common.args.VerifierFor;
+import com.twitter.common.args.apt.Configuration.ParserInfo;
+
+import static com.twitter.common.args.apt.Configuration.ArgInfo;
+import static com.twitter.common.args.apt.Configuration.VerifierInfo;
+
+/**
+ * Processes {@literal @CmdLine} annotated fields and {@literal @ArgParser} and
+ * {@literal @VerifierFor} parser and verifier registrations and stores configuration data listing
+ * these fields, parsers and verifiers on the classpath for discovery via
+ * {@link com.twitter.common.args.apt.Configuration#load()}.
+ *
+ * <p>Supports an apt option useful for some build setups that create monolithic jars aggregating
+ * many library jars, one or more of which have embedded arg definitions themselves.  By adding the
+ * following flag to a javac invocation:
+ * <code>-Acom.twitter.common.args.apt.CmdLineProcessor.main</code>
+ * you signal this apt processor that the compilation target is a leaf target that will comprise one
+ * or more executable mains (as opposed to a library jar).  As a result, the embedded arg
+ * definitions generated will occupy a special resource that is always checked for first during
+ * runtime arg parsing.
+ */
+@SupportedOptions({
+    CmdLineProcessor.MAIN_OPTION,
+    CmdLineProcessor.CHECK_LINKAGE_OPTION
+})
+public class CmdLineProcessor extends AbstractProcessor {
+  static final String MAIN_OPTION =
+      "com.twitter.common.args.apt.CmdLineProcessor.main";
+  static final String CHECK_LINKAGE_OPTION =
+      "com.twitter.common.args.apt.CmdLineProcessor.check_linkage";
+
+  private static final Function<Class<?>, String> GET_NAME = new Function<Class<?>, String>() {
+    @Override public String apply(Class<?> type) {
+      return type.getName();
+    }
+  };
+
+  private final Supplier<Configuration> configSupplier =
+      Suppliers.memoize(new Supplier<Configuration>() {
+        @Override public Configuration get() {
+          try {
+            Configuration configuration = Configuration.load();
+            for (ArgInfo argInfo : configuration.positionalInfo()) {
+              configBuilder.addPositionalInfo(argInfo);
+            }
+            for (ArgInfo argInfo : configuration.optionInfo()) {
+              configBuilder.addCmdLineArg(argInfo);
+            }
+            for (ParserInfo parserInfo : configuration.parserInfo()) {
+              configBuilder.addParser(parserInfo);
+            }
+            for (VerifierInfo verifierInfo : configuration.verifierInfo()) {
+              configBuilder.addVerifier(verifierInfo);
+            }
+            return configuration;
+          } catch (IOException e) {
+            error("Problem loading existing flags on compile time classpath: %s",
+                Throwables.getStackTraceAsString(e));
+            return null;
+          }
+        }
+      });
+
+  private final Configuration.Builder configBuilder = new Configuration.Builder();
+  private final ImmutableSet.Builder<String> contributingClassNamesBuilder = ImmutableSet.builder();
+
+  private Types typeUtils;
+  private Elements elementUtils;
+  private boolean isMain;
+  private boolean isCheckLinkage;
+
+  private static boolean getBooleanOption(Map<String, String> options, String name,
+      boolean defaultValue) {
+
+    if (!options.containsKey(name)) {
+      return defaultValue;
+    }
+
+    // We want to map the presence of a boolean option without a value to indicate true, giving the
+    // following accepted boolean option formats:
+    // -Afoo -> true
+    // -Afoo=false -> false
+    // -Afoo=true -> true
+
+    String isOption = options.get(name);
+    return (isOption == null) || Boolean.parseBoolean(isOption);
+  }
+
+  @Override
+  public void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+
+    typeUtils = processingEnv.getTypeUtils();
+    elementUtils = processingEnv.getElementUtils();
+
+    Map<String, String> options = processingEnv.getOptions();
+    isMain = getBooleanOption(options, MAIN_OPTION, false);
+    isCheckLinkage = getBooleanOption(options, CHECK_LINKAGE_OPTION, true);
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latest();
+  }
+
+  @Override
+  public Set<String> getSupportedAnnotationTypes() {
+    return ImmutableSet.copyOf(Iterables.transform(
+        ImmutableList.of(Positional.class, CmdLine.class, ArgParser.class, VerifierFor.class),
+        GET_NAME));
+  }
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    try {
+      @Nullable Configuration classpathConfiguration = configSupplier.get();
+
+      Set<? extends Element> parsers = getAnnotatedElements(roundEnv, ArgParser.class);
+      contributingClassNamesBuilder.addAll(extractClassNames(parsers));
+      @Nullable Set<String> parsedTypes = getParsedTypes(classpathConfiguration, parsers);
+
+      Set<? extends Element> cmdlineArgs = getAnnotatedElements(roundEnv, CmdLine.class);
+      contributingClassNamesBuilder.addAll(extractEnclosingClassNames(cmdlineArgs));
+      Set<? extends Element> positionalArgs = getAnnotatedElements(roundEnv, Positional.class);
+      contributingClassNamesBuilder.addAll(extractEnclosingClassNames(positionalArgs));
+
+      ImmutableSet<? extends Element> invalidArgs =
+          Sets.intersection(cmdlineArgs, positionalArgs).immutableCopy();
+      if (!invalidArgs.isEmpty()) {
+        error("An Arg cannot be annotated with both @CmdLine and @Positional, found bad Arg "
+            + "fields: %s", invalidArgs);
+      }
+
+      for (ArgInfo cmdLineInfo : processAnnotatedArgs(parsedTypes, cmdlineArgs, CmdLine.class)) {
+        configBuilder.addCmdLineArg(cmdLineInfo);
+      }
+
+      for (ArgInfo positionalInfo
+          : processAnnotatedArgs(parsedTypes, positionalArgs, Positional.class)) {
+
+        configBuilder.addPositionalInfo(positionalInfo);
+      }
+      checkPositionalArgsAreLists(roundEnv);
+
+      processParsers(parsers);
+
+      Set<? extends Element> verifiers = getAnnotatedElements(roundEnv, VerifierFor.class);
+      contributingClassNamesBuilder.addAll(extractClassNames(verifiers));
+      processVerifiers(verifiers);
+
+      if (roundEnv.processingOver()) {
+        if (classpathConfiguration != null
+            && (!classpathConfiguration.isEmpty() || !configBuilder.isEmpty())) {
+
+          @Nullable Resource cmdLinePropertiesResource =
+              openCmdLinePropertiesResource(classpathConfiguration);
+          if (cmdLinePropertiesResource != null) {
+            Writer writer = cmdLinePropertiesResource.getWriter();
+            try {
+              configBuilder.build(classpathConfiguration).store(writer,
+                  "Generated via apt by " + getClass().getName());
+            } finally {
+              closeQuietly(writer);
+            }
+
+            writeResourceMapping(contributingClassNamesBuilder.build(),
+                cmdLinePropertiesResource.getResource());
+          }
+        }
+      }
+    // TODO(John Sirois): Investigate narrowing this catch - its not clear there is any need to be
+    // so general.
+    // SUPPRESS CHECKSTYLE RegexpSinglelineJava
+    } catch (RuntimeException e) {
+      // Catch internal errors - when these bubble more useful queued error messages are lost in
+      // some javac implementations.
+      error("Unexpected error completing annotation processing:\n%s",
+          Throwables.getStackTraceAsString(e));
+    }
+    return true;
+  }
+
+  private void writeResourceMapping(
+      Set<String> contributingClassNames,
+      FileObject cmdLinePropertiesResourcePath) {
+
+    // TODO(John Sirois): Lift the compiler resource-mappings writer to its own class/artifact to be
+    // re-used by other apt processors: https://github.com/twitter/commons/issues/319
+
+    // NB: javac rejects a package name with illegal package name characters like '-' so we just
+    // pass the empty package and the fully qualified resource file name.
+    @Nullable Resource resource = openResource("",
+        "META-INF/compiler/resource-mappings/" + getClass().getName());
+    if (resource != null) {
+      PrintWriter writer = new PrintWriter(resource.getWriter());
+      writer.printf("resources by class name:\n");
+      writer.printf("%d items\n", contributingClassNames.size());
+      try {
+        for (String className : contributingClassNames) {
+          writer.printf("%s -> %s\n", className, cmdLinePropertiesResourcePath.toUri().getPath());
+        }
+      } finally {
+        closeQuietly(writer);
+      }
+    }
+  }
+
+  private static final Function<Element, Element> EXTRACT_ENCLOSING_CLASS =
+      new Function<Element, Element>() {
+        @Override public Element apply(Element element) {
+          return element.getEnclosingElement();
+        }
+      };
+
+  private final Function<Element, String> extractClassName = new Function<Element, String>() {
+    @Override public String apply(Element element) {
+      return getBinaryName((TypeElement) element);
+    }
+  };
+
+  private final Function<Element, String> extractEnclosingClassName =
+      Functions.compose(extractClassName, EXTRACT_ENCLOSING_CLASS);
+
+  private Iterable<String> extractEnclosingClassNames(Iterable<? extends Element> elements) {
+    return Iterables.transform(elements, extractEnclosingClassName);
+  }
+
+  private Iterable<String> extractClassNames(Iterable<? extends Element> elements) {
+    return Iterables.transform(elements, extractClassName);
+  }
+
+  private void closeQuietly(Closeable closeable) {
+    try {
+      closeable.close();
+    } catch (IOException e) {
+      log(Kind.MANDATORY_WARNING, "Failed to close %s: %s", closeable, e);
+    }
+  }
+
+  private void checkPositionalArgsAreLists(RoundEnvironment roundEnv) {
+    for (Element positionalArg : getAnnotatedElements(roundEnv, Positional.class)) {
+      @Nullable TypeMirror typeArgument =
+          getTypeArgument(positionalArg.asType(), typeElement(Arg.class));
+      if ((typeArgument == null)
+          || !typeUtils.isSubtype(typeElement(List.class).asType(), typeArgument)) {
+        error("Found @Positional %s %s.%s that is not a List",
+            positionalArg.asType(), positionalArg.getEnclosingElement(), positionalArg);
+      }
+    }
+  }
+
+  @Nullable
+  private Set<String> getParsedTypes(@Nullable Configuration configuration,
+      Set<? extends Element> parsers) {
+
+    if (!isCheckLinkage) {
+      return null;
+    }
+
+    Iterable<String> parsersFor = Optional.presentInstances(Iterables.transform(parsers,
+        new Function<Element, Optional<String>>() {
+          @Override public Optional<String> apply(Element parser) {
+            TypeMirror parsedType = getTypeArgument(parser.asType(), typeElement(Parser.class));
+            if (parsedType == null) {
+              error("failed to find a type argument for Parser: %s", parser);
+              return Optional.absent();
+            }
+            // Equals on TypeMirrors doesn't work - so we compare string representations :/
+            return Optional.of(typeUtils.erasure(parsedType).toString());
+          }
+        }));
+    if (configuration != null) {
+      parsersFor = Iterables.concat(parsersFor, Iterables.filter(
+          Iterables.transform(configuration.parserInfo(),
+              new Function<ParserInfo, String>() {
+                @Override @Nullable public String apply(ParserInfo parserInfo) {
+                  TypeElement typeElement = elementUtils.getTypeElement(parserInfo.parsedType);
+                  // We may not have a type on the classpath for a previous round - this is fine as
+                  // long as the no Args in this round that are of the type.
+                  return (typeElement == null)
+                      ? null : typeUtils.erasure(typeElement.asType()).toString();
+                }
+              }), Predicates.notNull()));
+    }
+    return ImmutableSet.copyOf(parsersFor);
+  }
+
+  private Iterable<ArgInfo> processAnnotatedArgs(
+      @Nullable final Set<String> parsedTypes,
+      Set<? extends Element> args,
+      final Class<? extends Annotation> argAnnotation) {
+
+    return Optional.presentInstances(Iterables.transform(args,
+        new Function<Element, Optional<ArgInfo>>() {
+          @Override public Optional<ArgInfo> apply(Element arg) {
+            @Nullable TypeElement containingType = processArg(parsedTypes, arg, argAnnotation);
+              if (containingType == null) {
+                return Optional.absent();
+              } else {
+                return Optional.of(new ArgInfo(getBinaryName(containingType),
+                    arg.getSimpleName().toString()));
+              }
+            }
+        }));
+  }
+
+  private Set<? extends Element> getAnnotatedElements(RoundEnvironment roundEnv,
+      Class<? extends Annotation> argAnnotation) {
+    return roundEnv.getElementsAnnotatedWith(typeElement(argAnnotation));
+  }
+
+  @Nullable
+  private TypeElement processArg(@Nullable Set<String> parsedTypes, Element annotationElement,
+      Class<? extends Annotation> annotationType) {
+
+    TypeElement parserType = typeElement(Parser.class);
+    if (annotationElement.getKind() != ElementKind.FIELD) {
+      error("Found a @%s annotation on a non-field %s",
+          annotationType.getSimpleName(), annotationElement);
+      return null;
+    } else {
+      // Only types contain fields so this cast is safe.
+      TypeElement containingType = (TypeElement) annotationElement.getEnclosingElement();
+
+      if (!isAssignable(annotationElement.asType(), Arg.class)) {
+        error("Found a @%s annotation on a non-Arg %s.%s",
+            annotationType.getSimpleName(), containingType, annotationElement);
+        return null;
+      }
+      if (!annotationElement.getModifiers().contains(Modifier.STATIC)) {
+        return null;
+      }
+
+      if (parsedTypes != null) {
+        // Check Parser<T> linkage for the Arg<T> type T.
+        TypeMirror typeArgument =
+            getTypeArgument(annotationElement.asType(), typeElement(Arg.class));
+        @Nullable AnnotationMirror cmdLine =
+            getAnnotationMirror(annotationElement, typeElement(annotationType));
+        if (cmdLine != null) {
+          TypeMirror customParserType = getClassType(cmdLine, "parser", parserType).asType();
+          if (typeUtils.isSameType(parserType.asType(), customParserType)) {
+            if (!checkTypePresent(parsedTypes, typeArgument)) {
+              error("No parser registered for %s, %s.%s is un-parseable",
+                  typeArgument, containingType, annotationElement);
+            }
+          } else {
+            TypeMirror customParsedType = getTypeArgument(customParserType, parserType);
+            if (!isAssignable(typeArgument, customParsedType)) {
+              error("Custom parser %s parses %s but registered for %s.%s with Arg type %s",
+                  customParserType, customParsedType, containingType, annotationElement,
+                  typeArgument);
+            }
+          }
+        }
+      }
+
+      // TODO(John Sirois): Add additional compile-time @CmdLine verification for:
+      // 1.) for each @CmdLine Arg<T> annotated with @VerifierFor.annotation: T is a subtype of
+      //     V where there is a Verifier<V>
+      // 2.) name checks, including dups
+
+      return containingType;
+    }
+  }
+
+  private boolean checkTypePresent(Set<String> types, TypeMirror type) {
+    Iterable<TypeMirror> allTypes = getAllTypes(type);
+    for (TypeMirror t : allTypes) {
+      if (types.contains(typeUtils.erasure(t).toString())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void processParsers(Set<? extends Element> elements) {
+    TypeElement parserType = typeElement(Parser.class);
+    for (Element element : elements) {
+      if (element.getKind() != ElementKind.CLASS) {
+        error("Found an @ArgParser annotation on a non-class %s", element);
+      } else {
+        TypeElement parser = (TypeElement) element;
+        if (!isAssignable(parser, Parser.class)) {
+          error("Found an @ArgParser annotation on a non-Parser %s", element);
+          return;
+        }
+
+        @Nullable String parsedType = getTypeArgument(parser, parserType);
+        if (parsedType != null) {
+          configBuilder.addParser(parsedType, getBinaryName(parser));
+        }
+      }
+    }
+  }
+
+  private void processVerifiers(Set<? extends Element> elements) {
+    TypeElement verifierType = typeElement(Verifier.class);
+    TypeElement verifierForType = typeElement(VerifierFor.class);
+    for (Element element : elements) {
+      if (element.getKind() != ElementKind.CLASS) {
+        error("Found a @VerifierFor annotation on a non-class %s", element);
+      } else {
+        TypeElement verifier = (TypeElement) element;
+        if (!isAssignable(verifier, Verifier.class)) {
+          error("Found a @Verifier annotation on a non-Verifier %s", element);
+          return;
+        }
+
+        @Nullable AnnotationMirror verifierFor = getAnnotationMirror(verifier, verifierForType);
+        if (verifierFor != null) {
+          @Nullable TypeElement verifyAnnotationType = getClassType(verifierFor, "value", null);
+          if (verifyAnnotationType != null) {
+            @Nullable String verifiedType = getTypeArgument(verifier, verifierType);
+            if (verifiedType != null) {
+              String verifyAnnotationClassName =
+                  elementUtils.getBinaryName(verifyAnnotationType).toString();
+              configBuilder.addVerifier(verifiedType, verifyAnnotationClassName,
+                  getBinaryName(verifier));
+            }
+          }
+        }
+      }
+    }
+  }
+
+  @Nullable
+  private String getTypeArgument(TypeElement annotatedType, final TypeElement baseType) {
+    TypeMirror typeArgument = getTypeArgument(annotatedType.asType(), baseType);
+    return typeArgument == null
+        ? null
+        : getBinaryName((TypeElement) typeUtils.asElement(typeArgument));
+  }
+
+  private Iterable<TypeMirror> getAllTypes(TypeMirror type) {
+    return getAllTypes(new HashSet<String>(), Lists.<TypeMirror>newArrayList(), type);
+  }
+
+  private Iterable<TypeMirror> getAllTypes(Set<String> visitedTypes, List<TypeMirror> types,
+      TypeMirror type) {
+
+    String typeName = typeUtils.erasure(type).toString();
+    if (!visitedTypes.contains(typeName)) {
+      types.add(type);
+      visitedTypes.add(typeName);
+      for (TypeMirror superType : typeUtils.directSupertypes(type)) {
+        getAllTypes(visitedTypes, types, superType);
+      }
+    }
+    return types;
+  }
+
+  @Nullable
+  private TypeMirror getTypeArgument(TypeMirror annotatedType, final TypeElement baseType) {
+    for (TypeMirror type : getAllTypes(annotatedType)) {
+      TypeMirror typeArgument = type.accept(new SimpleTypeVisitor6<TypeMirror, Void>() {
+        @Override public TypeMirror visitDeclared(DeclaredType t, Void aVoid) {
+          if (isAssignable(t, baseType)) {
+            List<? extends TypeMirror> typeArguments = t.getTypeArguments();
+            if (!typeArguments.isEmpty()) {
+              return typeUtils.erasure(typeArguments.get(0));
+            }
+          }
+          return null;
+        }
+      }, null);
+
+      if (typeArgument != null) {
+        return typeArgument;
+      }
+    }
+    error("Failed to find a type argument for %s in %s", baseType, annotatedType);
+    return null;
+  }
+
+  @Nullable
+  private AnnotationMirror getAnnotationMirror(Element element, TypeElement annotationType) {
+    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
+      if (typeUtils.isSameType(annotationMirror.getAnnotationType(),  annotationType.asType())) {
+        return annotationMirror;
+      }
+    }
+    error("Failed to find an annotation of type %s on %s", annotationType, element);
+    return null;
+  }
+
+  @SuppressWarnings("unchecked")
+  private TypeElement getClassType(AnnotationMirror annotationMirror, String methodName,
+      TypeElement defaultClassType) {
+
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
+        : annotationMirror.getElementValues().entrySet()) {
+      if (entry.getKey().getSimpleName().equals(elementUtils.getName(methodName))) {
+        TypeElement classType = entry.getValue().accept(
+            new SimpleAnnotationValueVisitor6<TypeElement, Void>() {
+              @Override public TypeElement visitType(TypeMirror t, Void unused) {
+                return (TypeElement) processingEnv.getTypeUtils().asElement(t);
+              }
+            }, null);
+
+        if (classType != null) {
+          return classType;
+        }
+      }
+    }
+    if (defaultClassType == null) {
+      error("Could not find a class type for %s.%s", annotationMirror, methodName);
+    }
+    return defaultClassType;
+  }
+
+  @Nullable
+  private FileObject createCommandLineDb(Configuration configuration) {
+    String name = isMain ? Configuration.mainResourceName() : configuration.nextResourceName();
+    return createResource(Configuration.DEFAULT_RESOURCE_PACKAGE, name);
+  }
+
+  @Nullable
+  private FileObject createResource(String packageName, String name) {
+    try {
+      return processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT,
+          packageName, name);
+    } catch (IOException e) {
+      error("Failed to create resource file to store %s/%s: %s",
+          packageName, name, Throwables.getStackTraceAsString(e));
+      return null;
+    }
+  }
+
+  private static final class Resource {
+    private final FileObject resource;
+    private final Writer writer;
+
+    Resource(FileObject resource, Writer writer) {
+      this.resource = resource;
+      this.writer = writer;
+    }
+
+    FileObject getResource() {
+      return resource;
+    }
+
+    Writer getWriter() {
+      return writer;
+    }
+  }
+
+  @Nullable
+  private Resource openCmdLinePropertiesResource(Configuration configuration) {
+    @Nullable FileObject resource = createCommandLineDb(configuration);
+    return openResource(resource);
+  }
+
+  @Nullable
+  private Resource openResource(String packageName, String name) {
+    @Nullable FileObject resource = createResource(packageName, name);
+    return openResource(resource);
+  }
+
+  @Nullable
+  private Resource openResource(@Nullable FileObject resource) {
+    if (resource == null) {
+      return null;
+    }
+    try {
+      log(Kind.NOTE, "Writing %s", resource.toUri());
+      return new Resource(resource, resource.openWriter());
+    } catch (IOException e) {
+      if (!resource.delete()) {
+        log(Kind.WARNING, "Failed to clean up %s after a failing to open it for writing",
+            resource.toUri());
+      }
+      error("Failed to open resource file to store %s: %s", resource.toUri(),
+          Throwables.getStackTraceAsString(e));
+      return null;
+    }
+  }
+
+  private TypeElement typeElement(Class<?> type) {
+    return elementUtils.getTypeElement(type.getName());
+  }
+
+  private String getBinaryName(TypeElement typeElement) {
+    return elementUtils.getBinaryName(typeElement).toString();
+  }
+
+  private boolean isAssignable(TypeElement subType, Class<?> baseType) {
+    return isAssignable(subType.asType(), baseType);
+  }
+
+  private boolean isAssignable(TypeMirror subType, Class<?> baseType) {
+    return isAssignable(subType, typeElement(baseType));
+  }
+
+  private boolean isAssignable(TypeMirror subType, TypeElement baseType) {
+    return isAssignable(subType, baseType.asType());
+  }
+
+  private boolean isAssignable(TypeMirror subType, TypeMirror baseType) {
+    return typeUtils.isAssignable(typeUtils.erasure(subType), typeUtils.erasure(baseType));
+  }
+
+  private void error(String message, Object ... args) {
+    log(Kind.ERROR, message, args);
+  }
+
+  private void log(Kind kind, String message, Object ... args) {
+    processingEnv.getMessager().printMessage(kind, String.format(message, args));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java
----------------------------------------------------------------------
diff --git a/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java b/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java
new file mode 100644
index 0000000..1254fc2
--- /dev/null
+++ b/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java
@@ -0,0 +1,530 @@
+// =================================================================================================
+// 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.args.apt;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
+import com.google.common.io.InputSupplier;
+import com.google.common.io.LineProcessor;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * Loads and stores {@literal @CmdLine} configuration data. By default, that data
+ * is contained in text files called cmdline.arg.info.txt.0, cmdline.arg.info.txt.1
+ * etc. Every time a new Configuration object is created, it consumes all existing
+ * files with the above names. Saving this Configuration results in creation of a
+ * file with index increased by one, e.g. cmdline.arg.info.txt.2 in the above
+ * example.
+ *
+ * @author John Sirois
+ */
+public final class Configuration {
+
+  /**
+   * Indicates a problem reading stored {@literal @CmdLine} arg configuration data.
+   */
+  public static class ConfigurationException extends RuntimeException {
+    public ConfigurationException(String message, Object... args) {
+      super(String.format(message, args));
+    }
+    public ConfigurationException(Throwable cause) {
+      super(cause);
+    }
+  }
+
+  static final String DEFAULT_RESOURCE_PACKAGE = Configuration.class.getPackage().getName();
+
+  private static final Logger LOG = Logger.getLogger(Configuration.class.getName());
+
+  private static final CharMatcher IDENTIFIER_START =
+      CharMatcher.forPredicate(new Predicate<Character>() {
+        @Override public boolean apply(Character c) {
+          return Character.isJavaIdentifierStart(c);
+        }
+      });
+
+  private static final CharMatcher IDENTIFIER_REST =
+      CharMatcher.forPredicate(new Predicate<Character>() {
+        @Override public boolean apply(Character c) {
+          return Character.isJavaIdentifierPart(c);
+        }
+      });
+
+  private static final Function<URL, InputSupplier<? extends InputStream>> URL_TO_INPUT =
+      new Function<URL, InputSupplier<? extends InputStream>>() {
+        @Override public InputSupplier<? extends InputStream> apply(final URL resource) {
+          return new InputSupplier<InputStream>() {
+            @Override public InputStream getInput() throws IOException {
+              return resource.openStream();
+            }
+          };
+        }
+      };
+
+  private static final Function<InputSupplier<? extends InputStream>,
+                                InputSupplier<? extends Reader>> INPUT_TO_READER =
+      new Function<InputSupplier<? extends InputStream>, InputSupplier<? extends Reader>>() {
+        @Override public InputSupplier<? extends Reader> apply(
+            final InputSupplier<? extends InputStream> input) {
+          return CharStreams.newReaderSupplier(input, Charsets.UTF_8);
+        }
+      };
+
+  private static final Function<URL, InputSupplier<? extends Reader>> URL_TO_READER =
+      Functions.compose(INPUT_TO_READER, URL_TO_INPUT);
+
+  private static final String DEFAULT_RESOURCE_NAME = "cmdline.arg.info.txt";
+
+  private int nextResourceIndex;
+  private final ImmutableSet<ArgInfo> positionalInfos;
+  private final ImmutableSet<ArgInfo> cmdLineInfos;
+  private final ImmutableSet<ParserInfo> parserInfos;
+  private final ImmutableSet<VerifierInfo> verifierInfos;
+
+  private Configuration(int nextResourceIndex,
+      Iterable<ArgInfo> positionalInfos, Iterable<ArgInfo> cmdLineInfos,
+      Iterable<ParserInfo> parserInfos, Iterable<VerifierInfo> verifierInfos) {
+    this.nextResourceIndex = nextResourceIndex;
+    this.positionalInfos = ImmutableSet.copyOf(positionalInfos);
+    this.cmdLineInfos = ImmutableSet.copyOf(cmdLineInfos);
+    this.parserInfos = ImmutableSet.copyOf(parserInfos);
+    this.verifierInfos = ImmutableSet.copyOf(verifierInfos);
+  }
+
+  private static String checkValidIdentifier(String identifier, boolean compound) {
+    Preconditions.checkNotNull(identifier);
+
+    String trimmed = identifier.trim();
+    Preconditions.checkArgument(!trimmed.isEmpty(), "Invalid identifier: '%s'", identifier);
+
+    String[] parts = compound ? trimmed.split("\\.") : new String[] {trimmed};
+    for (String part : parts) {
+      Preconditions.checkArgument(
+          IDENTIFIER_REST.matchesAllOf(IDENTIFIER_START.trimLeadingFrom(part)),
+          "Invalid identifier: '%s'", identifier);
+    }
+
+    return trimmed;
+  }
+
+  public static final class ArgInfo {
+    public final String className;
+    public final String fieldName;
+
+    public ArgInfo(String className, String fieldName) {
+      this.className = checkValidIdentifier(className, true);
+      this.fieldName = checkValidIdentifier(fieldName, false);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+
+      if (!(obj instanceof ArgInfo)) {
+        return false;
+      }
+
+      ArgInfo other = (ArgInfo) obj;
+
+      return new EqualsBuilder()
+          .append(className, other.className)
+          .append(fieldName, other.fieldName)
+          .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+      return new HashCodeBuilder()
+          .append(className)
+          .append(fieldName)
+          .toHashCode();
+    }
+
+    @Override public String toString() {
+      return new ToStringBuilder(this)
+          .append("className", className)
+          .append("fieldName", fieldName)
+          .toString();
+    }
+  }
+
+  public static final class ParserInfo {
+    public final String parsedType;
+    public final String parserClass;
+
+    public ParserInfo(String parsedType, String parserClass) {
+      this.parsedType = checkValidIdentifier(parsedType, true);
+      this.parserClass = checkValidIdentifier(parserClass, true);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+
+      if (!(obj instanceof ParserInfo)) {
+        return false;
+      }
+
+      ParserInfo other = (ParserInfo) obj;
+
+      return new EqualsBuilder()
+          .append(parsedType, other.parsedType)
+          .append(parserClass, other.parserClass)
+          .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+      return new HashCodeBuilder()
+          .append(parsedType)
+          .append(parserClass)
+          .toHashCode();
+    }
+
+    @Override public String toString() {
+      return new ToStringBuilder(this)
+          .append("parsedType", parsedType)
+          .append("parserClass", parserClass)
+          .toString();
+    }
+  }
+
+  public static final class VerifierInfo {
+    public final String verifiedType;
+    public final String verifyingAnnotation;
+    public final String verifierClass;
+
+    public VerifierInfo(String verifiedType, String verifyingAnnotation, String verifierClass) {
+      this.verifiedType = checkValidIdentifier(verifiedType, true);
+      this.verifyingAnnotation = checkValidIdentifier(verifyingAnnotation, true);
+      this.verifierClass = checkValidIdentifier(verifierClass, true);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+
+      if (!(obj instanceof VerifierInfo)) {
+        return false;
+      }
+
+      VerifierInfo other = (VerifierInfo) obj;
+
+      return new EqualsBuilder()
+          .append(verifiedType, other.verifiedType)
+          .append(verifyingAnnotation, other.verifyingAnnotation)
+          .append(verifierClass, other.verifierClass)
+          .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+      return new HashCodeBuilder()
+          .append(verifiedType)
+          .append(verifyingAnnotation)
+          .append(verifierClass)
+          .toHashCode();
+    }
+
+    @Override public String toString() {
+      return new ToStringBuilder(this)
+          .append("verifiedType", verifiedType)
+          .append("verifyingAnnotation", verifyingAnnotation)
+          .append("verifierClass", verifierClass)
+          .toString();
+    }
+  }
+
+  static class Builder {
+    private final Set<ArgInfo> positionalInfos = Sets.newHashSet();
+    private final Set<ArgInfo> argInfos = Sets.newHashSet();
+    private final Set<ParserInfo> parserInfos = Sets.newHashSet();
+    private final Set<VerifierInfo> verifierInfos = Sets.newHashSet();
+
+    public boolean isEmpty() {
+      return positionalInfos.isEmpty()
+          && argInfos.isEmpty()
+          && parserInfos.isEmpty()
+          && verifierInfos.isEmpty();
+    }
+
+    void addPositionalInfo(ArgInfo positionalInfo) {
+      positionalInfos.add(positionalInfo);
+    }
+
+    void addCmdLineArg(ArgInfo argInfo) {
+      argInfos.add(argInfo);
+    }
+
+    void addParser(ParserInfo parserInfo) {
+      parserInfos.add(parserInfo);
+    }
+
+    public void addParser(String parserForType, String parserType) {
+      addParser(new ParserInfo(parserForType, parserType));
+    }
+
+    void addVerifier(VerifierInfo verifierInfo) {
+      verifierInfos.add(verifierInfo);
+    }
+
+    public void addVerifier(String verifierForType, String annotationType, String verifierType) {
+      addVerifier(new VerifierInfo(verifierForType, annotationType, verifierType));
+    }
+
+    public Configuration build(Configuration configuration) {
+      return new Configuration(configuration.nextResourceIndex + 1,
+          positionalInfos, argInfos, parserInfos, verifierInfos);
+    }
+  }
+
+  private static String getResourceName(int index) {
+    return String.format("%s.%s", DEFAULT_RESOURCE_NAME, index);
+  }
+
+  private static String getResourcePath(int index) {
+    return String.format("%s/%s", DEFAULT_RESOURCE_PACKAGE.replace('.', '/'),
+        getResourceName(index));
+  }
+
+  static final class ConfigurationResources {
+    private final int nextResourceIndex;
+    private final Iterator<URL> resources;
+
+    private ConfigurationResources(int nextResourceIndex, Iterator<URL> resources) {
+      this.nextResourceIndex = nextResourceIndex;
+      this.resources = resources;
+    }
+  }
+
+  /**
+   * Loads the {@literal @CmdLine} argument configuration data stored in the classpath.
+   *
+   * @return The {@literal @CmdLine} argument configuration materialized from the classpath.
+   * @throws ConfigurationException if any configuration data is malformed.
+   * @throws IOException if the configuration data can not be read from the classpath.
+   */
+  public static Configuration load() throws ConfigurationException, IOException {
+    ConfigurationResources allResources = getAllResources();
+    List<URL> configs = ImmutableList.copyOf(allResources.resources);
+    if (configs.isEmpty()) {
+      LOG.info("No @CmdLine arg configs found on the classpath");
+    } else {
+      LOG.info("Loading @CmdLine config from: " + configs);
+    }
+    return load(allResources.nextResourceIndex, configs);
+  }
+
+  private static ConfigurationResources getAllResources() throws IOException {
+    int maxResourceIndex = 0;
+    Iterator<URL> allResources = getResources(0); // Try for a main
+    // Probe for resource files with index up to 10 (or more, while resources at the
+    // given index can be found)
+    for (int nextResourceIndex = 1; nextResourceIndex <= maxResourceIndex + 10;
+         nextResourceIndex++) {
+      Iterator<URL> resources = getResources(nextResourceIndex);
+      if (resources.hasNext()) {
+        allResources = Iterators.concat(allResources, resources);
+        maxResourceIndex = nextResourceIndex;
+      }
+    }
+    return new ConfigurationResources(maxResourceIndex + 1, allResources);
+  }
+
+  private static Iterator<URL> getResources(int index) throws IOException {
+    return Iterators.forEnumeration(
+        Configuration.class.getClassLoader().getResources(getResourcePath(index)));
+  }
+
+  private static final class ConfigurationParser implements LineProcessor<Configuration> {
+    private final int nextIndex;
+    private int lineNumber = 0;
+
+    private final ImmutableList.Builder<ArgInfo> positionalInfo = ImmutableList.builder();
+    private final ImmutableList.Builder<ArgInfo> fieldInfoBuilder = ImmutableList.builder();
+    private final ImmutableList.Builder<ParserInfo> parserInfoBuilder = ImmutableList.builder();
+    private final ImmutableList.Builder<VerifierInfo> verifierInfoBuilder = ImmutableList.builder();
+
+    private ConfigurationParser(int nextIndex) {
+      this.nextIndex = nextIndex;
+    }
+
+    @Override
+    public boolean processLine(String line) throws IOException {
+      ++lineNumber;
+      String trimmed = line.trim();
+      if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
+        List<String> parts = Lists.newArrayList(trimmed.split(" "));
+        if (parts.size() < 1) {
+          throw new ConfigurationException("Invalid line: %s @%d", trimmed, lineNumber);
+        }
+
+        String type = parts.remove(0);
+        if ("positional".equals(type)) {
+          if (parts.size() != 2) {
+            throw new ConfigurationException(
+                "Invalid positional line: %s @%d", trimmed, lineNumber);
+          }
+          positionalInfo.add(new ArgInfo(parts.get(0), parts.get(1)));
+        } else if ("field".equals(type)) {
+          if (parts.size() != 2) {
+            throw new ConfigurationException("Invalid field line: %s @%d", trimmed, lineNumber);
+          }
+          fieldInfoBuilder.add(new ArgInfo(parts.get(0), parts.get(1)));
+        } else if ("parser".equals(type)) {
+          if (parts.size() != 2) {
+            throw new ConfigurationException("Invalid parser line: %s @%d", trimmed, lineNumber);
+          }
+          parserInfoBuilder.add(new ParserInfo(parts.get(0), parts.get(1)));
+        } else if ("verifier".equals(type)) {
+          if (parts.size() != 3) {
+            throw new ConfigurationException("Invalid verifier line: %s @%d", trimmed, lineNumber);
+          }
+          verifierInfoBuilder.add(new VerifierInfo(parts.get(0), parts.get(1), parts.get(2)));
+        } else {
+          LOG.warning(String.format("Did not recognize entry type %s for line: %s @%d",
+              type, trimmed, lineNumber));
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public Configuration getResult() {
+      return new Configuration(nextIndex, positionalInfo.build(),
+          fieldInfoBuilder.build(), parserInfoBuilder.build(), verifierInfoBuilder.build());
+    }
+  }
+
+  private static Configuration load(int nextIndex, List<URL> configs)
+      throws ConfigurationException, IOException {
+    InputSupplier<Reader> input = CharStreams.join(Iterables.transform(configs, URL_TO_READER));
+    return CharStreams.readLines(input, new ConfigurationParser(nextIndex));
+  }
+
+  public boolean isEmpty() {
+    return positionalInfos.isEmpty()
+        && cmdLineInfos.isEmpty()
+        && parserInfos.isEmpty()
+        && verifierInfos.isEmpty();
+  }
+
+  /**
+   * Returns the field info for the sole {@literal @Positional} annotated field on the classpath,
+   * if any.
+   *
+   * @return The field info for the {@literal @Positional} annotated field if any.
+   */
+  public Iterable<ArgInfo> positionalInfo() {
+    return positionalInfos;
+  }
+
+  /**
+   * Returns the field info for all the {@literal @CmdLine} annotated fields on the classpath.
+   *
+   * @return The field info for all the {@literal @CmdLine} annotated fields.
+   */
+  public Iterable<ArgInfo> optionInfo() {
+    return cmdLineInfos;
+  }
+
+  /**
+   * Returns the parser info for all the {@literal @ArgParser} annotated parsers on the classpath.
+   *
+   * @return The parser info for all the {@literal @ArgParser} annotated parsers.
+   */
+  public Iterable<ParserInfo> parserInfo() {
+    return parserInfos;
+  }
+
+  /**
+   * Returns the verifier info for all the {@literal @VerifierFor} annotated verifiers on the
+   * classpath.
+   *
+   * @return The verifier info for all the {@literal @VerifierFor} annotated verifiers.
+   */
+  public Iterable<VerifierInfo> verifierInfo() {
+    return verifierInfos;
+  }
+
+  static String mainResourceName() {
+    return getResourceName(0);
+  }
+
+  String nextResourceName() {
+    return getResourceName(nextResourceIndex);
+  }
+
+  void store(Writer output, String message) {
+    PrintWriter writer = new PrintWriter(output);
+    writer.printf("# %s\n", new Date());
+    writer.printf("# %s\n ", message);
+
+    writer.println();
+    for (ArgInfo info : positionalInfos) {
+      writer.printf("positional %s %s\n", info.className, info.fieldName);
+    }
+
+    writer.println();
+    for (ArgInfo info : cmdLineInfos) {
+      writer.printf("field %s %s\n", info.className, info.fieldName);
+    }
+
+    writer.println();
+    for (ParserInfo info : parserInfos) {
+      writer.printf("parser %s %s\n", info.parsedType, info.parserClass);
+    }
+
+    writer.println();
+    for (VerifierInfo info : verifierInfos) {
+      writer.printf("verifier %s %s %s\n",
+          info.verifiedType, info.verifyingAnnotation, info.verifierClass);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons-args/src/main/resources/META-INF/services/javax.annotation.processing.Processor
----------------------------------------------------------------------
diff --git a/commons-args/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/commons-args/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..bc5a756
--- /dev/null
+++ b/commons-args/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+com.twitter.common.args.apt.CmdLineProcessor

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/application/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/AbstractApplication.java b/commons/src/main/java/com/twitter/common/application/AbstractApplication.java
new file mode 100644
index 0000000..239a9ef
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/AbstractApplication.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// 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;
+
+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/86a547b9/commons/src/main/java/com/twitter/common/application/AppLauncher.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/AppLauncher.java b/commons/src/main/java/com/twitter/common/application/AppLauncher.java
new file mode 100644
index 0000000..6b4ccc3
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/AppLauncher.java
@@ -0,0 +1,205 @@
+// =================================================================================================
+// 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;
+
+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 com.twitter.common.application.modules.AppLauncherModule;
+import com.twitter.common.application.modules.LifecycleModule;
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.ArgFilters;
+import com.twitter.common.args.ArgScanner;
+import com.twitter.common.args.ArgScanner.ArgScanException;
+import com.twitter.common.args.CmdLine;
+import com.twitter.common.args.constraints.NotNull;
+import com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/Application.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/Application.java b/commons/src/main/java/com/twitter/common/application/Application.java
new file mode 100644
index 0000000..c3203c0
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/Application.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// 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;
+
+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/86a547b9/commons/src/main/java/com/twitter/common/application/Lifecycle.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/Lifecycle.java b/commons/src/main/java/com/twitter/common/application/Lifecycle.java
new file mode 100644
index 0000000..28a667d
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/Lifecycle.java
@@ -0,0 +1,97 @@
+// =================================================================================================
+// 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;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+import com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/ShutdownRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/ShutdownRegistry.java b/commons/src/main/java/com/twitter/common/application/ShutdownRegistry.java
new file mode 100644
index 0000000..993d273
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/ShutdownRegistry.java
@@ -0,0 +1,102 @@
+// =================================================================================================
+// 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;
+
+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 com.twitter.common.base.Command;
+import com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/ShutdownStage.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/ShutdownStage.java b/commons/src/main/java/com/twitter/common/application/ShutdownStage.java
new file mode 100644
index 0000000..8c8b2bd
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/ShutdownStage.java
@@ -0,0 +1,34 @@
+// =================================================================================================
+// 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;
+
+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/86a547b9/commons/src/main/java/com/twitter/common/application/StartupRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/StartupRegistry.java b/commons/src/main/java/com/twitter/common/application/StartupRegistry.java
new file mode 100644
index 0000000..0643aff
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/StartupRegistry.java
@@ -0,0 +1,55 @@
+// =================================================================================================
+// 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;
+
+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 com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/StartupStage.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/StartupStage.java b/commons/src/main/java/com/twitter/common/application/StartupStage.java
new file mode 100644
index 0000000..b8e6a52
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/StartupStage.java
@@ -0,0 +1,34 @@
+// =================================================================================================
+// 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;
+
+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/86a547b9/commons/src/main/java/com/twitter/common/application/http/DefaultQuitHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/http/DefaultQuitHandler.java b/commons/src/main/java/com/twitter/common/application/http/DefaultQuitHandler.java
new file mode 100644
index 0000000..2b5d0d5
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/http/DefaultQuitHandler.java
@@ -0,0 +1,46 @@
+// =================================================================================================
+// 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.http;
+
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+import com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/http/GraphViewer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/http/GraphViewer.java b/commons/src/main/java/com/twitter/common/application/http/GraphViewer.java
new file mode 100644
index 0000000..5313c7e
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/http/GraphViewer.java
@@ -0,0 +1,53 @@
+// =================================================================================================
+// 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.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/86a547b9/commons/src/main/java/com/twitter/common/application/http/HttpAssetConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/http/HttpAssetConfig.java b/commons/src/main/java/com/twitter/common/application/http/HttpAssetConfig.java
new file mode 100644
index 0000000..4940751
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/http/HttpAssetConfig.java
@@ -0,0 +1,54 @@
+// =================================================================================================
+// 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.http;
+
+import java.net.URL;
+
+import com.google.common.io.Resources;
+
+import com.twitter.common.net.http.handlers.AssetHandler;
+import com.twitter.common.net.http.handlers.AssetHandler.StaticAsset;
+
+import static com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/http/HttpFilterConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/http/HttpFilterConfig.java b/commons/src/main/java/com/twitter/common/application/http/HttpFilterConfig.java
new file mode 100644
index 0000000..864c621
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/http/HttpFilterConfig.java
@@ -0,0 +1,26 @@
+package com.twitter.common.application.http;
+
+import javax.servlet.Filter;
+
+import com.twitter.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/86a547b9/commons/src/main/java/com/twitter/common/application/http/HttpServletConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/application/http/HttpServletConfig.java b/commons/src/main/java/com/twitter/common/application/http/HttpServletConfig.java
new file mode 100644
index 0000000..00479f0
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/application/http/HttpServletConfig.java
@@ -0,0 +1,68 @@
+// =================================================================================================
+// 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.http;
+
+import javax.servlet.http.HttpServlet;
+
+import com.google.common.collect.ImmutableMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import static com.twitter.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;
+  }
+}


Mime
View raw message