From 4ea593e97b2d884752a7d5bd18079c152216f2f0 Mon Sep 17 00:00:00 2001 From: jstehler Date: Fri, 14 Nov 2025 11:11:55 -0500 Subject: [PATCH] upgrade to dropwizard 5.0 --- README.md | 2 +- pom.xml | 190 +++++++++----- .../AllowUnknownFieldsObjectMapper.java | 2 +- .../guicier/DropwizardAwareModule.java | 6 +- .../dropwizard/guicier/DropwizardModule.java | 8 +- .../dropwizard/guicier/GuiceBundle.java | 48 +--- .../guicier/JerseyGuicierModule.java | 82 ++++-- .../guicier/injection/BindingUtils.java | 248 ++++++++++++++++++ .../BridgedGuiceInjectionManager.java | 64 +++++ .../BridgedGuiceInjectionManagerFactory.java | 68 +++++ .../injection/DelegatingInjectionManager.java | 131 +++++++++ .../injection/GuiceInjectionResolver.java | 77 ++++++ .../injection/InjectionManagerProvider.java | 38 +++ .../InjectionManagerProviderFeature.java | 31 +++ .../guicier/injection/InjectorProvider.java | 26 ++ ...ey.internal.inject.InjectionManagerFactory | 1 + .../dropwizard/guicier/GuiceBundleTest.java | 67 ++--- .../dropwizard/guicier/HK2LinkerTest.java | 68 ++--- .../guicier/InjectedIntegrationTest.java | 102 ++++--- .../guicier/InjectedResourcesTest.java | 21 +- .../dropwizard/guicier/StashesTest.java | 80 ++++++ .../dropwizard/guicier/aop/AopTest.java | 42 +++ .../guicier/aop/AopTestApplication.java | 45 ++++ .../dropwizard/guicier/aop/MyAnnotation.java | 12 + .../dropwizard/guicier/aop/MyInterceptor.java | 16 ++ .../dropwizard/guicier/aop/MyResource.java | 25 ++ .../objects/ComponentInvocationCounter.java | 23 ++ .../objects/ContextInjectedFilter.java | 39 +++ .../EmptyCtorGoogleInjectResource.java | 21 ++ .../guicier/objects/ExplicitResource.java | 8 +- .../guicier/objects/HK2ContextBindings.java | 26 +- .../guicier/objects/InjectedProvider.java | 2 +- .../guicier/objects/InjectedTask.java | 9 +- .../objects/JerseyContextResource.java | 10 +- .../guicier/objects/ProvidedProvider.java | 2 +- .../guicier/objects/ProvidedTask.java | 9 +- .../objects/ProviderManagedProvider.java | 4 +- .../dropwizard/guicier/objects/Stashed.java | 15 ++ .../objects/StashedValueFactoryProvider.java | 35 +++ .../guicier/objects/StashedValueFeature.java | 31 +++ .../guicier/objects/StashesTestModule.java | 21 ++ .../guicier/objects/StashesTestResource.java | 82 ++++++ .../guicier/objects/TestApplication.java | 22 +- .../guicier/objects/TestModule.java | 12 +- .../guicier/objects/TestValueParam.java | 11 + .../objects/TestValueParamFeature.java | 31 +++ .../objects/TestValueParamProvider.java | 29 ++ src/test/resources/test-config.yml | 2 +- 48 files changed, 1625 insertions(+), 319 deletions(-) create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/BindingUtils.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManager.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManagerFactory.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/DelegatingInjectionManager.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/GuiceInjectionResolver.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProvider.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProviderFeature.java create mode 100644 src/main/java/com/hubspot/dropwizard/guicier/injection/InjectorProvider.java create mode 100644 src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/StashesTest.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/aop/AopTest.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/aop/AopTestApplication.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/aop/MyAnnotation.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/aop/MyInterceptor.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/aop/MyResource.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/ComponentInvocationCounter.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/ContextInjectedFilter.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/EmptyCtorGoogleInjectResource.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/Stashed.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFactoryProvider.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFeature.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestModule.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestResource.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParam.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamFeature.java create mode 100644 src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamProvider.java diff --git a/README.md b/README.md index 8a9810e..666eb4c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A Dropwizard bundle to handle Guice integration. com.hubspot.dropwizard dropwizard-guicier - 1.3.5.2 + 5.0.0 ``` diff --git a/pom.xml b/pom.xml index 3f14b47..1e95fa4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,36 +5,70 @@ com.hubspot basepom - 63.7 + 65.2-SNAPSHOT com.hubspot.dropwizard dropwizard-guicier - 1.3.5.4-SNAPSHOT - - dropwizard-guicier - Integrates Dropwizard REST framework with Guice dependency injection + 5.0.0-SNAPSHOT true + + 5.0.0 + 4.2.37 + 33.5.0-jre + 7.0.0 + 3.0.6 + 2.20.0 + 3.1.11 + 12.1.1 + 1.5.18 + 2.0.17 + + 17 - com.google.code.findbugs - jsr305 - 3.0.2 + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + jakarta.inject + jakarta.inject-api + 2.0.1 + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + + + org.glassfish.jersey.inject + jersey-hk2 + ${dep.jersey2.version} - com.squarespace.jersey2-guice - jersey2-guice-impl - 1.0.6 - + org.hibernate.validator + hibernate-validator + 8.0.3.Final + + + org.eclipse.jetty + jetty-server + ${dep.jetty.version} - com.google.inject.extensions - guice-multibindings + org.eclipse.jetty.toolchain + jetty-servlet-api @@ -42,54 +76,37 @@ - - - com.squarespace.jersey2-guice - jersey2-guice-impl - - - com.google.inject - guice - - com.google.inject.extensions - guice-servlet + com.fasterxml.jackson.core + jackson-databind com.google.code.findbugs annotations - - javax.inject - javax.inject - com.google.guava guava - javax.servlet - javax.servlet-api - - - javax.validation - validation-api + com.google.inject + guice - javax.ws.rs - javax.ws.rs-api + com.google.inject.extensions + guice-servlet - org.slf4j - slf4j-api + io.dropwizard + dropwizard-core io.dropwizard - dropwizard-servlets + dropwizard-jersey io.dropwizard - dropwizard-core + dropwizard-jetty io.dropwizard @@ -97,68 +114,104 @@ io.dropwizard - dropwizard-jackson + dropwizard-servlets - io.dropwizard - dropwizard-jersey + io.dropwizard.metrics + metrics-healthchecks - io.dropwizard - dropwizard-jetty + jakarta.annotation + jakarta.annotation-api - io.dropwizard.metrics - metrics-core + jakarta.servlet + jakarta.servlet-api - io.dropwizard.metrics - metrics-healthchecks + jakarta.ws.rs + jakarta.ws.rs-api - org.glassfish.jersey.core - jersey-server + org.eclipse.jetty + jetty-server + + + org.glassfish.hk2 + guice-bridge org.glassfish.hk2 hk2-api - com.fasterxml.jackson.core - jackson-databind + org.glassfish.hk2 + hk2-utils - org.eclipse.jetty - jetty-server + org.glassfish.jersey.core + jersey-common - - junit - junit - test + org.glassfish.jersey.core + jersey-server - org.assertj - assertj-core - test + org.glassfish.jersey.inject + jersey-hk2 - org.mockito - mockito-core - test + org.slf4j + slf4j-api + io.dropwizard dropwizard-testing test - io.dropwizard - dropwizard-client + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter-api test + + + + + org.basepom.maven + duplicate-finder-maven-plugin + + + + + + org.jspecify + jspecify + + + io.dropwizard.logback + logback-throttling-appender + + + + org.jspecify.annotations.Nullable + + + + + + + + + https://github.com/HubSpot/dropwizard-guicier @@ -184,4 +237,3 @@ HEAD - diff --git a/src/main/java/com/hubspot/dropwizard/guicier/AllowUnknownFieldsObjectMapper.java b/src/main/java/com/hubspot/dropwizard/guicier/AllowUnknownFieldsObjectMapper.java index 32ad71f..d9d98bb 100644 --- a/src/main/java/com/hubspot/dropwizard/guicier/AllowUnknownFieldsObjectMapper.java +++ b/src/main/java/com/hubspot/dropwizard/guicier/AllowUnknownFieldsObjectMapper.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import io.dropwizard.setup.Bootstrap; +import io.dropwizard.core.setup.Bootstrap; /** * Dropwizard makes it really hard to allow unknown fields in the {@link io.dropwizard.Configuration} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/DropwizardAwareModule.java b/src/main/java/com/hubspot/dropwizard/guicier/DropwizardAwareModule.java index 336a1eb..a830193 100644 --- a/src/main/java/com/hubspot/dropwizard/guicier/DropwizardAwareModule.java +++ b/src/main/java/com/hubspot/dropwizard/guicier/DropwizardAwareModule.java @@ -4,9 +4,9 @@ import static com.google.common.base.Preconditions.checkState; import com.google.inject.Module; -import io.dropwizard.Configuration; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.core.Configuration; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; public abstract class DropwizardAwareModule implements Module { diff --git a/src/main/java/com/hubspot/dropwizard/guicier/DropwizardModule.java b/src/main/java/com/hubspot/dropwizard/guicier/DropwizardModule.java index 25084d0..d0b6486 100644 --- a/src/main/java/com/hubspot/dropwizard/guicier/DropwizardModule.java +++ b/src/main/java/com/hubspot/dropwizard/guicier/DropwizardModule.java @@ -7,13 +7,14 @@ import com.google.inject.Module; import com.google.inject.matcher.Matchers; import com.google.inject.spi.ProvisionListener; +import com.hubspot.dropwizard.guicier.injection.InjectorProvider; +import io.dropwizard.core.setup.Environment; import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.ServerLifecycleListener; import io.dropwizard.servlets.tasks.Task; -import io.dropwizard.setup.Environment; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ext.Provider; import java.lang.reflect.Type; -import javax.ws.rs.Path; -import javax.ws.rs.ext.Provider; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.model.Resource; import org.slf4j.Logger; @@ -59,6 +60,7 @@ public void onProvision(ProvisionInvocation provision) { } public void register(Injector injector) { + InjectorProvider.set(injector); registerResourcesAndProviders(environment.jersey().getResourceConfig(), injector); } diff --git a/src/main/java/com/hubspot/dropwizard/guicier/GuiceBundle.java b/src/main/java/com/hubspot/dropwizard/guicier/GuiceBundle.java index 9bd65ac..9b54637 100644 --- a/src/main/java/com/hubspot/dropwizard/guicier/GuiceBundle.java +++ b/src/main/java/com/hubspot/dropwizard/guicier/GuiceBundle.java @@ -11,16 +11,12 @@ import com.google.inject.servlet.GuiceFilter; import com.google.inject.servlet.GuiceServletContextListener; import com.google.inject.servlet.ServletModule; -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; -import java.lang.reflect.Field; +import com.hubspot.dropwizard.guicier.injection.InjectionManagerProviderFeature; +import io.dropwizard.core.Configuration; +import io.dropwizard.core.ConfiguredBundle; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; import java.util.Arrays; -import org.glassfish.hk2.api.ServiceLocator; -import org.glassfish.hk2.internal.ServiceLocatorFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,38 +80,25 @@ public void run(final T configuration, final Environment environment) throws Exc } 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(binder -> { binder.bind(Environment.class).toInstance(environment); binder.bind(configClass).toInstance(configuration); + binder.bind(InjectionManagerProviderFeature.class); }); if (enableGuiceEnforcer) { modulesBuilder.add(new GuiceEnforcerModule()); } this.injector = injectorFactory.create(guiceStage, 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 '{}'", name); - return JerseyGuiceUtils.newServiceLocator(name, parent); - } - }); - dropwizardModule.register(injector); - environment .servlets() .addFilter("Guice Filter", GuiceFilter.class) @@ -211,21 +194,4 @@ public final GuiceBundle build() { ); } } - - 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); - } - } } diff --git a/src/main/java/com/hubspot/dropwizard/guicier/JerseyGuicierModule.java b/src/main/java/com/hubspot/dropwizard/guicier/JerseyGuicierModule.java index 57bed64..d60793a 100644 --- a/src/main/java/com/hubspot/dropwizard/guicier/JerseyGuicierModule.java +++ b/src/main/java/com/hubspot/dropwizard/guicier/JerseyGuicierModule.java @@ -1,49 +1,95 @@ package com.hubspot.dropwizard.guicier; +import com.google.inject.AbstractModule; import com.google.inject.Provides; +import com.google.inject.Scopes; import com.google.inject.servlet.RequestScoped; -import com.squarespace.jersey2.guice.JerseyModule; -import javax.servlet.ServletConfig; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ResourceContext; -import javax.ws.rs.core.Configuration; -import org.glassfish.hk2.api.ServiceLocator; +import com.hubspot.dropwizard.guicier.injection.InjectionManagerProvider; +import jakarta.servlet.ServletConfig; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Providers; +import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.server.ExtendedUriInfo; /** * This supplements the bindings provided in {@link com.squarespace.jersey2.guice.JerseyGuiceModule}. */ -public class JerseyGuicierModule extends JerseyModule { +public class JerseyGuicierModule extends AbstractModule { @Override - protected void configure() {} + protected void configure() { + bind(InjectionManager.class) + .toProvider(InjectionManagerProvider.class) + .in(Scopes.NO_SCOPE); + } + + @Provides + public Application providesApplication(InjectionManager injectionManager) { + return injectionManager.getInstance(Application.class); + } + + @Provides + public Providers providesProviders(InjectionManager injectionManager) { + return injectionManager.getInstance(Providers.class); + } + + @Provides + @RequestScoped + public UriInfo providesUriInfo(InjectionManager injectionManager) { + return injectionManager.getInstance(UriInfo.class); + } + + @Provides + @RequestScoped + public HttpHeaders providesHttpHeaders(InjectionManager injectionManager) { + return injectionManager.getInstance(HttpHeaders.class); + } + + @Provides + @RequestScoped + public SecurityContext providesSecurityContext(InjectionManager injectionManager) { + return injectionManager.getInstance(SecurityContext.class); + } + + @Provides + @RequestScoped + public Request providesRequest(InjectionManager injectionManager) { + return injectionManager.getInstance(Request.class); + } @Provides - public Configuration providesConfiguration(ServiceLocator serviceLocator) { - return serviceLocator.getService(Configuration.class); + public Configuration providesConfiguration(InjectionManager injectionManager) { + return injectionManager.getInstance(Configuration.class); } @Provides @RequestScoped public ContainerRequestContext providesContainerRequestContext( - ServiceLocator serviceLocator + InjectionManager injectionManager ) { - return serviceLocator.getService(ContainerRequestContext.class); + return injectionManager.getInstance(ContainerRequestContext.class); } @Provides @RequestScoped - public ExtendedUriInfo providesExtendedUriInfo(ServiceLocator serviceLocator) { - return serviceLocator.getService(ExtendedUriInfo.class); + public ExtendedUriInfo providesExtendedUriInfo(InjectionManager injectionManager) { + return injectionManager.getInstance(ExtendedUriInfo.class); } @Provides - public ResourceContext providesResourceContext(ServiceLocator serviceLocator) { - return serviceLocator.getService(ResourceContext.class); + public ResourceContext providesResourceContext(InjectionManager injectionManager) { + return injectionManager.getInstance(ResourceContext.class); } @Provides - public ServletConfig providesServletConfig(ServiceLocator serviceLocator) { - return serviceLocator.getService(ServletConfig.class); + public ServletConfig providesServletConfig(InjectionManager injectionManager) { + return injectionManager.getInstance(ServletConfig.class); } } diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/BindingUtils.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/BindingUtils.java new file mode 100644 index 0000000..0263ab0 --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/BindingUtils.java @@ -0,0 +1,248 @@ +package com.hubspot.dropwizard.guicier.injection; + +import com.google.inject.BindingAnnotation; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.internal.Nullability; +import jakarta.inject.Qualifier; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Nullable; +import org.glassfish.hk2.utilities.reflection.ParameterizedTypeImpl; +import org.glassfish.hk2.utilities.reflection.ReflectionHelper; +import org.glassfish.jersey.internal.inject.Injectee; + +public class BindingUtils { + + public static Optional translateGuiceProviderType(Injectee injectee) { + Type requiredType = injectee.getRequiredType(); + if (requiredType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) requiredType; + if (parameterizedType.getRawType().equals(Provider.class)) { + return Optional.of( + new ParameterizedTypeImpl( + jakarta.inject.Provider.class, + parameterizedType.getActualTypeArguments() + ) + ); + } + } + return Optional.empty(); + } + + public static Provider javaxToGuiceProvider(Object instance) { + if (!(instance instanceof jakarta.inject.Provider)) { + throw new IllegalArgumentException( + "Given instance is not of type jakarta.inject.Provider: " + instance + ); + } + + jakarta.inject.Provider provider = (jakarta.inject.Provider) instance; + return provider::get; + } + + /** + * Returns {@code true} if the given {@link Injectee} can be {@code null}. + * + * @see Optional + * @see Injectee#isOptional() + * @see Nullable + * @see com.google.inject.Inject#optional() + * @see Nullability#hasNullableAnnotation(Annotation[]) + */ + public static boolean isNullable(Injectee injectee) { + // HK2's optional + if (injectee.isOptional()) { + return true; + } + + // Guice's optional + AnnotatedElement element = injectee.getParent(); + if (isGuiceOptional(element)) { + return true; + } + + // Any @Nullable? + int position = injectee.getPosition(); + + if (element instanceof Field) { + return Nullability.hasNullableAnnotation(((Field) element).getAnnotations()); + } else if (element instanceof Method) { + Annotation annotations[][] = ((Method) element).getParameterAnnotations(); + return Nullability.hasNullableAnnotation(annotations[position]); + } else if (element instanceof Constructor) { + Annotation annotations[][] = ((Constructor) element).getParameterAnnotations(); + return Nullability.hasNullableAnnotation(annotations[position]); + } + + return false; + } + + /** + * Returns {@code true} if the given {@link AnnotatedElement} has a + * {@link com.google.inject.Inject} {@link Annotation} and it's marked + * as being optional. + * + * @see com.google.inject.Inject#optional() + */ + private static boolean isGuiceOptional(AnnotatedElement element) { + com.google.inject.Inject inject = element.getAnnotation( + com.google.inject.Inject.class + ); + + if (inject != null) { + return inject.optional(); + } + + return false; + } + + /** + * Returns {@code true} if the {@link Injectee} has a HK2 SPI + * {@link org.jvnet.hk2.annotations.Contract} annotation. + * + * @see org.jvnet.hk2.annotations.Contract + */ + public static boolean isHk2Contract(Injectee injectee) { + Type type = injectee.getRequiredType(); + return hasTypeAnnotation(type, org.jvnet.hk2.annotations.Contract.class); + } + + /** + * Returns {@code true} if the {@link Injectee} has a Jersey SPI + * {@link org.glassfish.jersey.spi.Contract} annotation. + * + * @see org.glassfish.jersey.spi.Contract + */ + public static boolean isJerseyContract(Injectee injectee) { + Type type = injectee.getRequiredType(); + return hasTypeAnnotation(type, org.glassfish.jersey.spi.Contract.class); + } + + public static boolean hasTypeAnnotation( + Type type, + Class annotationType + ) { + if (type instanceof Class) { + return ((Class) type).isAnnotationPresent(annotationType); + } + + if (type instanceof ParameterizedType) { + Type rawType = ((ParameterizedType) type).getRawType(); + return hasTypeAnnotation(rawType, annotationType); + } + + return false; + } + + /** + * Creates and returns a {@link Key} from the given {@link Injectee}. + */ + public static Key toKey(Injectee injectee) { + Type type = injectee.getRequiredType(); + Set qualifiers = getQualifiers(injectee); + return newKey(type, qualifiers); + } + + /** + * Creates and returns a {@link Key} for the given {@link Type} and {@link Set} of {@link Annotation}s. + */ + public static Key newKey(Type type, Set qualifiers) { + if (qualifiers.isEmpty()) { + return Key.get(type); + } + + // There can be only one qualifier. + if (qualifiers.size() == 1) { + for (Annotation first : qualifiers) { + return Key.get(type, first); + } + } + + return null; + } + + /** + * NOTE: There can be only one {@link Annotation} that is a {@link Qualifier} or {@link BindingAnnotation}. + * They're the same but HK2 does not know about {@link BindingAnnotation}. + * + * @see Qualifier + * @see BindingAnnotation + * @see jakarta.inject.Named + * @see com.google.inject.name.Named + */ + private static Set getQualifiers(Injectee injectee) { + // JSR 330's @Qualifier + Set qualifiers = injectee.getRequiredQualifiers(); + if (!qualifiers.isEmpty()) { + return qualifiers; + } + + AnnotatedElement element = injectee.getParent(); + int position = injectee.getPosition(); + + // Guice's @BindingAnnotation is the same as @Qualifier + Annotation annotation = getBindingAnnotation(element, position); + if (annotation != null) { + return Collections.singleton(annotation); + } + + return Collections.emptySet(); + } + + /** + * Returns a {@link BindingAnnotation} for the given {@link AnnotatedElement} and position. + */ + private static Annotation getBindingAnnotation(AnnotatedElement element, int position) { + if (element instanceof Field) { + return getBindingAnnotation(((Field) element).getAnnotations()); + } + + if (element instanceof Method) { + Annotation annotations[][] = ((Method) element).getParameterAnnotations(); + return getBindingAnnotation(annotations[position]); + } + + if (element instanceof Constructor) { + Annotation annotations[][] = ((Constructor) element).getParameterAnnotations(); + return getBindingAnnotation(annotations[position]); + } + + return null; + } + + /** + * Returns the first {@link Annotation} from the given array that + * is a {@link BindingAnnotation}. + * + * @see BindingAnnotation + */ + private static Annotation getBindingAnnotation(Annotation[] annotations) { + for (Annotation annotation : annotations) { + Class type = annotation.annotationType(); + if (type.isAnnotationPresent(BindingAnnotation.class)) { + return annotation; + } + } + + return null; + } + + /** + * @see ReflectionHelper#getNameFromAllQualifiers(Set, AnnotatedElement) + */ + public static String getNameFromAllQualifiers( + Set qualifiers, + AnnotatedElement element + ) { + return ReflectionHelper.getNameFromAllQualifiers(qualifiers, element); + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManager.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManager.java new file mode 100644 index 0000000..7f4ff3d --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManager.java @@ -0,0 +1,64 @@ +package com.hubspot.dropwizard.guicier.injection; + +import static com.hubspot.dropwizard.guicier.injection.BindingUtils.newKey; + +import com.google.inject.Injector; +import com.google.inject.Key; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; +import org.glassfish.jersey.internal.inject.InjectionManager; + +/** + * InjectionManager which gets instances from Guice bindings first, falling back to delegate + */ +public class BridgedGuiceInjectionManager extends DelegatingInjectionManager { + + private final Injector guiceInjector; + + public BridgedGuiceInjectionManager(InjectionManager delegate, Injector guiceInjector) { + super(delegate); + this.guiceInjector = guiceInjector; + } + + @Override + public T getInstance(Class contractOrImpl, Annotation... qualifiers) { + T guiceInstance = getGuiceInstance(newKey(contractOrImpl, Set.of(qualifiers))); + return guiceInstance != null + ? guiceInstance + : super.getInstance(contractOrImpl, qualifiers); + } + + @Override + public T getInstance(Class contractOrImpl, String classAnalyzer) { + T guiceInstance = getGuiceInstance(newKey(contractOrImpl, Set.of())); + return guiceInstance != null + ? guiceInstance + : super.getInstance(contractOrImpl, classAnalyzer); + } + + @Override + public T getInstance(Class contractOrImpl) { + T guiceInstance = getGuiceInstance(newKey(contractOrImpl, Set.of())); + return guiceInstance != null ? guiceInstance : super.getInstance(contractOrImpl); + } + + @Override + public T getInstance(Type contractOrImpl) { + T guiceInstance = getGuiceInstance(newKey(contractOrImpl, Set.of())); + return guiceInstance != null ? guiceInstance : super.getInstance(contractOrImpl); + } + + @SuppressWarnings("unchecked") + private T getGuiceInstance(Key key) { + if (guiceInjector.getExistingBinding(key) == null) { + return null; + } + + T instance = (T) guiceInjector.getInstance(key); + if (instance != null) { + super.inject(instance); + } + return instance; + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManagerFactory.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManagerFactory.java new file mode 100644 index 0000000..32043b9 --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/BridgedGuiceInjectionManagerFactory.java @@ -0,0 +1,68 @@ +package com.hubspot.dropwizard.guicier.injection; + +import com.google.inject.Injector; +import jakarta.annotation.Priority; +import jakarta.ws.rs.ConstrainedTo; +import jakarta.ws.rs.RuntimeType; +import java.util.Optional; +import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory; +import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager; +import org.glassfish.jersey.internal.inject.Bindings; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManagerFactory; +import org.jvnet.hk2.guice.bridge.api.GuiceBridge; +import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Priority(15) +@ConstrainedTo(RuntimeType.SERVER) +public class BridgedGuiceInjectionManagerFactory implements InjectionManagerFactory { + + private static final Logger LOG = LoggerFactory.getLogger( + BridgedGuiceInjectionManagerFactory.class + ); + + @Override + public InjectionManager create(Object parent) { + ImmediateHk2InjectionManager injectionManager = + (ImmediateHk2InjectionManager) new Hk2InjectionManagerFactory() + .create(getHk2Parent(parent)); + + Optional guiceInjectorMaybe = InjectorProvider.getMaybe(); + + if (guiceInjectorMaybe.isEmpty()) { + LOG.warn("No guice injector is set"); + return injectionManager; + } + + Injector guiceInjector = guiceInjectorMaybe.get(); + + // initialize HK2 guice-bridge + GuiceBridge + .getGuiceBridge() + .initializeGuiceBridge(injectionManager.getServiceLocator()); + GuiceIntoHK2Bridge guiceBridge = injectionManager.getInstance( + GuiceIntoHK2Bridge.class + ); + guiceBridge.bridgeGuiceInjector(guiceInjector); + + injectionManager.register( + Bindings.injectionResolver( + new GuiceInjectionResolver(guiceInjector, injectionManager.getServiceLocator()) + ) + ); + injectionManager.register(Bindings.service(guiceInjector).to(Injector.class)); + + LOG.debug("Guice Component Provider initialized"); + return new BridgedGuiceInjectionManager(injectionManager, guiceInjector); + } + + private static Object getHk2Parent(Object parent) { + if (parent instanceof DelegatingInjectionManager) { + DelegatingInjectionManager injectionManager = (DelegatingInjectionManager) parent; + return injectionManager.getDelegate(); + } + return parent; + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/DelegatingInjectionManager.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/DelegatingInjectionManager.java new file mode 100644 index 0000000..28ef85f --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/DelegatingInjectionManager.java @@ -0,0 +1,131 @@ +package com.hubspot.dropwizard.guicier.injection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; +import org.glassfish.jersey.internal.inject.Binder; +import org.glassfish.jersey.internal.inject.Binding; +import org.glassfish.jersey.internal.inject.ForeignDescriptor; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.ServiceHolder; + +public class DelegatingInjectionManager implements InjectionManager { + + private final InjectionManager delegate; + + public DelegatingInjectionManager(InjectionManager delegate) { + this.delegate = delegate; + } + + public InjectionManager getDelegate() { + return delegate; + } + + @Override + public void completeRegistration() { + delegate.completeRegistration(); + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public void register(Binding binding) { + delegate.register(binding); + } + + @Override + public void register(Iterable descriptors) { + delegate.register(descriptors); + } + + @Override + public void register(Binder binder) { + delegate.register(binder); + } + + @Override + public void register(Object provider) throws IllegalArgumentException { + delegate.register(provider); + } + + @Override + public boolean isRegistrable(Class clazz) { + return delegate.isRegistrable(clazz); + } + + @Override + public T create(Class createMe) { + return delegate.create(createMe); + } + + @Override + public T createAndInitialize(Class createMe) { + return delegate.createAndInitialize(createMe); + } + + @Override + public List> getAllServiceHolders( + Class contractOrImpl, + Annotation... qualifiers + ) { + return delegate.getAllServiceHolders(contractOrImpl, qualifiers); + } + + @Override + public T getInstance(Class contractOrImpl, Annotation... qualifiers) { + return delegate.getInstance(contractOrImpl, qualifiers); + } + + @Override + public T getInstance(Class contractOrImpl, String classAnalyzer) { + return delegate.getInstance(contractOrImpl, classAnalyzer); + } + + @Override + public T getInstance(Class contractOrImpl) { + return delegate.getInstance(contractOrImpl); + } + + @Override + public T getInstance(Type contractOrImpl) { + return delegate.getInstance(contractOrImpl); + } + + @Override + public Object getInstance(ForeignDescriptor foreignDescriptor) { + return delegate.getInstance(foreignDescriptor); + } + + @Override + public ForeignDescriptor createForeignDescriptor(Binding binding) { + return delegate.createForeignDescriptor(binding); + } + + @Override + public List getAllInstances(Type contractOrImpl) { + return delegate.getAllInstances(contractOrImpl); + } + + @Override + public void inject(Object injectMe) { + delegate.inject(injectMe); + } + + @Override + public void inject(Object injectMe, String classAnalyzer) { + delegate.inject(injectMe, classAnalyzer); + } + + @Override + public void preDestroy(Object preDestroyMe) { + delegate.preDestroy(preDestroyMe); + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/GuiceInjectionResolver.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/GuiceInjectionResolver.java new file mode 100644 index 0000000..a248dcc --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/GuiceInjectionResolver.java @@ -0,0 +1,77 @@ +package com.hubspot.dropwizard.guicier.injection; + +import com.google.inject.ConfigurationException; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import jakarta.inject.Singleton; +import java.lang.reflect.Type; +import java.util.Optional; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.jersey.internal.inject.Injectee; +import org.glassfish.jersey.internal.inject.InjectionResolver; + +@Singleton +public class GuiceInjectionResolver implements InjectionResolver { + + private final Injector injector; + private final ServiceLocator serviceLocator; + + public GuiceInjectionResolver(Injector injector, ServiceLocator serviceLocator) { + this.injector = injector; + this.serviceLocator = serviceLocator; + } + + @Override + public Object resolve(Injectee injectee) { + Key key = BindingUtils.toKey(injectee); + + Object instance = null; + try { + instance = injector.getInstance(key); + } catch (ConfigurationException e) { + /* fallback to jersey hk2 injector */ + } + + if (instance == null) { + Optional translatedGuiceProviderType = + BindingUtils.translateGuiceProviderType(injectee); + + instance = + serviceLocator.getService( + translatedGuiceProviderType.orElse(injectee.getRequiredType()) + ); + + if (instance != null && translatedGuiceProviderType.isPresent()) { + return BindingUtils.javaxToGuiceProvider(instance); + } + } + + if (instance == null) { + if (BindingUtils.isNullable(injectee)) { + return null; + } + + throw new RuntimeException( + "There was no object available for injection at " + injectee + ); + } + + return instance; + } + + @Override + public boolean isConstructorParameterIndicator() { + return true; + } + + @Override + public boolean isMethodParameterIndicator() { + return false; + } + + @Override + public Class getAnnotation() { + return Inject.class; + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProvider.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProvider.java new file mode 100644 index 0000000..09f1b03 --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProvider.java @@ -0,0 +1,38 @@ +package com.hubspot.dropwizard.guicier.injection; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.dropwizard.core.setup.Environment; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import jakarta.inject.Singleton; +import jakarta.ws.rs.core.Application; +import java.util.HashMap; +import java.util.Map; +import org.glassfish.jersey.internal.inject.InjectionManager; + +@Singleton +public class InjectionManagerProvider implements Provider { + + private static final Map INJECTION_MANAGERS_BY_APP = + new HashMap<>(); + + private final Application application; + + @Inject + public InjectionManagerProvider(Environment environment) { + this.application = environment.jersey().getResourceConfig(); + } + + public static void set(Application application, InjectionManager injectionManager) { + INJECTION_MANAGERS_BY_APP.put(application, injectionManager); + } + + @Override + public InjectionManager get() { + return checkNotNull( + INJECTION_MANAGERS_BY_APP.get(application), + "InjectionManager not set" + ); + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProviderFeature.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProviderFeature.java new file mode 100644 index 0000000..1974df3 --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectionManagerProviderFeature.java @@ -0,0 +1,31 @@ +package com.hubspot.dropwizard.guicier.injection; + +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Feature; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.ext.Provider; +import org.glassfish.jersey.internal.inject.InjectionManager; + +/** + * This links the active InjectionManager to the Guice InjectionManagerProvider, + * to the active jakarta.ws.rs.core.Application, for use in the JerseyGuicierModule. + */ +@Provider +public class InjectionManagerProviderFeature implements Feature { + + private final InjectionManager injectionManager; + + @Inject + public InjectionManagerProviderFeature(InjectionManager injectionManager) { + this.injectionManager = injectionManager; + } + + @Override + public boolean configure(FeatureContext context) { + Application application = injectionManager.getInstance(Application.class); + InjectionManagerProvider.set(application, injectionManager); + + return true; + } +} diff --git a/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectorProvider.java b/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectorProvider.java new file mode 100644 index 0000000..c538d82 --- /dev/null +++ b/src/main/java/com/hubspot/dropwizard/guicier/injection/InjectorProvider.java @@ -0,0 +1,26 @@ +package com.hubspot.dropwizard.guicier.injection; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.inject.Injector; +import jakarta.inject.Provider; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +public class InjectorProvider implements Provider { + + private static final AtomicReference REF = new AtomicReference<>(); + + public static void set(Injector injector) { + REF.set(injector); + } + + public static Optional getMaybe() { + return Optional.ofNullable(REF.get()); + } + + @Override + public Injector get() { + return checkNotNull(REF.get(), "Guice Injector not set"); + } +} diff --git a/src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory b/src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory new file mode 100644 index 0000000..aa50640 --- /dev/null +++ b/src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory @@ -0,0 +1 @@ +com.hubspot.dropwizard.guicier.injection.BridgedGuiceInjectionManagerFactory diff --git a/src/test/java/com/hubspot/dropwizard/guicier/GuiceBundleTest.java b/src/test/java/com/hubspot/dropwizard/guicier/GuiceBundleTest.java index 0b46211..62221b0 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/GuiceBundleTest.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/GuiceBundleTest.java @@ -2,11 +2,7 @@ import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.codahale.metrics.MetricRegistry; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Injector; import com.hubspot.dropwizard.guicier.objects.ExplicitResource; import com.hubspot.dropwizard.guicier.objects.InjectedHealthCheck; @@ -21,44 +17,37 @@ import com.hubspot.dropwizard.guicier.objects.ProvidedServerLifecycleListener; import com.hubspot.dropwizard.guicier.objects.ProvidedTask; import com.hubspot.dropwizard.guicier.objects.ProviderManaged; -import com.hubspot.dropwizard.guicier.objects.TestModule; -import com.squarespace.jersey2.guice.JerseyGuiceUtils; -import io.dropwizard.Configuration; -import io.dropwizard.jackson.Jackson; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import com.hubspot.dropwizard.guicier.objects.TestApplication; +import io.dropwizard.core.Configuration; +import io.dropwizard.core.setup.Environment; +import io.dropwizard.testing.ResourceHelpers; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import jakarta.servlet.ServletException; import java.util.Set; -import javax.servlet.ServletException; import org.assertj.core.api.InstanceOfAssertFactories; -import org.glassfish.hk2.api.ServiceLocator; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(DropwizardExtensionsSupport.class) public class GuiceBundleTest { + private static final DropwizardAppExtension EXT = + new DropwizardAppExtension<>( + TestApplication.class, + ResourceHelpers.resourceFilePath("test-config.yml") + ); + private Environment environment; private GuiceBundle guiceBundle; - @After - public void tearDown() { - JerseyGuiceUtils.reset(); - } - - @Before - public void setUp() throws Exception { - ObjectMapper objectMapper = Jackson.newObjectMapper(); - environment = - new Environment("test env", objectMapper, null, new MetricRegistry(), null); - guiceBundle = - GuiceBundle.defaultBuilder(Configuration.class).modules(new TestModule()).build(); - Bootstrap bootstrap = mock(Bootstrap.class); - when(bootstrap.getObjectMapper()).thenReturn(objectMapper); - guiceBundle.initialize(bootstrap); - guiceBundle.run(new Configuration(), environment); + @BeforeEach + public void setup() { + TestApplication testApplication = EXT.getApplication(); + this.guiceBundle = testApplication.getGuiceBundle(); + this.environment = EXT.getEnvironment(); } @Test @@ -68,11 +57,11 @@ public void createsInjectorWhenInit() throws ServletException { } @Test - public void serviceLocatorIsAvaliable() throws ServletException { - ServiceLocator serviceLocator = guiceBundle + public void serviceLocatorIsAvailable() throws ServletException { + InjectionManager injectionManager = guiceBundle .getInjector() - .getInstance(ServiceLocator.class); - assertThat(serviceLocator).isNotNull(); + .getInstance(InjectionManager.class); + assertThat(injectionManager).isNotNull(); } @Test diff --git a/src/test/java/com/hubspot/dropwizard/guicier/HK2LinkerTest.java b/src/test/java/com/hubspot/dropwizard/guicier/HK2LinkerTest.java index a833f46..30989ea 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/HK2LinkerTest.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/HK2LinkerTest.java @@ -1,63 +1,47 @@ package com.hubspot.dropwizard.guicier; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; -import com.codahale.metrics.MetricRegistry; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.hubspot.dropwizard.guicier.objects.ExplicitResource; import com.hubspot.dropwizard.guicier.objects.HK2ContextBindings; -import com.hubspot.dropwizard.guicier.objects.TestModule; -import com.squarespace.jersey2.guice.JerseyGuiceUtils; -import io.dropwizard.Configuration; -import io.dropwizard.jackson.Jackson; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; -import javax.servlet.ServletException; -import org.glassfish.hk2.api.ServiceLocator; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; - +import com.hubspot.dropwizard.guicier.objects.TestApplication; +import io.dropwizard.core.Configuration; +import io.dropwizard.testing.ResourceHelpers; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import jakarta.servlet.ServletException; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(DropwizardExtensionsSupport.class) public class HK2LinkerTest { + private static final DropwizardAppExtension EXT = + new DropwizardAppExtension<>( + TestApplication.class, + ResourceHelpers.resourceFilePath("test-config.yml") + ); + private Injector injector; - private ServiceLocator serviceLocator; + private InjectionManager injectionManager; - @Before - public void setup() throws Exception { - ObjectMapper objectMapper = Jackson.newObjectMapper(); - Environment environment = new Environment( - "test env", - objectMapper, - null, - new MetricRegistry(), - null - ); - GuiceBundle guiceBundle = GuiceBundle - .defaultBuilder(Configuration.class) - .modules(new TestModule()) - .build(); - Bootstrap bootstrap = mock(Bootstrap.class); - when(bootstrap.getObjectMapper()).thenReturn(objectMapper); - guiceBundle.initialize(bootstrap); - guiceBundle.run(new Configuration(), environment); + @BeforeEach + public void setup() { + TestApplication testApplication = EXT.getApplication(); + GuiceBundle guiceBundle = testApplication.getGuiceBundle(); injector = guiceBundle.getInjector(); - serviceLocator = injector.getInstance(ServiceLocator.class); - } - - @AfterClass - public static void tearDown() { - JerseyGuiceUtils.reset(); + injectionManager = injector.getInstance(InjectionManager.class); } @Test public void explicitGuiceBindingsAreBridgedToHk2() throws ServletException { - ExplicitResource resource = serviceLocator.createAndInitialize( + ExplicitResource resource = injectionManager.createAndInitialize( ExplicitResource.class ); @@ -68,7 +52,7 @@ public void explicitGuiceBindingsAreBridgedToHk2() throws ServletException { @Test public void contextBindingsAreBridgedToGuice() { for (Class clazz : HK2ContextBindings.SET) { - Binding binding = injector.getExistingBinding(Key.get(clazz)); + Binding binding = injector.getExistingBinding(Key.get(clazz)); assertThat(binding).as("%s has a Guice binding", clazz.getName()).isNotNull(); } } diff --git a/src/test/java/com/hubspot/dropwizard/guicier/InjectedIntegrationTest.java b/src/test/java/com/hubspot/dropwizard/guicier/InjectedIntegrationTest.java index d8bdf9d..1b8ef6e 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/InjectedIntegrationTest.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/InjectedIntegrationTest.java @@ -2,47 +2,36 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.google.common.io.Resources; +import com.hubspot.dropwizard.guicier.objects.ComponentInvocationCounter; +import com.hubspot.dropwizard.guicier.objects.ContextInjectedFilter; import com.hubspot.dropwizard.guicier.objects.HK2ContextBindings; import com.hubspot.dropwizard.guicier.objects.TestApplication; -import com.squarespace.jersey2.guice.JerseyGuiceUtils; -import io.dropwizard.Configuration; -import io.dropwizard.client.JerseyClientBuilder; -import io.dropwizard.testing.junit.DropwizardAppRule; -import java.io.File; -import javax.ws.rs.client.Client; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; +import io.dropwizard.core.Configuration; +import io.dropwizard.testing.ResourceHelpers; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status.Family; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(DropwizardExtensionsSupport.class) public class InjectedIntegrationTest { - @ClassRule - public static final DropwizardAppRule RULE = new DropwizardAppRule<>( - TestApplication.class, - resourceFilePath("test-config.yml") - ); + private static final DropwizardAppExtension EXT = + new DropwizardAppExtension<>( + TestApplication.class, + ResourceHelpers.resourceFilePath("test-config.yml") + ); protected static Client client; - @BeforeClass + @BeforeAll public static void setUp() { - client = new JerseyClientBuilder(RULE.getEnvironment()).build("test client"); - } - - @AfterClass - public static void tearDown() { - JerseyGuiceUtils.reset(); - } - - public static String resourceFilePath(String resourceClassPathLocation) { - try { - return new File(Resources.getResource(resourceClassPathLocation).toURI()) - .getAbsolutePath(); - } catch (Exception e) { - throw new RuntimeException(e); - } + client = EXT.client(); } @Test @@ -54,20 +43,51 @@ public void shouldGetExplicitMessage() { assertThat(message).isEqualTo("this DAO was bound explicitly"); } + @Test + public void emptyCtorGoogleInject() { + assertThat( + client.target(getUri("/empty-ctor-google-inject")).request().get(String.class) + ) + .isEqualTo("world"); + } + + @Test + public void testContextInjectedFilter() { + Response response = client.target(getUri("/explicit/message")).request().get(); + assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.SUCCESSFUL); + assertThat(response.getHeaderString(ContextInjectedFilter.RESPONSE_HEADER)) + .isEqualTo("true"); + + TestApplication app = EXT.getApplication(); + ComponentInvocationCounter counter = app + .getGuiceBundle() + .getInjector() + .getInstance(ComponentInvocationCounter.class); + assertThat(counter.count("ContextInjectedFilter")).isGreaterThanOrEqualTo(1); + } + @Test public void hk2ContextBindingsAreResolvableInGuice() { - for (Class clazz : HK2ContextBindings.SET) { - boolean resolvable = client - .target(getUri("/jersey-context/is-resolvable-by-guice")) - .queryParam("className", clazz.getName()) - .request() - .get(Boolean.class); - assertThat(resolvable).as("%s is resolvable by Guice", clazz.getName()).isTrue(); - } + Assertions.assertAll( + HK2ContextBindings.SET + .stream() + .map(clazz -> + () -> { + boolean resolvable = client + .target(getUri("/jersey-context/is-resolvable-by-guice")) + .queryParam("className", clazz.getName()) + .request() + .get(Boolean.class); + assertThat(resolvable) + .as("%s is resolvable by Guice", clazz.getName()) + .isTrue(); + } + ) + ); } private static String getUri(String path) { - String domain = "http://localhost:" + RULE.getLocalPort(); + String domain = "http://localhost:" + EXT.getLocalPort(); return domain + (path.startsWith("/") ? "" : "/") + path; } } diff --git a/src/test/java/com/hubspot/dropwizard/guicier/InjectedResourcesTest.java b/src/test/java/com/hubspot/dropwizard/guicier/InjectedResourcesTest.java index d987d2a..e5ed093 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/InjectedResourcesTest.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/InjectedResourcesTest.java @@ -4,22 +4,19 @@ import com.hubspot.dropwizard.guicier.objects.ExplicitDAO; import com.hubspot.dropwizard.guicier.objects.ExplicitResource; -import com.squarespace.jersey2.guice.JerseyGuiceUtils; -import io.dropwizard.testing.junit.ResourceTestRule; -import org.junit.ClassRule; -import org.junit.Test; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import io.dropwizard.testing.junit5.ResourceExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; /** * this test is created to address to Null Pointer Exceptions in JerseyTest.teardown() related to ServiceLocator * See: https://github.com/dropwizard/dropwizard/issues/828 and http://permalink.gmane.org/gmane.comp.java.dropwizard.devel/376 */ +@ExtendWith(DropwizardExtensionsSupport.class) public class InjectedResourcesTest { - static { - JerseyGuiceUtils.reset(); - } - @ClassRule - public static final ResourceTestRule resources = ResourceTestRule + private static final ResourceExtension EXT = ResourceExtension .builder() .addResource(new ExplicitResource(new ExplicitDAO())) .build(); @@ -27,11 +24,7 @@ public class InjectedResourcesTest { @Test public void shouldGetExplicitMessage() { // when - String message = resources - .client() - .target("/explicit/message") - .request() - .get(String.class); + String message = EXT.client().target("/explicit/message").request().get(String.class); // then assertThat(message).isEqualTo("this DAO was bound explicitly"); diff --git a/src/test/java/com/hubspot/dropwizard/guicier/StashesTest.java b/src/test/java/com/hubspot/dropwizard/guicier/StashesTest.java new file mode 100644 index 0000000..8997a9a --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/StashesTest.java @@ -0,0 +1,80 @@ +package com.hubspot.dropwizard.guicier; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hubspot.dropwizard.guicier.objects.StashesTestModule; +import com.hubspot.dropwizard.guicier.objects.TestApplication; +import io.dropwizard.core.Configuration; +import io.dropwizard.testing.ResourceHelpers; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import jakarta.ws.rs.client.Client; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(DropwizardExtensionsSupport.class) +public class StashesTest { + + private static final DropwizardAppExtension EXT = + new DropwizardAppExtension<>( + TestApplication.class, + ResourceHelpers.resourceFilePath("test-config.yml") + ); + + private Client client; + + @BeforeEach + public void setup() { + this.client = EXT.client(); + } + + private int doGet(String path) { + return client + .target("http://localhost:" + EXT.getLocalPort() + "/stashes/" + path) + .request() + .get(int.class); + } + + @Test + public void itConstructorInjectsAStashedInt() { + assertThat(doGet("itConstructorInjectsAStashedInt")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } + + @Test + public void itConstructorInjectsAStashedIntGuiceProvider() { + assertThat(doGet("itConstructorInjectsAStashedIntGuiceProvider")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } + + @Test + public void itConstructorInjectsAStashedIntJavaxProvider() { + assertThat(doGet("itConstructorInjectsAStashedIntJavaxProvider")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } + + @Test + public void itFieldInjectsAStashedInt() { + assertThat(doGet("itFieldInjectsAStashedInt")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } + + @Test + public void itFieldInjectsAStashedIntGuiceProvider() { + assertThat(doGet("itFieldInjectsAStashedIntGuiceProvider")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } + + @Test + public void itFieldInjectsAStashedIntJavaxProvider() { + assertThat(doGet("itFieldInjectsAStashedIntJavaxProvider")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } + + @Test + public void itMethodInjectsAStashedInt() { + assertThat(doGet("itMethodInjectsAStashedInt")) + .isEqualTo(StashesTestModule.STASHED_INT_VALUE); + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/aop/AopTest.java b/src/test/java/com/hubspot/dropwizard/guicier/aop/AopTest.java new file mode 100644 index 0000000..a0e19df --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/aop/AopTest.java @@ -0,0 +1,42 @@ +package com.hubspot.dropwizard.guicier.aop; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.dropwizard.core.Configuration; +import io.dropwizard.testing.ResourceHelpers; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import jakarta.ws.rs.client.Client; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(DropwizardExtensionsSupport.class) +public class AopTest { + + private static final DropwizardAppExtension EXT = + new DropwizardAppExtension<>( + AopTestApplication.class, + ResourceHelpers.resourceFilePath("test-config.yml") + ); + + protected static Client client; + protected static AopTestApplication app; + + @BeforeAll + public static void setUp() { + client = EXT.client(); + } + + @Test + public void itInterceptsMethod() { + String response = client + .target("http://localhost:" + EXT.getLocalPort() + MyResource.PATH) + .request() + .get(String.class); + assertThat(response).isEqualTo(MyResource.RESPONSE); + + AopTestApplication app = EXT.getApplication(); + assertThat(app.getInterceptor().counter.get()).isEqualTo(1); + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/aop/AopTestApplication.java b/src/test/java/com/hubspot/dropwizard/guicier/aop/AopTestApplication.java new file mode 100644 index 0000000..acd5375 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/aop/AopTestApplication.java @@ -0,0 +1,45 @@ +package com.hubspot.dropwizard.guicier.aop; + +import com.google.inject.AbstractModule; +import com.google.inject.matcher.Matchers; +import com.hubspot.dropwizard.guicier.GuiceBundle; +import io.dropwizard.core.Application; +import io.dropwizard.core.Configuration; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; + +public class AopTestApplication extends Application { + + private MyInterceptor interceptor = new MyInterceptor(); + + public MyInterceptor getInterceptor() { + return interceptor; + } + + @Override + public void initialize(Bootstrap bootstrap) { + bootstrap.addBundle( + GuiceBundle + .defaultBuilder(Configuration.class) + .modules( + new AbstractModule() { + @Override + protected void configure() { + bind(MyResource.class); + + bindInterceptor( + Matchers.any(), + Matchers.annotatedWith(MyAnnotation.class), + interceptor + ); + } + } + ) + .build() + ); + } + + @Override + public void run(Configuration configuration, Environment environment) + throws Exception {} +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/aop/MyAnnotation.java b/src/test/java/com/hubspot/dropwizard/guicier/aop/MyAnnotation.java new file mode 100644 index 0000000..9dfcb8a --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/aop/MyAnnotation.java @@ -0,0 +1,12 @@ +package com.hubspot.dropwizard.guicier.aop; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(METHOD) +@Retention(RUNTIME) +public @interface MyAnnotation { +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/aop/MyInterceptor.java b/src/test/java/com/hubspot/dropwizard/guicier/aop/MyInterceptor.java new file mode 100644 index 0000000..d140420 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/aop/MyInterceptor.java @@ -0,0 +1,16 @@ +package com.hubspot.dropwizard.guicier.aop; + +import java.util.concurrent.atomic.AtomicInteger; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +class MyInterceptor implements MethodInterceptor { + + public final AtomicInteger counter = new AtomicInteger(); + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + counter.incrementAndGet(); + return invocation.proceed(); + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/aop/MyResource.java b/src/test/java/com/hubspot/dropwizard/guicier/aop/MyResource.java new file mode 100644 index 0000000..ef99d8e --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/aop/MyResource.java @@ -0,0 +1,25 @@ +package com.hubspot.dropwizard.guicier.aop; + +import com.google.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path(MyResource.PATH) +public class MyResource { + + public static final String PATH = "/aop-rsrc"; + + public static final String RESPONSE = "Hello, World!"; + + @Inject + public MyResource() {} + + @GET + @Produces(MediaType.TEXT_PLAIN) + @MyAnnotation + public String sayHello() { + return RESPONSE; + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/ComponentInvocationCounter.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/ComponentInvocationCounter.java new file mode 100644 index 0000000..7d0cc1f --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/ComponentInvocationCounter.java @@ -0,0 +1,23 @@ +package com.hubspot.dropwizard.guicier.objects; + +import com.google.common.collect.LinkedHashMultiset; +import com.google.common.collect.Multiset; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +@Singleton +public class ComponentInvocationCounter { + + private final Multiset invocations = LinkedHashMultiset.create(); + + @Inject + public ComponentInvocationCounter() {} + + public void inc(String name) { + invocations.add(name); + } + + public int count(String name) { + return invocations.count(name); + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/ContextInjectedFilter.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/ContextInjectedFilter.java new file mode 100644 index 0000000..e5c9262 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/ContextInjectedFilter.java @@ -0,0 +1,39 @@ +package com.hubspot.dropwizard.guicier.objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.ext.Provider; +import java.io.IOException; +import org.glassfish.jersey.server.ExtendedUriInfo; + +@Provider +public class ContextInjectedFilter implements ContainerResponseFilter { + + public static final String RESPONSE_HEADER = "x-context-injected-filter"; + + private final ComponentInvocationCounter counter; + + @Context + private ExtendedUriInfo extendedUriInfo; + + @Inject + public ContextInjectedFilter(ComponentInvocationCounter counter) { + this.counter = counter; + } + + @Override + public void filter( + ContainerRequestContext requestContext, + ContainerResponseContext responseContext + ) throws IOException { + checkNotNull(extendedUriInfo, "@Context injected ExtendedUriInfo null"); + + counter.inc(getClass().getSimpleName()); + responseContext.getHeaders().putSingle(RESPONSE_HEADER, "true"); + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/EmptyCtorGoogleInjectResource.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/EmptyCtorGoogleInjectResource.java new file mode 100644 index 0000000..8d3e724 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/EmptyCtorGoogleInjectResource.java @@ -0,0 +1,21 @@ +package com.hubspot.dropwizard.guicier.objects; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +import com.google.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +@Path("/empty-ctor-google-inject") +@Produces(APPLICATION_JSON) +public class EmptyCtorGoogleInjectResource { + + @Inject + public EmptyCtorGoogleInjectResource() {} + + @GET + public String hello() { + return "world"; + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/ExplicitResource.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/ExplicitResource.java index b4f0790..4796dae 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/ExplicitResource.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/ExplicitResource.java @@ -1,11 +1,11 @@ package com.hubspot.dropwizard.guicier.objects; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import com.google.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; @Path("/explicit") @Produces(APPLICATION_JSON) diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/HK2ContextBindings.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/HK2ContextBindings.java index 4b74a79..29c3de8 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/HK2ContextBindings.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/HK2ContextBindings.java @@ -1,19 +1,19 @@ package com.hubspot.dropwizard.guicier.objects; import com.google.common.collect.ImmutableSet; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Providers; import java.util.Set; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ResourceContext; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.core.UriInfo; -import javax.ws.rs.ext.Providers; import org.glassfish.jersey.server.ExtendedUriInfo; public class HK2ContextBindings { @@ -21,7 +21,7 @@ public class HK2ContextBindings { // This list comes from https://stackoverflow.com/a/35868654 public static final Set> SET = ImmutableSet.of( Application.class, - javax.ws.rs.core.Configuration.class, + jakarta.ws.rs.core.Configuration.class, ContainerRequestContext.class, HttpHeaders.class, HttpServletRequest.class, diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedProvider.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedProvider.java index 09a329d..b4a4f41 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedProvider.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedProvider.java @@ -1,7 +1,7 @@ package com.hubspot.dropwizard.guicier.objects; import com.google.inject.Inject; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.Provider; @Provider public class InjectedProvider { diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedTask.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedTask.java index 2b6efc5..ba54598 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedTask.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/InjectedTask.java @@ -1,11 +1,12 @@ package com.hubspot.dropwizard.guicier.objects; -import com.google.common.collect.ImmutableMultimap; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import io.dropwizard.servlets.tasks.Task; import java.io.PrintWriter; +import java.util.List; +import java.util.Map; @Singleton public class InjectedTask extends Task { @@ -16,8 +17,6 @@ protected InjectedTask(@Named("TestTaskName") String name) { } @Override - public void execute( - ImmutableMultimap immutableMultimap, - PrintWriter printWriter - ) throws Exception {} + public void execute(Map> parameters, PrintWriter output) + throws Exception {} } diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/JerseyContextResource.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/JerseyContextResource.java index 7fcd5d0..3939bbd 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/JerseyContextResource.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/JerseyContextResource.java @@ -1,14 +1,14 @@ package com.hubspot.dropwizard.guicier.objects; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import com.google.inject.ConfigurationException; import com.google.inject.Inject; import com.google.inject.Injector; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; @Path("/jersey-context") @Produces(APPLICATION_JSON) diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedProvider.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedProvider.java index 999cff4..3695ec7 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedProvider.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedProvider.java @@ -1,6 +1,6 @@ package com.hubspot.dropwizard.guicier.objects; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.Provider; @Provider public class ProvidedProvider {} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedTask.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedTask.java index 475af70..446b128 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedTask.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/ProvidedTask.java @@ -1,8 +1,9 @@ package com.hubspot.dropwizard.guicier.objects; -import com.google.common.collect.ImmutableMultimap; import io.dropwizard.servlets.tasks.Task; import java.io.PrintWriter; +import java.util.List; +import java.util.Map; public class ProvidedTask extends Task { @@ -11,8 +12,6 @@ public ProvidedTask(String name) { } @Override - public void execute( - ImmutableMultimap immutableMultimap, - PrintWriter printWriter - ) {} + public void execute(Map> parameters, PrintWriter output) + throws Exception {} } diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/ProviderManagedProvider.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/ProviderManagedProvider.java index 53dafd8..ff552ed 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/ProviderManagedProvider.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/ProviderManagedProvider.java @@ -1,7 +1,7 @@ package com.hubspot.dropwizard.guicier.objects; -import javax.inject.Inject; -import javax.inject.Provider; +import jakarta.inject.Inject; +import jakarta.inject.Provider; public class ProviderManagedProvider implements Provider { diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/Stashed.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/Stashed.java new file mode 100644 index 0000000..56f521b --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/Stashed.java @@ -0,0 +1,15 @@ +package com.hubspot.dropwizard.guicier.objects; + +import com.google.inject.BindingAnnotation; +import jakarta.inject.Qualifier; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Qualifier +@BindingAnnotation +@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Stashed { +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFactoryProvider.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFactoryProvider.java new file mode 100644 index 0000000..2917bb0 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFactoryProvider.java @@ -0,0 +1,35 @@ +package com.hubspot.dropwizard.guicier.objects; + +import com.google.inject.Injector; +import com.google.inject.Key; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import java.util.function.Function; +import org.glassfish.jersey.model.Parameter.Source; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.internal.inject.AbstractValueParamProvider; +import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; +import org.glassfish.jersey.server.model.Parameter; + +public class StashedValueFactoryProvider extends AbstractValueParamProvider { + + private final Injector injector; + + @Inject + public StashedValueFactoryProvider( + Provider extractorProviderProvider, + Injector injector + ) { + super(extractorProviderProvider, Source.UNKNOWN); + this.injector = injector; + } + + @Override + protected Function createValueProvider(Parameter parameter) { + if (!parameter.isAnnotationPresent(Stashed.class)) { + return null; + } + + return request -> injector.getInstance(Key.get(parameter.getType(), Stashed.class)); + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFeature.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFeature.java new file mode 100644 index 0000000..de2de9b --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashedValueFeature.java @@ -0,0 +1,31 @@ +package com.hubspot.dropwizard.guicier.objects; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.core.Feature; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.ext.Provider; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.server.spi.internal.ValueParamProvider; + +@Provider +public class StashedValueFeature implements Feature { + + @Inject + StashedValueFeature() {} + + @Override + public boolean configure(FeatureContext context) { + context.register( + new AbstractBinder() { + @Override + protected void configure() { + bind(StashedValueFactoryProvider.class) + .to(ValueParamProvider.class) + .in(Singleton.class); + } + } + ); + return true; + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestModule.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestModule.java new file mode 100644 index 0000000..e776be9 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestModule.java @@ -0,0 +1,21 @@ +package com.hubspot.dropwizard.guicier.objects; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Provides; + +public class StashesTestModule implements Module { + + public static final int STASHED_INT_VALUE = 42; + + @Override + public void configure(Binder binder) { + binder.bind(StashesTestResource.class); + } + + @Stashed + @Provides + public int providesStashedInt() { + return STASHED_INT_VALUE; + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestResource.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestResource.java new file mode 100644 index 0000000..16112ee --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/StashesTestResource.java @@ -0,0 +1,82 @@ +package com.hubspot.dropwizard.guicier.objects; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +import com.google.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +@Path("/stashes") +@Produces(APPLICATION_JSON) +public class StashesTestResource { + + private final int stashedIntCtor; + private final com.google.inject.Provider stashedIntGuiceProviderCtor; + private final jakarta.inject.Provider stashedIntJavaxProviderCtor; + + @Inject + @Stashed + private int stashedIntField; + + @Inject + @Stashed + private com.google.inject.Provider stashedIntGuiceProviderField; + + @Inject + @Stashed + private jakarta.inject.Provider stashedIntJavaxProviderField; + + @Inject + public StashesTestResource( + @Stashed int stashedIntCtor, + @Stashed com.google.inject.Provider stashedIntGuiceProviderCtor, + @Stashed jakarta.inject.Provider stashedIntJavaxProviderCtor + ) { + this.stashedIntCtor = stashedIntCtor; + this.stashedIntGuiceProviderCtor = stashedIntGuiceProviderCtor; + this.stashedIntJavaxProviderCtor = stashedIntJavaxProviderCtor; + } + + @GET + @Path("/itConstructorInjectsAStashedInt") + public int itConstructorInjectsAStashedInt() { + return stashedIntCtor; + } + + @GET + @Path("/itConstructorInjectsAStashedIntGuiceProvider") + public Integer itConstructorInjectsAStashedIntGuiceProvider() { + return stashedIntGuiceProviderCtor.get(); + } + + @GET + @Path("/itConstructorInjectsAStashedIntJavaxProvider") + public Integer itConstructorInjectsAStashedIntJavaxProvider() { + return stashedIntJavaxProviderCtor.get(); + } + + @GET + @Path("/itFieldInjectsAStashedInt") + public int itFieldInjectsAStashedInt() { + return stashedIntField; + } + + @GET + @Path("/itFieldInjectsAStashedIntGuiceProvider") + public Integer itFieldInjectsAStashedIntGuiceProvider() { + return stashedIntGuiceProviderField.get(); + } + + @GET + @Path("/itFieldInjectsAStashedIntJavaxProvider") + public Integer itFieldInjectsAStashedIntJavaxProvider() { + return stashedIntJavaxProviderField.get(); + } + + @GET + @Path("/itMethodInjectsAStashedInt") + public int itMethodInjectsAStashedInt(@Stashed int stashedInt) { + return stashedInt; + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestApplication.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestApplication.java index cc91062..c9d1722 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestApplication.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestApplication.java @@ -1,20 +1,24 @@ package com.hubspot.dropwizard.guicier.objects; import com.hubspot.dropwizard.guicier.GuiceBundle; -import io.dropwizard.Application; -import io.dropwizard.Configuration; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.core.Application; +import io.dropwizard.core.Configuration; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; public class TestApplication extends Application { + private GuiceBundle guiceBundle; + + public GuiceBundle getGuiceBundle() { + return guiceBundle; + } + @Override public void initialize(final Bootstrap bootstrap) { - final GuiceBundle jersey2GuiceBundle = GuiceBundle - .defaultBuilder(Configuration.class) - .modules(new TestModule()) - .build(); - bootstrap.addBundle(jersey2GuiceBundle); + this.guiceBundle = + GuiceBundle.defaultBuilder(Configuration.class).modules(new TestModule()).build(); + bootstrap.addBundle(guiceBundle); } @Override diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestModule.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestModule.java index fc1f43c..dd1f965 100644 --- a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestModule.java +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestModule.java @@ -5,7 +5,7 @@ import com.google.inject.Scopes; import com.google.inject.name.Named; import com.google.inject.name.Names; -import javax.inject.Singleton; +import jakarta.inject.Singleton; public class TestModule extends AbstractModule { @@ -29,6 +29,16 @@ protected void configure() { bind(ExplicitResource.class); bind(JerseyContextResource.class); + + bind(TestValueParamFeature.class); + + bind(StashedValueFeature.class); + install(new StashesTestModule()); + + bind(EmptyCtorGoogleInjectResource.class); + + bind(ComponentInvocationCounter.class); + bind(ContextInjectedFilter.class); } @Provides diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParam.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParam.java new file mode 100644 index 0000000..e90085d --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParam.java @@ -0,0 +1,11 @@ +package com.hubspot.dropwizard.guicier.objects; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestValueParam { +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamFeature.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamFeature.java new file mode 100644 index 0000000..4b4d790 --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamFeature.java @@ -0,0 +1,31 @@ +package com.hubspot.dropwizard.guicier.objects; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.core.Feature; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.ext.Provider; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.server.spi.internal.ValueParamProvider; + +@Provider +public class TestValueParamFeature implements Feature { + + @Inject + public TestValueParamFeature() {} + + @Override + public boolean configure(FeatureContext context) { + context.register( + new AbstractBinder() { + @Override + protected void configure() { + bind(TestValueParamProvider.class) + .to(ValueParamProvider.class) + .in(Singleton.class); + } + } + ); + return true; + } +} diff --git a/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamProvider.java b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamProvider.java new file mode 100644 index 0000000..c7cbd2d --- /dev/null +++ b/src/test/java/com/hubspot/dropwizard/guicier/objects/TestValueParamProvider.java @@ -0,0 +1,29 @@ +package com.hubspot.dropwizard.guicier.objects; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.util.function.Function; +import org.glassfish.jersey.model.Parameter.Source; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.internal.inject.AbstractValueParamProvider; +import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; +import org.glassfish.jersey.server.model.Parameter; + +public class TestValueParamProvider extends AbstractValueParamProvider { + + @Inject + TestValueParamProvider( + Provider extractorProvider + ) { + super(extractorProvider, Source.UNKNOWN); + } + + @Override + protected Function createValueProvider(Parameter parameter) { + if (!parameter.isAnnotationPresent(TestValueParam.class)) { + return null; + } + + return request -> "testparam"; + } +} diff --git a/src/test/resources/test-config.yml b/src/test/resources/test-config.yml index 001fe6f..de6facc 100644 --- a/src/test/resources/test-config.yml +++ b/src/test/resources/test-config.yml @@ -4,7 +4,7 @@ server: adminContextPath: /admin connector: type: http - port: 9999 + port: 0 # default logging relies on logback JMXConfigurator # which no longer exists