All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.dropwizard.guicier.GuiceBundle Maven / Gradle / Ivy

package com.hubspot.dropwizard.guicier;

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;

/**
 * @author Henning P. Schmiedehausen
 */
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);
    }
  }
}