diff --git a/Blazar.yaml b/Blazar.yaml new file mode 100644 index 00000000..52b0c627 --- /dev/null +++ b/Blazar.yaml @@ -0,0 +1,6 @@ +server: + type: simple + applicationContextPath: /blazar + connector: + type: http + port: 7099 diff --git a/BlazarBase/pom.xml b/BlazarBase/pom.xml new file mode 100644 index 00000000..6f978486 --- /dev/null +++ b/BlazarBase/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + + com.hubspot + Blazar + 1.0-SNAPSHOT + + + + BlazarBase + + + + com.fasterxml.jackson.core + jackson-annotations + + + diff --git a/BlazarBase/src/main/java/com/hubspot/blazar/BuildState.java b/BlazarBase/src/main/java/com/hubspot/blazar/BuildState.java new file mode 100644 index 00000000..b287b6e8 --- /dev/null +++ b/BlazarBase/src/main/java/com/hubspot/blazar/BuildState.java @@ -0,0 +1,35 @@ +package com.hubspot.blazar; + +import java.util.Optional; + +public class BuildState { + public enum Result { SUCCEEDED, IN_PROGRESS, FAILED } + + private final int buildNumber; + private final Result result; + private final long startTime; + private final Optional endTime; + + public BuildState(int buildNumber, Result result, long startTime, Optional endTime) { + this.buildNumber = buildNumber; + this.result = result; + this.startTime = startTime; + this.endTime = endTime; + } + + public int getBuildNumber() { + return buildNumber; + } + + public Result getResult() { + return result; + } + + public long getStartTime() { + return startTime; + } + + public Optional getEndTime() { + return endTime; + } +} diff --git a/BlazarBase/src/main/java/com/hubspot/blazar/GitInfo.java b/BlazarBase/src/main/java/com/hubspot/blazar/GitInfo.java new file mode 100644 index 00000000..57be3905 --- /dev/null +++ b/BlazarBase/src/main/java/com/hubspot/blazar/GitInfo.java @@ -0,0 +1,38 @@ +package com.hubspot.blazar; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class GitInfo { + private final String host; + private final String organization; + private final String repository; + private final String branch; + + @JsonCreator + public GitInfo(@JsonProperty("host") String host, + @JsonProperty("organization") String organization, + @JsonProperty("repository") String repository, + @JsonProperty("branch") String branch) { + this.host = host; + this.organization = organization; + this.repository = repository; + this.branch = branch; + } + + public String getHost() { + return host; + } + + public String getOrganization() { + return organization; + } + + public String getRepository() { + return repository; + } + + public String getBranch() { + return branch; + } +} diff --git a/BlazarBase/src/main/java/com/hubspot/blazar/Module.java b/BlazarBase/src/main/java/com/hubspot/blazar/Module.java new file mode 100644 index 00000000..abecdcf6 --- /dev/null +++ b/BlazarBase/src/main/java/com/hubspot/blazar/Module.java @@ -0,0 +1,23 @@ +package com.hubspot.blazar; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Module { + private final String name; + private final String path; + + @JsonCreator + public Module(@JsonProperty("name") String name, @JsonProperty("path") String path) { + this.name = name; + this.path = path; + } + + public String getName() { + return name; + } + + public String getPath() { + return path; + } +} diff --git a/BlazarBase/src/main/java/com/hubspot/blazar/ModuleBuild.java b/BlazarBase/src/main/java/com/hubspot/blazar/ModuleBuild.java new file mode 100644 index 00000000..5a9221f8 --- /dev/null +++ b/BlazarBase/src/main/java/com/hubspot/blazar/ModuleBuild.java @@ -0,0 +1,23 @@ +package com.hubspot.blazar; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ModuleBuild { + private final GitInfo gitInfo; + private final Module module; + + @JsonCreator + public ModuleBuild(@JsonProperty("gitInfo") GitInfo gitInfo, @JsonProperty("module") Module module) { + this.gitInfo = gitInfo; + this.module = module; + } + + public GitInfo getGitInfo() { + return gitInfo; + } + + public Module getModule() { + return module; + } +} diff --git a/BlazarBase/src/main/java/com/hubspot/blazar/RepoBuild.java b/BlazarBase/src/main/java/com/hubspot/blazar/RepoBuild.java new file mode 100644 index 00000000..3da1c429 --- /dev/null +++ b/BlazarBase/src/main/java/com/hubspot/blazar/RepoBuild.java @@ -0,0 +1,25 @@ +package com.hubspot.blazar; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Set; + +public class RepoBuild { + private final GitInfo gitInfo; + private final Set modules; + + @JsonCreator + public RepoBuild(@JsonProperty("gitInfo") GitInfo gitInfo, @JsonProperty("modules") Set modules) { + this.gitInfo = gitInfo; + this.modules = modules; + } + + public GitInfo getGitInfo() { + return gitInfo; + } + + public Set getModules() { + return modules; + } +} diff --git a/BlazarService/pom.xml b/BlazarService/pom.xml new file mode 100644 index 00000000..526b2d26 --- /dev/null +++ b/BlazarService/pom.xml @@ -0,0 +1,222 @@ + + + 4.0.0 + + + com.hubspot + Blazar + 1.0-SNAPSHOT + + + BlazarService + + + + com.hubspot.jackson + jackson-jaxrs-propertyfiltering + + + com.hubspot.jackson + jackson-datatype-protobuf + + + com.google.inject + guice + + + com.google.inject.extensions + guice-servlet + + + com.google.inject.extensions + guice-multibindings + + + com.google.guava + guava + + + com.google.protobuf + protobuf-java + + + javax.inject + javax.inject + + + com.sun.mail + javax.mail + + + javax.servlet + javax.servlet-api + + + javax.ws.rs + jsr311-api + + + javax.validation + validation-api + + + org.slf4j + slf4j-api + + + + org.slf4j + jcl-over-slf4j + runtime + + + ch.qos.logback + logback-classic + + + aopalliance + aopalliance + + + org.apache.mesos + mesos + + + com.groupon.mesos + jesos + + + org.apache.commons + commons-lang3 + + + org.apache.curator + curator-client + + + org.apache.curator + curator-framework + + + org.apache.zookeeper + zookeeper + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-servlets + + + de.neuland-bfi + jade4j + + + com.ning + async-http-client + + + io.dropwizard + dropwizard-servlets + + + io.dropwizard + dropwizard-jdbi + + + io.dropwizard + dropwizard-migrations + + + io.dropwizard + dropwizard-assets + + + io.dropwizard + dropwizard-core + + + io.dropwizard + dropwizard-db + + + io.dropwizard + dropwizard-jackson + + + io.dropwizard + dropwizard-jersey + + + io.dropwizard + dropwizard-jetty + + + io.dropwizard + dropwizard-lifecycle + + + io.dropwizard + dropwizard-views + + + io.dropwizard + dropwizard-views-mustache + runtime + + + com.codahale.metrics + metrics-core + + + com.codahale.metrics + metrics-healthchecks + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-guava + + + com.sun.jersey + jersey-core + + + com.sun.jersey + jersey-server + + + com.sun.jersey + jersey-servlet + + + com.sun.jersey.contribs + jersey-guice + + + org.hibernate + hibernate-validator + + + org.jdbi + jdbi + + + com.google.code.findbugs + annotations + + + diff --git a/BlazarService/src/main/java/com/hubspot/blazar/BlazarConfiguration.java b/BlazarService/src/main/java/com/hubspot/blazar/BlazarConfiguration.java new file mode 100644 index 00000000..ef688144 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/BlazarConfiguration.java @@ -0,0 +1,6 @@ +package com.hubspot.blazar; + +import io.dropwizard.Configuration; + +public class BlazarConfiguration extends Configuration { +} diff --git a/BlazarService/src/main/java/com/hubspot/blazar/BlazarService.java b/BlazarService/src/main/java/com/hubspot/blazar/BlazarService.java new file mode 100644 index 00000000..ccee87c2 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/BlazarService.java @@ -0,0 +1,36 @@ +package com.hubspot.blazar; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.hubspot.jackson.datatype.protobuf.ProtobufModule; +import io.dropwizard.Application; +import io.dropwizard.ConfiguredBundle; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; + +public class BlazarService extends Application { + + @Override + public void initialize(final Bootstrap bootstrap) { + bootstrap.addBundle(buildGuiceBundle()); + bootstrap.getObjectMapper().registerModule(new ProtobufModule()); + bootstrap.getObjectMapper().setSerializationInclusion(Include.NON_NULL); + bootstrap.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public void run(final T configuration, final Environment environment) {} + + private ConfiguredBundle buildGuiceBundle() { + return GuiceBundle.defaultBuilder(BlazarConfiguration.class).modules(new BlazarServiceModule()).build(); + } + + public static void main(String... args) throws Exception { + try { + new BlazarService().run(args); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } +} diff --git a/BlazarService/src/main/java/com/hubspot/blazar/BlazarServiceModule.java b/BlazarService/src/main/java/com/hubspot/blazar/BlazarServiceModule.java new file mode 100644 index 00000000..d0c32327 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/BlazarServiceModule.java @@ -0,0 +1,11 @@ +package com.hubspot.blazar; + +import com.google.inject.AbstractModule; + +public class BlazarServiceModule extends AbstractModule { + + @Override + protected void configure() { + bind(TestResource.class); + } +} diff --git a/BlazarService/src/main/java/com/hubspot/blazar/ConfigurationAwareModule.java b/BlazarService/src/main/java/com/hubspot/blazar/ConfigurationAwareModule.java new file mode 100644 index 00000000..c3c89141 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/ConfigurationAwareModule.java @@ -0,0 +1,28 @@ +package com.hubspot.blazar; + +import com.google.inject.Binder; +import com.google.inject.Module; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public abstract class ConfigurationAwareModule implements Module { + private volatile Configuration configuration = null; + + @Override + public void configure(Binder binder) { + configure(binder, getConfiguration()); + } + + public void setConfiguration(Configuration configuration) { + checkState(this.configuration == null, "configuration was already set!"); + this.configuration = checkNotNull(configuration, "configuration is null"); + } + + protected Configuration getConfiguration() { + return checkNotNull(this.configuration, "configuration was not set!"); + } + + protected abstract void configure(final Binder binder, final Configuration configuration); + +} diff --git a/BlazarService/src/main/java/com/hubspot/blazar/DropwizardGuiceContainer.java b/BlazarService/src/main/java/com/hubspot/blazar/DropwizardGuiceContainer.java new file mode 100644 index 00000000..50f678c8 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/DropwizardGuiceContainer.java @@ -0,0 +1,32 @@ +package com.hubspot.blazar; + +import com.google.inject.Injector; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; +import com.sun.jersey.spi.container.servlet.WebConfig; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.dropwizard.setup.Environment; + +import javax.inject.Inject; +import javax.servlet.ServletException; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class DropwizardGuiceContainer extends GuiceContainer { + @SuppressFBWarnings("BAD_PRACTICE") + private final Environment environment; + + private static final long serialVersionUID = 1L; + + @Inject + public DropwizardGuiceContainer(final Injector injector, final Environment environment) { + super(injector); + this.environment = checkNotNull(environment, "environment is null"); + } + + @Override + protected ResourceConfig getDefaultResourceConfig(final Map props, final WebConfig webConfig) throws ServletException { + return environment.jersey().getResourceConfig(); + } +} diff --git a/BlazarService/src/main/java/com/hubspot/blazar/GuiceBundle.java b/BlazarService/src/main/java/com/hubspot/blazar/GuiceBundle.java new file mode 100644 index 00000000..b0c884c6 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/GuiceBundle.java @@ -0,0 +1,272 @@ +package com.hubspot.blazar; + +import com.codahale.metrics.health.HealthCheck; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeParameter; +import com.google.common.reflect.TypeToken; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Scopes; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matchers; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.servlet.GuiceFilter; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.jersey.guice.JerseyServletModule; +import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; +import com.sun.jersey.spi.container.ContainerRequestFilter; +import com.sun.jersey.spi.container.ContainerResponseFilter; +import com.sun.jersey.spi.container.ResourceFilterFactory; +import com.sun.jersey.spi.container.servlet.ServletContainer; +import io.dropwizard.Configuration; +import io.dropwizard.ConfiguredBundle; +import io.dropwizard.lifecycle.Managed; +import io.dropwizard.lifecycle.ServerLifecycleListener; +import io.dropwizard.servlets.tasks.Task; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public class GuiceBundle implements ConfiguredBundle { + private static final String GUICE_BUNDLE_NAME = "_guice_bundle"; + private static final Named GUICE_BUNDLE_NAMED = Names.named(GUICE_BUNDLE_NAME); + + private static final Logger LOG = LoggerFactory.getLogger(GuiceBundle.class); + + public static Builder defaultBuilder(final Class configClass) { + return new Builder<>(configClass); + } + + private final Class configClass; + private final ImmutableSet> configurationAwareModules; + private final ImmutableSet guiceModules; + private final Stage guiceStage; + + @Inject + @Named(GUICE_BUNDLE_NAME) + private volatile Function replacer = null; + + private GuiceBundle(final Class configClass, final ImmutableSet guiceModules, final ImmutableSet> configurationAwareModules, final Stage guiceStage) { + this.configClass = configClass; + + this.guiceModules = guiceModules; + this.configurationAwareModules = configurationAwareModules; + this.guiceStage = guiceStage; + } + + @Override + public void initialize(final Bootstrap bootstrap) {} + + @Override + public void run(final T configuration, final Environment environment) throws Exception { + + for (ConfigurationAwareModule configurationAwareModule : configurationAwareModules) { + configurationAwareModule.setConfiguration(configuration); + } + + final DropwizardModule dropwizardModule = new DropwizardModule(); + + final Injector injector = + Guice.createInjector(guiceStage, + ImmutableSet.builder() + .addAll(guiceModules) + .addAll(configurationAwareModules) + .add(new GuiceEnforcerModule()) + .add(new JerseyServletModule()) + .add(dropwizardModule) + .add(new Module() { + @Override + public void configure(final Binder binder) { + binder.bind(Environment.class).toInstance(environment); + binder.bind(configClass).toInstance(configuration); + + binder.bind(GuiceContainer.class).to(DropwizardGuiceContainer.class).in(Scopes.SINGLETON); + + binder.bind(new TypeLiteral>() {}) + .annotatedWith(GUICE_BUNDLE_NAMED) + .to(GuiceContainerReplacer.class) + .in(Scopes.SINGLETON); + } + }).build()); + + injector.injectMembers(this); + checkState(replacer != null, "No guice container replacer was injected!"); + + for (Managed managed : dropwizardModule.getManaged()) { + LOG.info("Added guice injected managed Object: {}", managed.getClass().getName()); + environment.lifecycle().manage(managed); + } + + for (Task task : dropwizardModule.getTasks()) { + environment.admin().addTask(task); + LOG.info("Added guice injected Task: {}", task.getClass().getName()); + } + + for (HealthCheck healthcheck : dropwizardModule.getHealthChecks()) { + environment.healthChecks().register(healthcheck.getClass().getSimpleName(), healthcheck); + LOG.info("Added guice injected health check: {}", healthcheck.getClass().getName()); + } + + for (ServerLifecycleListener serverLifecycleListener : dropwizardModule.getServerLifecycleListeners()) { + environment.lifecycle().addServerLifecycleListener(serverLifecycleListener); + } + + addJerseyBindings(environment, injector, ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, ContainerRequestFilter.class); + addJerseyBindings(environment, injector, ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ContainerResponseFilter.class); + addJerseyBindings(environment, injector, ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, ResourceFilterFactory.class); + + environment.jersey().replace(replacer); + environment.servlets().addFilter("Guice Filter", GuiceFilter.class).addMappingForUrlPatterns(null, false, environment.getApplicationContext().getContextPath() + "*"); + } + + @SuppressWarnings("serial") + private static void addJerseyBindings(Environment environment, Injector injector, String propertyName, Class clazz) { + TypeToken> setToken = new TypeToken>() {}.where(new TypeParameter() {}, clazz); + + @SuppressWarnings("unchecked") + Key> key = (Key>) Key.get(setToken.getType()); + + Binding> binding = injector.getExistingBinding(key); + + if (binding != null) { + Set values = injector.getInstance(key); + environment.jersey().property(propertyName, ImmutableList.copyOf(values)); + } + } + + private static class GuiceContainerReplacer implements Function { + private final GuiceContainer container; + + @Inject + GuiceContainerReplacer(final GuiceContainer container) { + this.container = checkNotNull(container, "container is null"); + } + + @Override + public ServletContainer apply(@Nonnull final ResourceConfig resourceConfig) { + return container; + } + } + + public static class GuiceEnforcerModule implements Module { + @Override + public void configure(final Binder binder) { + binder.disableCircularProxies(); + binder.requireExplicitBindings(); + binder.requireExactBindingAnnotations(); + binder.requireAtInjectOnConstructors(); + } + } + + public static class DropwizardModule implements Module { + private final ImmutableSet.Builder managedBuilder = ImmutableSet.builder(); + private final ImmutableSet.Builder taskBuilder = ImmutableSet.builder(); + private final ImmutableSet.Builder healthcheckBuilder = ImmutableSet.builder(); + private final ImmutableSet.Builder serverLifecycleListenerBuilder = ImmutableSet.builder(); + + @Override + public void configure(final Binder binder) { + binder.bindListener(Matchers.any(), new TypeListener() { + @Override + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register(new InjectionListener() { + @Override + public void afterInjection(T obj) { + if (obj instanceof Managed) { + managedBuilder.add((Managed) obj); + } + + if (obj instanceof Task) { + taskBuilder.add((Task) obj); + } + + if (obj instanceof HealthCheck) { + healthcheckBuilder.add((HealthCheck) obj); + } + + if (obj instanceof ServerLifecycleListener) { + serverLifecycleListenerBuilder.add((ServerLifecycleListener) obj); + } + } + }); + } + }); + } + + public Set getManaged() { + return managedBuilder.build(); + } + + public Set getTasks() { + return taskBuilder.build(); + } + + public Set getHealthChecks() { + return healthcheckBuilder.build(); + } + + public Set getServerLifecycleListeners() { + return serverLifecycleListenerBuilder.build(); + } + } + + public static class Builder { + private final Class configClass; + private final ImmutableSet.Builder guiceModules = ImmutableSet.builder(); + private final ImmutableSet.Builder> configurationAwareModules = ImmutableSet.builder(); + private Stage guiceStage = Stage.PRODUCTION; + + private Builder(final Class configClass) { + this.configClass = configClass; + } + + public final Builder stage(final Stage guiceStage) { + checkNotNull(guiceStage, "guiceStage is null"); + if (guiceStage != Stage.PRODUCTION) { + LOG.warn("Guice should only ever run in PRODUCTION mode except for testing!"); + } + this.guiceStage = guiceStage; + return this; + } + + public final Builder modules(final Module... modules) { + return modules(Arrays.asList(modules)); + } + + @SuppressWarnings("unchecked") + public final Builder modules(final Iterable modules) { + for (Module module : modules) { + if (module instanceof ConfigurationAwareModule) { + configurationAwareModules.add((ConfigurationAwareModule) module); + } else { + guiceModules.add(module); + } + } + return this; + } + + public final GuiceBundle build() { + return new GuiceBundle(configClass, guiceModules.build(), configurationAwareModules.build(), guiceStage); + } + } +} diff --git a/BlazarService/src/main/java/com/hubspot/blazar/TestResource.java b/BlazarService/src/main/java/com/hubspot/blazar/TestResource.java new file mode 100644 index 00000000..a33e2dd6 --- /dev/null +++ b/BlazarService/src/main/java/com/hubspot/blazar/TestResource.java @@ -0,0 +1,21 @@ +package com.hubspot.blazar; + +import com.google.inject.Inject; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/test") +@Produces(MediaType.APPLICATION_JSON) +public class TestResource { + + @Inject + public TestResource() {} + + @GET + public String test() { + return "hello world!"; + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..48976df7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + com.hubspot + basepom + 10.7 + + + Blazar + 1.0-SNAPSHOT + pom + + + scm:git:git@github.com:HubSpot/Blazar.git + scm:git:git@github.com:HubSpot/Blazar.git + git@github.com:HubSpot/Blazar.git + + + + + Jonathan Haber + jhaber@hubspot.com + + + + + 1.19 + + + + BlazarBase + BlazarService + + + + + + com.hubspot + BlazarBase + ${project.version} + + + com.hubspot + BlazarService + ${project.version} + + + javax.ws.rs + jsr311-api + 1.1.1 + + + com.hubspot.jackson + jackson-jaxrs-propertyfiltering + 0.7.0 + + + com.hubspot.jackson + jackson-datatype-protobuf + 0.8.0 + + + +