/* * Copyright 2014-2016 Squarespace, Inc. * * 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 com.squarespace.jersey2.guice; import static com.squarespace.jersey2.guice.BindingUtils.newGuiceInjectionResolverDescriptor; import static com.squarespace.jersey2.guice.BindingUtils.newThreeThirtyInjectionResolverDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Singleton; import org.glassfish.hk2.api.ActiveDescriptor; import org.glassfish.hk2.api.DynamicConfiguration; import org.glassfish.hk2.api.DynamicConfigurationService; import org.glassfish.hk2.api.InjectionResolver; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.api.ServiceLocatorFactory; import org.glassfish.hk2.extension.ServiceLocatorGenerator; import org.glassfish.hk2.utilities.Binder; import org.glassfish.hk2.utilities.BuilderHelper; import org.glassfish.jersey.message.internal.MessagingBinders; import org.jvnet.hk2.external.generator.ServiceLocatorGeneratorImpl; import org.jvnet.hk2.internal.DefaultClassAnalyzer; import org.jvnet.hk2.internal.DynamicConfigurationImpl; import org.jvnet.hk2.internal.DynamicConfigurationServiceImpl; import org.jvnet.hk2.internal.InstantiationServiceImpl; import org.jvnet.hk2.internal.ServiceLocatorImpl; import org.jvnet.hk2.internal.ServiceLocatorRuntimeImpl; import org.jvnet.hk2.internal.Utilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.spi.ElementSource; /** * An utility class to bootstrap HK2's {@link ServiceLocator}s and {@link Guice}'s {@link Injector}s. * * @see ServiceLocator * @see Injector */ public class JerseyGuiceUtils { private static final Logger LOG = LoggerFactory.getLogger(JerseyGuiceUtils.class); private static final String MODIFIERS_FIELD = "modifiers"; private static final String PREFIX = "GuiceServiceLocator-"; private static final AtomicInteger NTH = new AtomicInteger(); private static boolean SPI_CHECKED = false; private static boolean SPI_PRESENT = false; private JerseyGuiceUtils() {} /** * Installs the given {@link Injector}. * * @see JerseyGuiceModule */ public static void install(Injector injector) { // This binding is provided by JerseyGuiceModule ServiceLocator locator = injector.getInstance(ServiceLocator.class); GuiceServiceLocatorGenerator generator = getOrCreateGuiceServiceLocatorGenerator(); generator.add(locator); } /** * Installs a {@link ServiceLocatorGenerator} instead of an {@link Injector}. This * is mostly needed for testing and bootstrapping. * * @see #install(Injector) */ public static void install(ServiceLocatorGenerator delegate) { GuiceServiceLocatorGenerator generator = getOrCreateGuiceServiceLocatorGenerator(); generator.delegate(delegate); } /** * */ public static void reset() { GuiceServiceLocatorGenerator generator = getOrCreateGuiceServiceLocatorGenerator(); generator.reset(); } private static synchronized GuiceServiceLocatorGenerator getOrCreateGuiceServiceLocatorGenerator() { // Use SPI if (isProviderPresent()) { GuiceServiceLocatorGenerator generator = (GuiceServiceLocatorGenerator)GuiceServiceLocatorGeneratorStub.get(); if (generator == null) { generator = new GuiceServiceLocatorGenerator(); GuiceServiceLocatorGeneratorStub.install(generator); } return generator; } // Use Reflection GuiceServiceLocatorFactory factory = getOrCreateFactory(); if (factory != null) { GuiceServiceLocatorGenerator generator = (GuiceServiceLocatorGenerator)factory.get(); if (generator == null) { generator = new GuiceServiceLocatorGenerator(); factory.install(generator); } return generator; } throw new IllegalStateException(); } /** * Returns {@code true} if jersey2-guice SPI is present. */ private static synchronized boolean isProviderPresent() { if (!SPI_CHECKED) { SPI_CHECKED = true; ServiceLocatorGenerator generator = lookupSPI(); if (generator instanceof GuiceServiceLocatorGeneratorStub) { SPI_PRESENT = true; } if (!SPI_PRESENT) { LOG.warn("It appears jersey2-guice-spi is either not present or in conflict with some other Jar: {}", generator); } } return SPI_PRESENT; } private static ServiceLocatorGenerator lookupSPI() { return AccessController.doPrivileged(new PrivilegedAction<ServiceLocatorGenerator>() { @Override public ServiceLocatorGenerator run() { try { ClassLoader classLoader = JerseyGuiceUtils.class.getClassLoader(); ServiceLoader<ServiceLocatorGenerator> providers = ServiceLoader.load(ServiceLocatorGenerator.class, classLoader); for (ServiceLocatorGenerator generator : providers) { return generator; } } catch (Throwable th) { LOG.warn("Exception", th); } return null; } }); } /** * @see ServiceLocatorFactory */ private static synchronized GuiceServiceLocatorFactory getOrCreateFactory() { ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance(); if (factory instanceof GuiceServiceLocatorFactory) { return (GuiceServiceLocatorFactory)factory; } if (LOG.isInfoEnabled()) { LOG.info("Attempting to install (using relfection) a Guice-aware ServiceLocatorFactory..."); } try { GuiceServiceLocatorFactory guiceServiceLocatorFactory = new GuiceServiceLocatorFactory(factory); Class<?> clazz = ServiceLocatorFactory.class; Field field = clazz.getDeclaredField("INSTANCE"); set(field, null, guiceServiceLocatorFactory); return guiceServiceLocatorFactory; } catch (Exception err) {err.printStackTrace(); LOG.error("Exception", err); } return null; } /** * @see #newServiceLocator(String, ServiceLocator) */ public static ServiceLocator newServiceLocator() { return newServiceLocator(null); } /** * @see #newServiceLocator(String, ServiceLocator) */ public static ServiceLocator newServiceLocator(String name) { return newServiceLocator(name, null); } /** * Creates and returns a {@link ServiceLocator}. * * NOTE: This code should be very similar to HK2's own {@link ServiceLocatorGeneratorImpl}. */ public static ServiceLocator newServiceLocator(String name, ServiceLocator parent) { if (parent != null && !(parent instanceof ServiceLocatorImpl)) { throw new IllegalArgumentException("name=" + name + ", parent=" + parent); } if (name == null) { name = PREFIX; } if (name.endsWith("-")) { name += NTH.incrementAndGet(); } GuiceServiceLocator locator = new GuiceServiceLocator(name, parent); DynamicConfigurationImpl config = new DynamicConfigurationImpl(locator); config.bind(Utilities.getLocatorDescriptor(locator)); ActiveDescriptor<InjectionResolver<javax.inject.Inject>> threeThirtyResolver = newThreeThirtyInjectionResolverDescriptor(locator); config.addActiveDescriptor(threeThirtyResolver); config.addActiveDescriptor(newGuiceInjectionResolverDescriptor( locator, threeThirtyResolver)); config.bind(BuilderHelper.link(DynamicConfigurationServiceImpl.class, false). to(DynamicConfigurationService.class). in(Singleton.class.getName()). localOnly(). build()); config.bind(BuilderHelper.createConstantDescriptor( new DefaultClassAnalyzer(locator))); config.bind(BuilderHelper.createDescriptorFromClass(ServiceLocatorRuntimeImpl.class)); config.bind(BuilderHelper.createConstantDescriptor( new InstantiationServiceImpl())); config.commit(); return locator; } /** * This method links the {@link Injector} to the {@link ServiceLocator}. */ public static ServiceLocator link(ServiceLocator locator, Injector injector) { Map<Key<?>, Binding<?>> bindings = gatherBindings(injector); Set<Binder> binders = toBinders(bindings); return link(locator, injector, binders); } /** * Gathers Guice {@link Injector} bindings over the hierarchy. */ private static Map<Key<?>, Binding<?>> gatherBindings(Injector injector) { Map<Key<?>, Binding<?>> dst = new HashMap<Key<?>, Binding<?>>(); Injector current = injector; while (current != null) { dst.putAll(current.getBindings()); current = current.getParent(); } return dst; } /** * @see #link(ServiceLocator, Injector) * @see #newChildInjector(Injector, ServiceLocator) */ private static ServiceLocator link(ServiceLocator locator, Injector injector, Iterable<? extends Binder> binders) { DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class); DynamicConfiguration dc = dcs.createDynamicConfiguration(); GuiceJustInTimeResolver resolver = new GuiceJustInTimeResolver(locator, injector); dc.bind(BuilderHelper.createConstantDescriptor(resolver)); dc.addActiveDescriptor(GuiceScopeContext.class); bind(locator, dc, new MessagingBinders.HeaderDelegateProviders()); for (Binder binder : binders) { bind(locator, dc, binder); } dc.commit(); return locator; } /** * @see ServiceLocator#inject(Object) * @see Binder#bind(DynamicConfiguration) */ private static void bind(ServiceLocator locator, DynamicConfiguration dc, Binder binder) { locator.inject(binder); binder.bind(dc); } /** * Turns the given Guice {@link Binding}s into HK2 {@link Binder}s. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static Set<Binder> toBinders(Map<Key<?>, Binding<?>> bindings) { Set<Binder> binders = new HashSet<>(); for (Map.Entry<Key<?>, Binding<?>> entry : bindings.entrySet()) { Key<?> key = entry.getKey(); Binding<?> binding = entry.getValue(); Object source = binding.getSource(); if (!(source instanceof ElementSource)) { // Things like the Injector itself don't have an ElementSource. if (LOG.isTraceEnabled()) { LOG.trace("Adding binding: key={}, source={}", key, source); } binders.add(new GuiceBinder(key, binding)); continue; } ElementSource element = (ElementSource)source; List<String> names = element.getModuleClassNames(); String name = names.get(0); // Skip everything that is declared in a JerseyModule try { Class<?> module; // Attempt to load the classes via the context class loader first, in order to support // environments that enforce tighter constraints on class loading (such as in an OSGi container) ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader != null) { module = classLoader.loadClass(name); } else { module = Class.forName(name); } if (JerseyModule.class.isAssignableFrom(module)) { if (LOG.isTraceEnabled()) { LOG.trace("Ignoring binding {} in {}", key, module); } continue; } } catch (ClassNotFoundException err) { // Some modules may not be able to be instantiated directly here as a class if we're running // in a container that enforcer tighter class loader constraints (such as the // org.ops4j.peaberry.osgi.OSGiModule Guice module when running in an OSGi container), // so we're only logging a warning here instead of throwing a hard exception if (LOG.isWarnEnabled()) { LOG.warn("Unavailable to load class in order to validate module: name={}", name); } } binders.add(new GuiceBinder(key, binding)); } return binders; } private static void set(Field field, Object instance, Object value) throws IllegalAccessException, NoSuchFieldException, SecurityException { field.setAccessible(true); int modifiers = field.getModifiers(); if (Modifier.isFinal(modifiers)) { setModifiers(field, modifiers & ~Modifier.FINAL); try { field.set(instance, value); } finally { setModifiers(field, modifiers | Modifier.FINAL); } } else { field.set(instance, value); } } private static void setModifiers(Field dst, int modifiers) throws IllegalAccessException, NoSuchFieldException, SecurityException { Field field = Field.class.getDeclaredField(MODIFIERS_FIELD); field.setAccessible(true); field.setInt(dst, modifiers); } }