/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
Java | 648 lines | 525 code | 80 blank | 43 comment | 125 complexity | c676a774f45fdff394da89905a1c22b9 MD5 | raw file
- // 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.common.base.Preconditions.checkNotNull;
- 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 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.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.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 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<?>, DynamicSet<?>> sysSets;
- private Map<TypeLiteral<?>, DynamicSet<?>> sshSets;
- private Map<TypeLiteral<?>, DynamicSet<?>> httpSets;
- private Map<TypeLiteral<?>, DynamicMap<?>> sysMaps;
- private Map<TypeLiteral<?>, DynamicMap<?>> sshMaps;
- private Map<TypeLiteral<?>, DynamicMap<?>> httpMaps;
- @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);
- }
- 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);
- checkNotNull(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;
- }
- 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);
- } finally {
- exit(oldContext);
- }
- for (StartPluginListener l : onStart) {
- l.onStartPlugin(plugin);
- }
- }
- 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, items, plugin.getName())) {
- plugin.add(h);
- }
- }
- private void attachSet(
- Map<TypeLiteral<?>, DynamicSet<?>> sets, @Nullable Injector src, Plugin plugin) {
- for (RegistrationHandle h : PrivateInternals_DynamicTypes.attachSets(src, 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 {
- reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin);
- reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin);
- reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin);
- reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin);
- reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin);
- reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin);
- reattachItem(old, sysItems, newPlugin.getSysInjector(), newPlugin);
- reattachItem(old, sshItems, newPlugin.getSshInjector(), newPlugin);
- reattachItem(old, httpItems, newPlugin.getHttpInjector(), 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(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", 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;
- }
- }