http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/apt/CmdLineProcessor.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/apt/CmdLineProcessor.java b/commons-args/src/main/java/org/apache/aurora/common/args/apt/CmdLineProcessor.java new file mode 100644 index 0000000..5abb06b --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/apt/CmdLineProcessor.java @@ -0,0 +1,677 @@ +/** + * 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.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 org.apache.aurora.common.args.Arg; +import org.apache.aurora.common.args.ArgParser; +import org.apache.aurora.common.args.CmdLine; +import org.apache.aurora.common.args.Parser; +import org.apache.aurora.common.args.Positional; +import org.apache.aurora.common.args.Verifier; +import org.apache.aurora.common.args.VerifierFor; +import org.apache.aurora.common.args.apt.Configuration.ParserInfo; + +import static org.apache.aurora.common.args.apt.Configuration.ArgInfo; +import static org.apache.aurora.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 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/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/apt/Configuration.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/apt/Configuration.java b/commons-args/src/main/java/org/apache/aurora/common/args/apt/Configuration.java new file mode 100644 index 0000000..e8e6447 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/apt/Configuration.java @@ -0,0 +1,527 @@ +/** + * 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.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/06ddaadb/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 index bc5a756..b548fcd 100644 --- 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 @@ -1 +1 @@ -com.twitter.common.args.apt.CmdLineProcessor +org.apache.aurora.common.args.apt.CmdLineProcessor http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/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 deleted file mode 100644 index 17514e3..0000000 --- a/commons/src/main/java/com/twitter/common/application/AbstractApplication.java +++ /dev/null @@ -1,29 +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.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/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 deleted file mode 100644 index 700454f..0000000 --- a/commons/src/main/java/com/twitter/common/application/AppLauncher.java +++ /dev/null @@ -1,202 +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.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/06ddaadb/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 deleted file mode 100644 index 5a2a9b8..0000000 --- a/commons/src/main/java/com/twitter/common/application/Application.java +++ /dev/null @@ -1,29 +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.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/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 deleted file mode 100644 index 5c7af1f..0000000 --- a/commons/src/main/java/com/twitter/common/application/Lifecycle.java +++ /dev/null @@ -1,94 +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.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/06ddaadb/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 deleted file mode 100644 index dc20861..0000000 --- a/commons/src/main/java/com/twitter/common/application/ShutdownRegistry.java +++ /dev/null @@ -1,99 +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.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/06ddaadb/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 deleted file mode 100644 index 09a7229..0000000 --- a/commons/src/main/java/com/twitter/common/application/ShutdownStage.java +++ /dev/null @@ -1,31 +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.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/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 deleted file mode 100644 index 943c7af..0000000 --- a/commons/src/main/java/com/twitter/common/application/StartupRegistry.java +++ /dev/null @@ -1,52 +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.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/06ddaadb/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 deleted file mode 100644 index 0938cee..0000000 --- a/commons/src/main/java/com/twitter/common/application/StartupStage.java +++ /dev/null @@ -1,31 +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.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/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 deleted file mode 100644 index 544b4dc..0000000 --- a/commons/src/main/java/com/twitter/common/application/http/DefaultQuitHandler.java +++ /dev/null @@ -1,43 +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.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/06ddaadb/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 deleted file mode 100644 index adb1ddc..0000000 --- a/commons/src/main/java/com/twitter/common/application/http/GraphViewer.java +++ /dev/null @@ -1,50 +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.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/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 deleted file mode 100644 index ed7a2bd..0000000 --- a/commons/src/main/java/com/twitter/common/application/http/HttpAssetConfig.java +++ /dev/null @@ -1,51 +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.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/06ddaadb/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 deleted file mode 100644 index 59b746f..0000000 --- a/commons/src/main/java/com/twitter/common/application/http/HttpFilterConfig.java +++ /dev/null @@ -1,39 +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.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/06ddaadb/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 deleted file mode 100644 index d3b8829..0000000 --- a/commons/src/main/java/com/twitter/common/application/http/HttpServletConfig.java +++ /dev/null @@ -1,65 +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.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; - } -} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/com/twitter/common/application/http/Registration.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/application/http/Registration.java b/commons/src/main/java/com/twitter/common/application/http/Registration.java deleted file mode 100644 index fef5d09..0000000 --- a/commons/src/main/java/com/twitter/common/application/http/Registration.java +++ /dev/null @@ -1,155 +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.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)); - } -}
