/** * Copyright 2014 Jordan Zimmerman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.soabase.guice; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.InjectableValues; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; import com.google.inject.AbstractModule; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import io.dropwizard.Configuration; import io.dropwizard.ConfiguredBundle; import io.dropwizard.configuration.ConfigurationFactory; import io.dropwizard.configuration.ConfigurationFactoryFactory; import io.dropwizard.jersey.DropwizardResourceConfig; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.ServiceLocatorProvider; import org.glassfish.jersey.server.monitoring.ApplicationEvent; import org.glassfish.jersey.server.monitoring.ApplicationEventListener; import org.glassfish.jersey.server.monitoring.RequestEvent; import org.glassfish.jersey.server.monitoring.RequestEventListener; import org.jvnet.hk2.guice.bridge.api.GuiceBridge; import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterRegistration; import javax.servlet.ServletRegistration; import javax.servlet.http.HttpServlet; import javax.validation.Validator; import javax.ws.rs.Path; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.ClientResponseFilter; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.DynamicFeature; import javax.ws.rs.core.Feature; import javax.ws.rs.core.FeatureContext; import javax.ws.rs.ext.ReaderInterceptor; import javax.ws.rs.ext.WriterInterceptor; import java.lang.reflect.Type; import java.util.Collection; import java.util.EnumSet; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; /** * Bundle for adding Guice support to Jersey 2.0 Resources */ public class GuiceBundle<T extends Configuration> implements ConfiguredBundle<T> { // guarded by sync private Injector injector = null; private final Logger log = LoggerFactory.getLogger(getClass()); private final InjectorProvider<T> injectorProvider; private final DropwizardResourceConfig loggingConfig = new DropwizardResourceConfig() { @Override public String getEndpointsInfo() { return "GuiceBundle - " + super.getEndpointsInfo(); } }; @SuppressWarnings("unchecked") private static final Collection<Class<?>> componentClasses = ImmutableSet.of ( ContainerRequestFilter.class, ContainerResponseFilter.class, ClientResponseFilter.class, ClientRequestFilter.class, DynamicFeature.class, ReaderInterceptor.class, WriterInterceptor.class ); /** * @param injectorProvider a provider for the Guice injector to use */ public GuiceBundle(InjectorProvider<T> injectorProvider) { this.injectorProvider = injectorProvider; } @Override public void initialize(Bootstrap<?> bootstrap) { final InjectableValues injectableValues = new InjectableValues() { @Override public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) { return null; } }; final ConfigurationFactoryFactory<? extends Configuration> configurationFactoryFactory = bootstrap.getConfigurationFactoryFactory(); ConfigurationFactoryFactory factoryFactory = new ConfigurationFactoryFactory() { @Override public ConfigurationFactory create(Class klass, Validator validator, ObjectMapper objectMapper, String propertyPrefix) { objectMapper.setInjectableValues(injectableValues); //noinspection unchecked return configurationFactoryFactory.create(klass, validator, objectMapper, propertyPrefix); } }; //noinspection unchecked bootstrap.setConfigurationFactoryFactory(factoryFactory); } @Override public void run(final T configuration, final Environment environment) throws Exception { Feature feature = new GuiceBundleFeature() { private final AtomicBoolean firstTime = new AtomicBoolean(true); @Override public boolean configure(FeatureContext context) { internalApply(context); return true; } private void internalApply(FeatureContext context) { ServiceLocator serviceLocator = ServiceLocatorProvider.getServiceLocator(context); GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator); GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class); AbstractModule additionalModule = new AbstractModule() { @Override protected void configure() { try { // make sure there are no compile-time references to Soa by using reflection Class.forName("io.soabase.core.SoaFeatures"); log.info("Installing SoaIntegrationModule"); Module soaIntegrationModule = (Module)Class.forName("io.soabase.guice.SoaIntegrationModule").getConstructor(Environment.class).newInstance(environment); install(soaIntegrationModule); } catch ( ClassNotFoundException ignore ) { // Soa has not been included - ignore log.info("SoaFeatures not available"); } catch ( Exception e ) { log.error("Could not instantiate SoaIntegrationModule", e); } } }; Injector localInjector = getInjector(configuration, environment, additionalModule); guiceBridge.bridgeGuiceInjector(localInjector); if ( firstTime.compareAndSet(true, false) ) { registerBoundJerseyComponents(localInjector, context, environment); } } }; environment.jersey().register(feature); ApplicationEventListener listener = new ApplicationEventListener() { @Override public void onEvent(ApplicationEvent event) { if ( event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED ) { loggingConfig.logComponents(); } } @Override public RequestEventListener onRequest(RequestEvent requestEvent) { return null; } }; environment.jersey().register(listener); } private void registerBoundJerseyComponents(Injector injector, FeatureContext context, Environment environment) { // mostly copied from GuiceComponentProviderFactory#register(ResourceConfig, Injector) while ( injector != null ) { for ( Key<?> key : injector.getBindings().keySet() ) { Type type = key.getTypeLiteral().getType(); if ( type instanceof Class ) { Class<?> c = (Class)type; if ( isProviderClass(c) ) { log.info(String.format("Registering %s as a provider class", c.getName())); context.register(c); loggingConfig.register(c); } else if ( isRootResourceClass(c) ) { log.info(String.format("Registering %s as a root resource class", c.getName())); context.register(c); loggingConfig.register(c); } else if ( componentClasses.contains(c) ) { log.info(String.format("Registering %s", c.getName())); context.register(c); loggingConfig.register(c); } else if ( FilterDefinition.class.equals(c) ) { registerFilter(injector, environment, injector.getBinding(key)); loggingConfig.register(c); } else if ( ServletDefinition.class.equals(c) ) { registerServlet(injector, environment, injector.getBinding(key)); loggingConfig.register(c); } else if ( InternalFilter.class.equals(c) ) { log.debug("Registering internal filter"); context.register(injector.getBinding(key).getProvider().get()); loggingConfig.register(c); } else if ( InternalCommonConfig.class.equals(c) ) { applyInternalCommonConfig(context, (InternalCommonConfig)injector.getBinding(key).getProvider().get()); loggingConfig.register(c); } } } injector = injector.getParent(); } } private synchronized Injector getInjector(T configuration, Environment environment, Module module) { if ( injector == null ) { injector = injectorProvider.get(configuration, environment, module); } return injector; } private void applyInternalCommonConfig(FeatureContext context, InternalCommonConfig internalCommonConfig) { for ( Class<?> clazz : internalCommonConfig.getClasses() ) { log.info(String.format("Registering %s as a component", clazz)); context.register(clazz); } for ( Object obj : internalCommonConfig.getInstances() ) { log.info(String.format("Registering instance of %s as a component", obj.getClass())); context.register(obj); } for ( Map.Entry<String, Object> entry : internalCommonConfig.getProperties().entrySet() ) { String key = entry.getKey(); Object value = entry.getValue(); log.info(String.format("Registering property key: %s\tvalue: %s", key, value)); context.property(key, value); } } private void registerServlet(Injector injector, Environment environment, Binding<?> binding) { ServletDefinition servletDefinition = (ServletDefinition)binding.getProvider().get(); log.info("Registering servlet: " + servletDefinition); HttpServlet servletInstance = servletDefinition.getServletInstance(); if ( servletInstance == null ) { servletInstance = injector.getInstance(servletDefinition.getServletKey()); } ServletRegistration.Dynamic registration = environment.servlets().addServlet(servletDefinition.getServletKey().toString(), servletInstance); registration.setInitParameters(servletDefinition.getInitParams()); registration.addMapping(servletDefinition.getPatterns()); } private void registerFilter(Injector injector, Environment environment, Binding<?> binding) { FilterDefinition filterDefinition = (FilterDefinition)binding.getProvider().get(); log.info("Registering filter: " + filterDefinition); Filter filterInstance = filterDefinition.getFilterInstance(); if ( filterInstance == null ) { filterInstance = injector.getInstance(filterDefinition.getFilterKey()); } FilterRegistration.Dynamic registration = environment.servlets().addFilter(filterDefinition.getFilterKey().toString(), filterInstance); registration.setInitParameters(filterDefinition.getInitParams()); registration.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, filterDefinition.getUriPatterns()); } // copied from Jersey 1.17.1 private static boolean isProviderClass(Class<?> c) { return (c != null) && c.isAnnotationPresent(javax.ws.rs.ext.Provider.class); } // copied from Jersey 1.17.1 private static boolean isRootResourceClass(Class<?> c) { if ( c == null ) { return false; } else if ( c.isAnnotationPresent(Path.class) ) { return true; } else { Class[] arr = c.getInterfaces(); int len = arr.length; //noinspection ForLoopReplaceableByForEach for ( int i = 0; i < len; ++i ) { Class clazz = arr[i]; if ( clazz.isAnnotationPresent(Path.class) ) { return true; } } return false; } } }