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

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

There is a newer version: 1.3.5.2
Show newest version
package com.hubspot.dropwizard.guicier;

import static com.google.common.base.Preconditions.checkNotNull;

import java.lang.reflect.Field;
import java.util.Arrays;

import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.internal.ServiceLocatorFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;
import com.google.inject.util.Modules;
import com.squarespace.jersey2.guice.JerseyGuiceModule;
import com.squarespace.jersey2.guice.JerseyGuiceUtils;

import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

/**
 * @author Henning P. Schmiedehausen
 */
public class GuiceBundle implements ConfiguredBundle {
  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> dropwizardAwareModules;
  private final ImmutableSet guiceModules;
  private final Stage guiceStage;
  private final boolean allowUnknownFields;
  private final boolean enableGuiceEnforcer;
  private final InjectorFactory injectorFactory;

  private Bootstrap bootstrap = null;
  private Injector injector = null;

  private GuiceBundle(final Class configClass,
                      final ImmutableSet guiceModules,
                      final ImmutableSet> dropwizardAwareModules,
                      final Stage guiceStage,
                      final boolean allowUnknownFields,
                      final boolean enableGuiceEnforcer,
                      final InjectorFactory injectorFactory) {
    this.configClass = configClass;

    this.guiceModules = guiceModules;
    this.dropwizardAwareModules = dropwizardAwareModules;
    this.guiceStage = guiceStage;
    this.allowUnknownFields = allowUnknownFields;
    this.enableGuiceEnforcer = enableGuiceEnforcer;
    this.injectorFactory = injectorFactory;
  }

  @Override
  public void initialize(final Bootstrap bootstrap) {
    this.bootstrap = bootstrap;
    if (allowUnknownFields) {
      AllowUnknownFieldsObjectMapper.applyTo(bootstrap);
    }
  }

  @Override
  public void run(final T configuration, final Environment environment) throws Exception {
    for (DropwizardAwareModule dropwizardAwareModule : dropwizardAwareModules) {
      dropwizardAwareModule.setBootstrap(bootstrap);
      dropwizardAwareModule.setConfiguration(configuration);
      dropwizardAwareModule.setEnvironment(environment);
    }

    final DropwizardModule dropwizardModule = new DropwizardModule(environment);
    // We assume that the next service locator will be the main application one
    final String serviceLocatorName = getNextServiceLocatorName();
    ImmutableSet.Builder modulesBuilder =
        ImmutableSet.builder()
            .addAll(guiceModules)
            .addAll(dropwizardAwareModules)
            .add(new ServletModule())
            .add(dropwizardModule)
            .add(new JerseyGuiceModule(serviceLocatorName))
            .add(new JerseyGuicierModule())
            .add(new Module() {
              @Override
              public void configure(final Binder binder) {
                binder.bind(Environment.class).toInstance(environment);
                binder.bind(configClass).toInstance(configuration);
              }
            });
    if (enableGuiceEnforcer) {
      modulesBuilder.add(new GuiceEnforcerModule());
    }
    this.injector = injectorFactory.create(guiceStage, Modules.combine(modulesBuilder.build()));

    JerseyGuiceUtils.install((name, parent) -> {
      if (!name.startsWith("__HK2_")) {
        return null;
      } else if (serviceLocatorName.equals(name)) {
        return injector.getInstance(ServiceLocator.class);
      } else {
        LOG.debug("Returning a new ServiceLocator for name '%s'", name);
        return JerseyGuiceUtils.newServiceLocator(name, parent);
      }
    });

    dropwizardModule.register(injector);

    environment.servlets().addFilter("Guice Filter", GuiceFilter.class).addMappingForUrlPatterns(null, false, "/*");
    environment.servlets().addServletListeners(new GuiceServletContextListener() {

      @Override
      protected Injector getInjector() {
        return injector;
      }
    });
  }

  public Injector getInjector() {
    return checkNotNull(injector, "injector has not been initialized yet");
  }

  public static class GuiceEnforcerModule implements Module {
    @Override
    public void configure(final Binder binder) {
      binder.disableCircularProxies();
      binder.requireExplicitBindings();
      binder.requireExactBindingAnnotations();
      binder.requireAtInjectOnConstructors();
    }
  }

  public static class Builder {
    private final Class configClass;
    private final ImmutableSet.Builder guiceModules = ImmutableSet.builder();
    private final ImmutableSet.Builder> dropwizardAwareModules = ImmutableSet.builder();
    private Stage guiceStage = Stage.PRODUCTION;
    private boolean allowUnknownFields = true;
    private boolean enableGuiceEnforcer = true;
    private InjectorFactory injectorFactory = Guice::createInjector;

    private Builder(final Class configClass) {
      this.configClass = configClass;
    }

    public final Builder stage(final Stage guiceStage) {
      checkNotNull(guiceStage, "guiceStage is null");
      this.guiceStage = guiceStage;
      return this;
    }

    public final Builder allowUnknownFields(final boolean allowUnknownFields) {
      this.allowUnknownFields = allowUnknownFields;
      return this;
    }

    public final Builder enableGuiceEnforcer(final boolean enableGuiceEnforcer) {
      this.enableGuiceEnforcer = enableGuiceEnforcer;
      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 DropwizardAwareModule) {
          dropwizardAwareModules.add((DropwizardAwareModule) module);
        } else {
          guiceModules.add(module);
        }
      }
      return this;
    }

    public final Builder injectorFactory(final InjectorFactory injectorFactory) {
      this.injectorFactory = injectorFactory;
      return this;
    }

    public final GuiceBundle build() {
      return new GuiceBundle<>(configClass,
                               guiceModules.build(),
                               dropwizardAwareModules.build(),
                               guiceStage,
                               allowUnknownFields,
                               enableGuiceEnforcer,
                               injectorFactory);
    }
  }

  private static String getNextServiceLocatorName() {
    Class factoryClass = ServiceLocatorFactoryImpl.class;
    try {
      Field nameCountField = factoryClass.getDeclaredField("name_count");
      nameCountField.setAccessible(true);
      int count = (int) nameCountField.get(null);

      Field namePrefixField = factoryClass.getDeclaredField("GENERATED_NAME_PREFIX");
      namePrefixField.setAccessible(true);
      String prefix = (String) namePrefixField.get(null);

      return prefix + count;
    } catch (NoSuchFieldException | IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }
}