package ru.vyarus.dropwizard.guice.module.installer.feature.jersey.provider; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Stage; import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.internal.inject.InjectionResolver; import org.glassfish.jersey.server.monitoring.ApplicationEventListener; import org.glassfish.jersey.server.spi.internal.ValueParamProvider; import ru.vyarus.dropwizard.guice.module.installer.feature.jersey.AbstractJerseyInstaller; import ru.vyarus.dropwizard.guice.module.installer.feature.jersey.JerseyManaged; import ru.vyarus.dropwizard.guice.module.installer.install.binding.BindingInstaller; import ru.vyarus.dropwizard.guice.module.installer.order.Order; import ru.vyarus.dropwizard.guice.module.installer.util.BindingUtils; import ru.vyarus.dropwizard.guice.module.installer.util.FeatureUtils; import ru.vyarus.java.generics.resolver.GenericsResolver; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.DynamicFeature; import javax.ws.rs.ext.*; import java.util.Set; import java.util.function.Supplier; import static ru.vyarus.dropwizard.guice.module.installer.util.FeatureUtils.is; import static ru.vyarus.dropwizard.guice.module.installer.util.JerseyBinding.*; /** * Jersey provider installer. * Looks for classes annotated with {@link javax.ws.rs.ext.Provider} and register bindings in HK context. * <p> * If provider is annotated with {@link JerseyManaged} it's instance will be created by HK2, not guice. * This is important when extensions directly depends on HK beans (no way to wrap with {@link Provider} * or if it's eager extension, which instantiated by HK immediately (when hk-guice contexts not linked yet). * <p> * In some cases {@link ru.vyarus.dropwizard.guice.module.installer.install.binding.LazyBinding} could * be an alternative to {@link JerseyManaged} * <p> * Force singleton scope for extensions, but not for beans having explicit scope annotation. * See {@link ru.vyarus.dropwizard.guice.module.installer.InstallersOptions#ForceSingletonForJerseyExtensions}. * {@link ru.vyarus.dropwizard.guice.module.support.scope.Prototype} annotation may be used on guice beans * to declare bean in prototype scope (prevent forced singleton). * * @author Vyacheslav Rusakov * @see JerseyManaged * @see ru.vyarus.dropwizard.guice.module.installer.feature.jersey.GuiceManaged * @see ru.vyarus.dropwizard.guice.module.installer.install.binding.LazyBinding * @since 10.10.2014 */ @Order(30) public class JerseyProviderInstaller extends AbstractJerseyInstaller<Object> implements BindingInstaller { private static final Set<Class<?>> EXTENSION_TYPES = ImmutableSet.<Class<?>>of( ExceptionMapper.class, ParamConverterProvider.class, ContextResolver.class, MessageBodyReader.class, MessageBodyWriter.class, ReaderInterceptor.class, WriterInterceptor.class, ContainerRequestFilter.class, ContainerResponseFilter.class, DynamicFeature.class, ValueParamProvider.class, InjectionResolver.class, ApplicationEventListener.class ); private final ProviderReporter reporter = new ProviderReporter(); @Override public boolean matches(final Class<?> type) { return FeatureUtils.hasAnnotation(type, Provider.class); } @Override public void bind(final Binder binder, final Class<?> type, final boolean lazyMarker) { final boolean hkManaged = isJerseyExtension(type); final boolean lazy = isLazy(type, lazyMarker); // register in guice only if not managed by hk and just in time (lazy) binding not requested if (!hkManaged && !lazy) { bindInGuice(binder, type); } } @Override public <T> void manualBinding(final Binder binder, final Class<T> type, final Binding<T> binding) { // no need to bind in case of manual binding final boolean hkManaged = isJerseyExtension(type); Preconditions.checkState(!hkManaged, // intentially no "at" before stacktrtace because idea may hide error in some cases "Provider annotated as jersey managed is declared manually in guice: %s (%s)", type.getName(), BindingUtils.getDeclarationSource(binding)); } @Override public void extensionBound(final Stage stage, final Class<?> type) { if (stage != Stage.TOOL) { // reporting (common for both registration types) final boolean hkManaged = isJerseyExtension(type); reporter.provider(type, hkManaged, false); } } @Override public void install(final AbstractBinder binder, final Injector injector, final Class<Object> type) { final boolean hkExtension = isJerseyExtension(type); final boolean forceSingleton = isForceSingleton(type, hkExtension); // since jersey 2.26 internal hk Factory class replaced by java 8 Supplier if (is(type, Supplier.class)) { // register factory directly (without wrapping) bindFactory(binder, injector, type, hkExtension, forceSingleton); } else { // support multiple extension interfaces on one type final Set<Class<?>> extensions = Sets.intersection(EXTENSION_TYPES, GenericsResolver.resolve(type).getGenericsInfo().getComposingTypes()); if (!extensions.isEmpty()) { for (Class<?> ext : extensions) { bindSpecificComponent(binder, injector, type, ext, hkExtension, forceSingleton); } } else { // no known extension found bindComponent(binder, injector, type, hkExtension, forceSingleton); } } } @Override public void report() { reporter.report(); } }