| // Copyright (C) 2012 The Android Open Source Project |
| // |
| // 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.google.gerrit.server.plugins; |
| |
| import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicItemsOf; |
| import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicMapsOf; |
| import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicSetsOf; |
| import static java.util.Objects.requireNonNull; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.LinkedListMultimap; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.common.UsedAt; |
| import com.google.gerrit.extensions.annotations.RootRelative; |
| import com.google.gerrit.extensions.events.LifecycleListener; |
| import com.google.gerrit.extensions.registration.DynamicItem; |
| import com.google.gerrit.extensions.registration.DynamicMap; |
| import com.google.gerrit.extensions.registration.DynamicSet; |
| import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl; |
| import com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes; |
| import com.google.gerrit.extensions.registration.RegistrationHandle; |
| import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle; |
| import com.google.gerrit.extensions.systemstatus.ServerInformation; |
| import com.google.gerrit.extensions.webui.WebUiPlugin; |
| import com.google.gerrit.index.IndexCollection; |
| import com.google.gerrit.metrics.MetricMaker; |
| import com.google.gerrit.server.util.PluginRequestContext; |
| import com.google.gerrit.server.util.RequestContext; |
| import com.google.gerrit.server.util.ThreadLocalRequestContext; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Binding; |
| import com.google.inject.Guice; |
| import com.google.inject.Inject; |
| import com.google.inject.Injector; |
| import com.google.inject.Key; |
| import com.google.inject.Module; |
| import com.google.inject.Provider; |
| import com.google.inject.Singleton; |
| import com.google.inject.TypeLiteral; |
| import com.google.inject.internal.UniqueAnnotations; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.ParameterizedType; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| /** |
| * Tracks Guice bindings that should be exposed to loaded plugins. |
| * |
| * <p>This is an internal implementation detail of how the main server is able to export its |
| * explicit Guice bindings to tightly coupled plugins, giving them access to singletons and request |
| * scoped resources just like any core code. |
| */ |
| @Singleton |
| public class PluginGuiceEnvironment { |
| private final Injector sysInjector; |
| private final ServerInformation srvInfo; |
| private final ThreadLocalRequestContext local; |
| private final CopyConfigModule copyConfigModule; |
| private final Set<Key<?>> copyConfigKeys; |
| private final List<StartPluginListener> onStart; |
| private final List<StopPluginListener> onStop; |
| private final List<ReloadPluginListener> onReload; |
| private final MetricMaker serverMetrics; |
| |
| private Module sysModule; |
| private Module sshModule; |
| private Module httpModule; |
| private Injector apiInjector; |
| |
| private Provider<ModuleGenerator> sshGen; |
| private Provider<ModuleGenerator> httpGen; |
| |
| private Map<TypeLiteral<?>, DynamicItem<?>> sysItems; |
| private Map<TypeLiteral<?>, DynamicItem<?>> sshItems; |
| private Map<TypeLiteral<?>, DynamicItem<?>> httpItems; |
| private Map<TypeLiteral<?>, DynamicItem<?>> apiItems; |
| |
| private Map<TypeLiteral<?>, DynamicSet<?>> sysSets; |
| private Map<TypeLiteral<?>, DynamicSet<?>> sshSets; |
| private Map<TypeLiteral<?>, DynamicSet<?>> httpSets; |
| private Map<TypeLiteral<?>, DynamicSet<?>> apiSets; |
| |
| private Map<TypeLiteral<?>, DynamicMap<?>> sysMaps; |
| private Map<TypeLiteral<?>, DynamicMap<?>> sshMaps; |
| private Map<TypeLiteral<?>, DynamicMap<?>> httpMaps; |
| private Map<TypeLiteral<?>, DynamicMap<?>> apiMaps; |
| |
| @Inject |
| PluginGuiceEnvironment( |
| Injector sysInjector, |
| ThreadLocalRequestContext local, |
| ServerInformation srvInfo, |
| CopyConfigModule ccm, |
| MetricMaker serverMetrics) { |
| this.sysInjector = sysInjector; |
| this.srvInfo = srvInfo; |
| this.local = local; |
| this.copyConfigModule = ccm; |
| this.copyConfigKeys = Guice.createInjector(ccm).getAllBindings().keySet(); |
| this.serverMetrics = serverMetrics; |
| |
| onStart = new CopyOnWriteArrayList<>(); |
| onStart.addAll(listeners(sysInjector, StartPluginListener.class)); |
| |
| onStop = new CopyOnWriteArrayList<>(); |
| onStop.addAll(listeners(sysInjector, StopPluginListener.class)); |
| |
| onReload = new CopyOnWriteArrayList<>(); |
| onReload.addAll(listeners(sysInjector, ReloadPluginListener.class)); |
| |
| sysItems = dynamicItemsOf(sysInjector); |
| sysSets = dynamicSetsOf(sysInjector); |
| sysMaps = dynamicMapsOf(sysInjector); |
| |
| apiSets = new HashMap<>(); |
| apiItems = new HashMap<>(); |
| apiMaps = new HashMap<>(); |
| } |
| |
| ServerInformation getServerInformation() { |
| return srvInfo; |
| } |
| |
| MetricMaker getServerMetrics() { |
| return serverMetrics; |
| } |
| |
| boolean hasDynamicItem(TypeLiteral<?> type) { |
| return sysItems.containsKey(type) |
| || (sshItems != null && sshItems.containsKey(type)) |
| || (httpItems != null && httpItems.containsKey(type)); |
| } |
| |
| boolean hasDynamicSet(TypeLiteral<?> type) { |
| return sysSets.containsKey(type) |
| || (sshSets != null && sshSets.containsKey(type)) |
| || (httpSets != null && httpSets.containsKey(type)); |
| } |
| |
| boolean hasDynamicMap(TypeLiteral<?> type) { |
| return sysMaps.containsKey(type) |
| || (sshMaps != null && sshMaps.containsKey(type)) |
| || (httpMaps != null && httpMaps.containsKey(type)); |
| } |
| |
| public Module getSysModule() { |
| return sysModule; |
| } |
| |
| public void setDbCfgInjector(Injector dbInjector, Injector cfgInjector) { |
| final Module db = copy(dbInjector); |
| final Module cm = copy(cfgInjector); |
| final Module sm = copy(sysInjector); |
| sysModule = |
| new AbstractModule() { |
| @Override |
| protected void configure() { |
| install(copyConfigModule); |
| install(db); |
| install(cm); |
| install(sm); |
| } |
| }; |
| } |
| |
| public void setSshInjector(Injector injector) { |
| sshModule = copy(injector); |
| sshGen = injector.getProvider(ModuleGenerator.class); |
| sshItems = dynamicItemsOf(injector); |
| sshSets = dynamicSetsOf(injector); |
| sshMaps = dynamicMapsOf(injector); |
| onStart.addAll(listeners(injector, StartPluginListener.class)); |
| onStop.addAll(listeners(injector, StopPluginListener.class)); |
| onReload.addAll(listeners(injector, ReloadPluginListener.class)); |
| } |
| |
| boolean hasSshModule() { |
| return sshModule != null; |
| } |
| |
| Module getSshModule() { |
| return sshModule; |
| } |
| |
| ModuleGenerator newSshModuleGenerator() { |
| return sshGen.get(); |
| } |
| |
| public void setHttpInjector(Injector injector) { |
| httpModule = copy(injector); |
| httpGen = injector.getProvider(ModuleGenerator.class); |
| httpItems = dynamicItemsOf(injector); |
| httpSets = httpDynamicSetsOf(injector); |
| httpMaps = dynamicMapsOf(injector); |
| onStart.addAll(listeners(injector, StartPluginListener.class)); |
| onStop.addAll(listeners(injector, StopPluginListener.class)); |
| onReload.addAll(listeners(injector, ReloadPluginListener.class)); |
| } |
| |
| private Map<TypeLiteral<?>, DynamicSet<?>> httpDynamicSetsOf(Injector i) { |
| // Copy binding of DynamicSet<WebUiPlugin> from sysInjector to HTTP. |
| // This supports older plugins that bound a plugin in the HttpModule. |
| TypeLiteral<WebUiPlugin> key = TypeLiteral.get(WebUiPlugin.class); |
| DynamicSet<?> web = sysSets.get(key); |
| requireNonNull(web, "DynamicSet<WebUiPlugin> exists in sysInjector"); |
| |
| Map<TypeLiteral<?>, DynamicSet<?>> m = new HashMap<>(dynamicSetsOf(i)); |
| m.put(key, web); |
| return Collections.unmodifiableMap(m); |
| } |
| |
| boolean hasHttpModule() { |
| return httpModule != null; |
| } |
| |
| @UsedAt(UsedAt.Project.GOOGLE) |
| public Module getHttpModule() { |
| return httpModule; |
| } |
| |
| ModuleGenerator newHttpModuleGenerator() { |
| return httpGen.get(); |
| } |
| |
| public RequestContext enter(Plugin plugin) { |
| return local.setContext(new PluginRequestContext(plugin.getPluginUser())); |
| } |
| |
| public void exit(RequestContext old) { |
| local.setContext(old); |
| } |
| |
| public void onStartPlugin(Plugin plugin) { |
| RequestContext oldContext = enter(plugin); |
| try { |
| attachItem(sysItems, plugin.getSysInjector(), plugin); |
| attachItem(sshItems, plugin.getSshInjector(), plugin); |
| attachItem(httpItems, plugin.getHttpInjector(), plugin); |
| |
| attachSet(sysSets, plugin.getSysInjector(), plugin); |
| attachSet(sshSets, plugin.getSshInjector(), plugin); |
| attachSet(httpSets, plugin.getHttpInjector(), plugin); |
| |
| attachMap(sysMaps, plugin.getSysInjector(), plugin); |
| attachMap(sshMaps, plugin.getSshInjector(), plugin); |
| attachMap(httpMaps, plugin.getHttpInjector(), plugin); |
| |
| apiInjector = Optional.ofNullable(plugin.getApiInjector()).orElse(apiInjector); |
| |
| if (apiInjector != null) { |
| apiItems.putAll(dynamicItemsOf(apiInjector)); |
| apiSets.putAll(dynamicSetsOf(apiInjector)); |
| apiMaps.putAll(dynamicMapsOf(apiInjector)); |
| |
| List<Injector> allPluginInjectors = |
| listOfInjectors( |
| plugin.getSysInjector(), plugin.getSshInjector(), plugin.getHttpInjector()); |
| allPluginInjectors.forEach(i -> attachItem(apiItems, i, plugin)); |
| allPluginInjectors.forEach(i -> attachSet(apiSets, i, plugin)); |
| allPluginInjectors.forEach(i -> attachMap(apiMaps, i, plugin)); |
| } |
| } finally { |
| exit(oldContext); |
| } |
| |
| for (StartPluginListener l : onStart) { |
| l.onStartPlugin(plugin); |
| } |
| } |
| |
| private List<Injector> listOfInjectors(Injector... injectors) { |
| ImmutableList.Builder<Injector> injectorsListBuilder = ImmutableList.builder(); |
| |
| for (Injector injector : injectors) { |
| if (injector != null) { |
| injectorsListBuilder.add(injector); |
| } |
| } |
| |
| return injectorsListBuilder.build(); |
| } |
| |
| public void onStopPlugin(Plugin plugin) { |
| for (StopPluginListener l : onStop) { |
| l.onStopPlugin(plugin); |
| } |
| } |
| |
| private void attachItem( |
| Map<TypeLiteral<?>, DynamicItem<?>> items, @Nullable Injector src, Plugin plugin) { |
| for (RegistrationHandle h : |
| PrivateInternals_DynamicTypes.attachItems(src, plugin.getName(), items)) { |
| plugin.add(h); |
| } |
| } |
| |
| private void attachSet( |
| Map<TypeLiteral<?>, DynamicSet<?>> sets, @Nullable Injector src, Plugin plugin) { |
| for (RegistrationHandle h : |
| PrivateInternals_DynamicTypes.attachSets(src, plugin.getName(), sets)) { |
| plugin.add(h); |
| } |
| } |
| |
| private void attachMap( |
| Map<TypeLiteral<?>, DynamicMap<?>> maps, @Nullable Injector src, Plugin plugin) { |
| for (RegistrationHandle h : |
| PrivateInternals_DynamicTypes.attachMaps(src, plugin.getName(), maps)) { |
| plugin.add(h); |
| } |
| } |
| |
| void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin) { |
| // Index all old registrations by the raw type. These may be replaced |
| // during the reattach calls below. Any that are not replaced will be |
| // removed when the old plugin does its stop routine. |
| ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> old = LinkedListMultimap.create(); |
| for (ReloadableRegistrationHandle<?> h : oldPlugin.getReloadableHandles()) { |
| old.put(h.getKey().getTypeLiteral(), h); |
| } |
| |
| RequestContext oldContext = enter(newPlugin); |
| try { |
| Optional.ofNullable(newPlugin.getApiInjector()) |
| .ifPresent(i -> reattachMap(old, apiMaps, i, newPlugin)); |
| reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin); |
| reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin); |
| reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin); |
| |
| Optional.ofNullable(newPlugin.getApiInjector()) |
| .ifPresent(i -> reattachSet(old, apiSets, i, newPlugin)); |
| reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin); |
| reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin); |
| reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin); |
| |
| Optional.ofNullable(newPlugin.getApiInjector()) |
| .ifPresent(i -> reattachItem(old, apiItems, i, newPlugin)); |
| reattachItem(old, sysItems, newPlugin.getSysInjector(), newPlugin); |
| reattachItem(old, sshItems, newPlugin.getSshInjector(), newPlugin); |
| reattachItem(old, httpItems, newPlugin.getHttpInjector(), newPlugin); |
| |
| apiInjector = Optional.ofNullable(newPlugin.getApiInjector()).orElse(apiInjector); |
| |
| if (apiInjector != null) { |
| apiItems.putAll(dynamicItemsOf(apiInjector)); |
| apiSets.putAll(dynamicSetsOf(apiInjector)); |
| apiMaps.putAll(dynamicMapsOf(apiInjector)); |
| |
| List<Injector> allPluginInjectors = |
| listOfInjectors( |
| newPlugin.getSysInjector(), |
| newPlugin.getSshInjector(), |
| newPlugin.getHttpInjector()); |
| allPluginInjectors.forEach(i -> reattachItem(old, apiItems, i, newPlugin)); |
| allPluginInjectors.forEach(i -> reattachSet(old, apiSets, i, newPlugin)); |
| allPluginInjectors.forEach(i -> reattachMap(old, apiMaps, i, newPlugin)); |
| } |
| } finally { |
| exit(oldContext); |
| } |
| |
| for (ReloadPluginListener l : onReload) { |
| l.onReloadPlugin(oldPlugin, newPlugin); |
| } |
| } |
| |
| private void reattachMap( |
| ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles, |
| Map<TypeLiteral<?>, DynamicMap<?>> maps, |
| @Nullable Injector src, |
| Plugin newPlugin) { |
| if (src == null || maps == null || maps.isEmpty()) { |
| return; |
| } |
| |
| for (Map.Entry<TypeLiteral<?>, DynamicMap<?>> e : maps.entrySet()) { |
| @SuppressWarnings("unchecked") |
| TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey(); |
| |
| @SuppressWarnings("unchecked") |
| PrivateInternals_DynamicMapImpl<Object> map = |
| (PrivateInternals_DynamicMapImpl<Object>) e.getValue(); |
| |
| Map<Annotation, ReloadableRegistrationHandle<?>> am = new HashMap<>(); |
| for (ReloadableRegistrationHandle<?> h : oldHandles.get(type)) { |
| Annotation a = h.getKey().getAnnotation(); |
| if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) { |
| am.put(a, h); |
| } |
| } |
| |
| for (Binding<?> binding : bindings(src, e.getKey())) { |
| @SuppressWarnings("unchecked") |
| Binding<Object> b = (Binding<Object>) binding; |
| Key<Object> key = b.getKey(); |
| if (key.getAnnotation() == null) { |
| continue; |
| } |
| |
| @SuppressWarnings("unchecked") |
| ReloadableRegistrationHandle<Object> h = |
| (ReloadableRegistrationHandle<Object>) am.remove(key.getAnnotation()); |
| if (h != null) { |
| replace(newPlugin, h, b); |
| oldHandles.remove(type, h); |
| } else { |
| newPlugin.add(map.put(newPlugin.getName(), b.getKey(), b.getProvider())); |
| } |
| } |
| } |
| } |
| |
| /** Type used to declare unique annotations. Guice hides this, so extract it. */ |
| private static final Class<?> UNIQUE_ANNOTATION = UniqueAnnotations.create().annotationType(); |
| |
| private void reattachSet( |
| ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles, |
| Map<TypeLiteral<?>, DynamicSet<?>> sets, |
| @Nullable Injector src, |
| Plugin newPlugin) { |
| if (src == null || sets == null || sets.isEmpty()) { |
| return; |
| } |
| |
| for (Map.Entry<TypeLiteral<?>, DynamicSet<?>> e : sets.entrySet()) { |
| @SuppressWarnings("unchecked") |
| TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey(); |
| |
| @SuppressWarnings("unchecked") |
| DynamicSet<Object> set = (DynamicSet<Object>) e.getValue(); |
| |
| // Index all old handles that match this DynamicSet<T> keyed by |
| // annotations. Ignore the unique annotations, thereby favoring |
| // the @Named annotations or some other non-unique naming. |
| Map<Annotation, ReloadableRegistrationHandle<?>> am = new HashMap<>(); |
| List<ReloadableRegistrationHandle<?>> old = oldHandles.get(type); |
| Iterator<ReloadableRegistrationHandle<?>> oi = old.iterator(); |
| while (oi.hasNext()) { |
| ReloadableRegistrationHandle<?> h = oi.next(); |
| Annotation a = h.getKey().getAnnotation(); |
| if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) { |
| am.put(a, h); |
| oi.remove(); |
| } |
| } |
| |
| // Replace old handles with new bindings, favoring cases where there |
| // is an exact match on an @Named annotation. If there is no match |
| // pick any handle and replace it. We generally expect only one |
| // handle of each DynamicSet type when using unique annotations, but |
| // possibly multiple ones if @Named was used. Plugin authors that want |
| // atomic replacement across reloads should use @Named annotations with |
| // stable names that do not change across plugin versions to ensure the |
| // handles are swapped correctly. |
| oi = old.iterator(); |
| for (Binding<?> binding : bindings(src, type)) { |
| @SuppressWarnings("unchecked") |
| Binding<Object> b = (Binding<Object>) binding; |
| Key<Object> key = b.getKey(); |
| if (key.getAnnotation() == null) { |
| continue; |
| } |
| |
| @SuppressWarnings("unchecked") |
| ReloadableRegistrationHandle<Object> h1 = |
| (ReloadableRegistrationHandle<Object>) am.remove(key.getAnnotation()); |
| if (h1 != null) { |
| replace(newPlugin, h1, b); |
| } else if (oi.hasNext()) { |
| @SuppressWarnings("unchecked") |
| ReloadableRegistrationHandle<Object> h2 = |
| (ReloadableRegistrationHandle<Object>) oi.next(); |
| oi.remove(); |
| replace(newPlugin, h2, b); |
| } else { |
| newPlugin.add(set.add(newPlugin.getName(), b.getKey(), b.getProvider())); |
| } |
| } |
| } |
| } |
| |
| private void reattachItem( |
| ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles, |
| Map<TypeLiteral<?>, DynamicItem<?>> items, |
| @Nullable Injector src, |
| Plugin newPlugin) { |
| if (src == null || items == null || items.isEmpty()) { |
| return; |
| } |
| |
| for (Map.Entry<TypeLiteral<?>, DynamicItem<?>> e : items.entrySet()) { |
| @SuppressWarnings("unchecked") |
| TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey(); |
| |
| @SuppressWarnings("unchecked") |
| DynamicItem<Object> item = (DynamicItem<Object>) e.getValue(); |
| |
| Iterator<ReloadableRegistrationHandle<?>> oi = oldHandles.get(type).iterator(); |
| |
| for (Binding<?> binding : bindings(src, type)) { |
| @SuppressWarnings("unchecked") |
| Binding<Object> b = (Binding<Object>) binding; |
| if (oi.hasNext()) { |
| @SuppressWarnings("unchecked") |
| ReloadableRegistrationHandle<Object> h = (ReloadableRegistrationHandle<Object>) oi.next(); |
| oi.remove(); |
| replace(newPlugin, h, b); |
| } else { |
| newPlugin.add(item.set(b.getKey(), b.getProvider(), newPlugin.getName())); |
| } |
| } |
| } |
| } |
| |
| private static <T> void replace( |
| Plugin newPlugin, ReloadableRegistrationHandle<T> h, Binding<T> b) { |
| RegistrationHandle n = h.replace(b.getKey(), b.getProvider()); |
| if (n != null) { |
| newPlugin.add(n); |
| } |
| } |
| |
| static <T> List<T> listeners(Injector src, Class<T> type) { |
| List<Binding<T>> bindings = bindings(src, TypeLiteral.get(type)); |
| int cnt = bindings != null ? bindings.size() : 0; |
| List<T> found = Lists.newArrayListWithCapacity(cnt); |
| if (bindings != null) { |
| for (Binding<T> b : bindings) { |
| found.add(b.getProvider().get()); |
| } |
| } |
| return found; |
| } |
| |
| private static <T> List<Binding<T>> bindings(Injector src, TypeLiteral<T> type) { |
| return src.findBindingsByType(type); |
| } |
| |
| private Module copy(Injector src) { |
| Set<TypeLiteral<?>> dynamicTypes = new HashSet<>(); |
| Set<TypeLiteral<?>> dynamicItemTypes = new HashSet<>(); |
| for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) { |
| TypeLiteral<?> type = e.getKey().getTypeLiteral(); |
| if (type.getRawType() == DynamicItem.class) { |
| ParameterizedType t = (ParameterizedType) type.getType(); |
| dynamicItemTypes.add(TypeLiteral.get(t.getActualTypeArguments()[0])); |
| } else if (type.getRawType() == DynamicSet.class || type.getRawType() == DynamicMap.class) { |
| ParameterizedType t = (ParameterizedType) type.getType(); |
| dynamicTypes.add(TypeLiteral.get(t.getActualTypeArguments()[0])); |
| } |
| } |
| |
| final Map<Key<?>, Binding<?>> bindings = new LinkedHashMap<>(); |
| for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) { |
| if (dynamicTypes.contains(e.getKey().getTypeLiteral()) |
| && e.getKey().getAnnotation() != null) { |
| // A type used in DynamicSet or DynamicMap that has an annotation |
| // must be picked up by the set/map itself. A type used in either |
| // but without an annotation may be magic glue implementing F and |
| // using DynamicSet<F> or DynamicMap<F> internally. That should be |
| // exported to plugins. |
| continue; |
| } else if (dynamicItemTypes.contains(e.getKey().getTypeLiteral())) { |
| continue; |
| } else if (shouldCopy(e.getKey())) { |
| bindings.put(e.getKey(), e.getValue()); |
| } |
| } |
| bindings.remove(Key.get(Injector.class)); |
| bindings.remove(Key.get(java.util.logging.Logger.class)); |
| |
| @Nullable |
| final Binding<HttpServletRequest> requestBinding = |
| src.getExistingBinding(Key.get(HttpServletRequest.class)); |
| |
| @Nullable |
| final Binding<HttpServletResponse> responseBinding = |
| src.getExistingBinding(Key.get(HttpServletResponse.class)); |
| |
| return new AbstractModule() { |
| @SuppressWarnings("unchecked") |
| @Override |
| protected void configure() { |
| for (Map.Entry<Key<?>, Binding<?>> e : bindings.entrySet()) { |
| Key<Object> k = (Key<Object>) e.getKey(); |
| Binding<Object> b = (Binding<Object>) e.getValue(); |
| bind(k).toProvider(b.getProvider()); |
| } |
| |
| if (requestBinding != null) { |
| bind(HttpServletRequest.class) |
| .annotatedWith(RootRelative.class) |
| .toProvider(requestBinding.getProvider()); |
| } |
| if (responseBinding != null) { |
| bind(HttpServletResponse.class) |
| .annotatedWith(RootRelative.class) |
| .toProvider(responseBinding.getProvider()); |
| } |
| } |
| }; |
| } |
| |
| private boolean shouldCopy(Key<?> key) { |
| if (copyConfigKeys.contains(key)) { |
| return false; |
| } |
| Class<?> type = key.getTypeLiteral().getRawType(); |
| if (LifecycleListener.class.isAssignableFrom(type) |
| // This is needed for secondary index to work from plugin listeners |
| && !IndexCollection.class.isAssignableFrom(type)) { |
| return false; |
| } |
| if (StartPluginListener.class.isAssignableFrom(type)) { |
| return false; |
| } |
| if (StopPluginListener.class.isAssignableFrom(type)) { |
| return false; |
| } |
| if (MetricMaker.class.isAssignableFrom(type)) { |
| return false; |
| } |
| |
| if (type.getName().startsWith("com.google.inject.")) { |
| return false; |
| } |
| |
| if (is("org.apache.sshd.server.command.Command", type)) { |
| return false; |
| } |
| |
| if (is("javax.servlet.Filter", type)) { |
| return false; |
| } |
| if (is("javax.servlet.ServletContext", type)) { |
| return false; |
| } |
| if (is("javax.servlet.ServletRequest", type)) { |
| return false; |
| } |
| if (is("javax.servlet.ServletResponse", type)) { |
| return false; |
| } |
| if (is("javax.servlet.http.HttpServlet", type)) { |
| return false; |
| } |
| if (is("javax.servlet.http.HttpServletRequest", type)) { |
| return false; |
| } |
| if (is("javax.servlet.http.HttpServletResponse", type)) { |
| return false; |
| } |
| if (is("javax.servlet.http.HttpSession", type)) { |
| return false; |
| } |
| if (Map.class.isAssignableFrom(type) |
| && key.getAnnotationType() != null |
| && "com.google.inject.servlet.RequestParameters" |
| .equals(key.getAnnotationType().getName())) { |
| return false; |
| } |
| if (type.getName().startsWith("com.google.gerrit.httpd.GitOverHttpServlet$")) { |
| return false; |
| } |
| return true; |
| } |
| |
| static boolean is(String name, Class<?> type) { |
| while (type != null) { |
| if (name.equals(type.getName())) { |
| return true; |
| } |
| |
| Class<?>[] interfaces = type.getInterfaces(); |
| if (interfaces != null) { |
| for (Class<?> i : interfaces) { |
| if (is(name, i)) { |
| return true; |
| } |
| } |
| } |
| |
| type = type.getSuperclass(); |
| } |
| return false; |
| } |
| |
| @Nullable |
| public Injector getApiInjector() { |
| return apiInjector; |
| } |
| } |