Register DynamicSet, DynamicMap from parent injectors

If the HTTP or SSH injector registers implementations of a type
that is tracked by a DynamicSet or DynamicMap from the system
injector, ensure those are registered when these injectors are
started by the LifecycleManager.

Change-Id: I6d4134f6689c872b162b04f8cf5935d50bd0fd9d
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
new file mode 100644
index 0000000..de1cd68
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
@@ -0,0 +1,171 @@
+// 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.extensions.registration;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.inject.Binding;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** <b>DO NOT USE</b> */
+public class PrivateInternals_DynamicTypes {
+  public static Map<TypeLiteral<?>, DynamicSet<?>> dynamicSetsOf(Injector src) {
+    Map<TypeLiteral<?>, DynamicSet<?>> m = newHashMap();
+    for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
+      TypeLiteral<?> type = e.getKey().getTypeLiteral();
+      if (type.getRawType() == DynamicSet.class) {
+        ParameterizedType p = (ParameterizedType) type.getType();
+        m.put(TypeLiteral.get(p.getActualTypeArguments()[0]),
+            (DynamicSet<?>) e.getValue().getProvider().get());
+      }
+    }
+    if (m.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    return Collections.unmodifiableMap(m);
+  }
+
+  public static Map<TypeLiteral<?>, DynamicMap<?>> dynamicMapsOf(Injector src) {
+    Map<TypeLiteral<?>, DynamicMap<?>> m = newHashMap();
+    for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
+      TypeLiteral<?> type = e.getKey().getTypeLiteral();
+      if (type.getRawType() == DynamicMap.class) {
+        ParameterizedType p = (ParameterizedType) type.getType();
+        m.put(TypeLiteral.get(p.getActualTypeArguments()[0]),
+            (DynamicMap<?>) e.getValue().getProvider().get());
+      }
+    }
+    if (m.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    return Collections.unmodifiableMap(m);
+  }
+
+  public static List<RegistrationHandle> attachSets(
+      Injector src,
+      Map<TypeLiteral<?>, DynamicSet<?>> sets) {
+    if (src == null || sets == null || sets.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    List<RegistrationHandle> handles = new ArrayList<RegistrationHandle>(4);
+    try {
+      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();
+
+        for (Binding<Object> b : bindings(src, type)) {
+          handles.add(set.add(b.getKey(), b.getProvider()));
+        }
+      }
+    } catch (RuntimeException e) {
+      remove(handles);
+      throw e;
+    } catch (Error e) {
+      remove(handles);
+      throw e;
+    }
+    return handles;
+  }
+
+  public static List<RegistrationHandle> attachMaps(
+      Injector src,
+      String groupName,
+      Map<TypeLiteral<?>, DynamicMap<?>> maps) {
+    if (src == null || maps == null || maps.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    List<RegistrationHandle> handles = new ArrayList<RegistrationHandle>(4);
+    try {
+      for (Map.Entry<TypeLiteral<?>, DynamicMap<?>> e : maps.entrySet()) {
+        @SuppressWarnings("unchecked")
+        TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
+
+        @SuppressWarnings("unchecked")
+        PrivateInternals_DynamicMapImpl<Object> set =
+            (PrivateInternals_DynamicMapImpl<Object>) e.getValue();
+
+        for (Binding<Object> b : bindings(src, type)) {
+          handles.add(set.put(groupName, b.getKey(), b.getProvider()));
+        }
+      }
+    } catch (RuntimeException e) {
+      remove(handles);
+      throw e;
+    } catch (Error e) {
+      remove(handles);
+      throw e;
+    }
+    return handles;
+  }
+
+  public static LifecycleListener registerInParentInjectors() {
+    return new LifecycleListener() {
+      private List<RegistrationHandle> handles;
+
+      @Inject
+      private Injector self;
+
+      @Override
+      public void start() {
+        handles = new ArrayList<RegistrationHandle>(4);
+        Injector parent = self.getParent();
+        while (parent != null) {
+          handles.addAll(attachSets(self, dynamicSetsOf(parent)));
+          handles.addAll(attachMaps(self, "gerrit", dynamicMapsOf(parent)));
+          parent = parent.getParent();
+        }
+        if (handles.isEmpty()) {
+          handles = null;
+        }
+      }
+
+      @Override
+      public void stop() {
+        remove(handles);
+        handles = null;
+      }
+    };
+  }
+
+  private static void remove(List<RegistrationHandle> handles) {
+    if (handles != null) {
+      for (RegistrationHandle handle : handles) {
+        handle.remove();
+      }
+    }
+  }
+
+  private static <K,V> Map<K, V> newHashMap() {
+    return new HashMap<K,V>();
+  }
+
+  private static <T> List<Binding<T>> bindings(Injector src, TypeLiteral<T> type) {
+    return src.findBindingsByType(type);
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 0f223c9..d198b93 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.httpd;
 
 import static com.google.inject.Scopes.SINGLETON;
+import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
 
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
@@ -23,6 +24,7 @@
 import com.google.gerrit.httpd.auth.ldap.LdapAuthModule;
 import com.google.gerrit.httpd.gitweb.GitWebModule;
 import com.google.gerrit.httpd.rpc.UiRpcModule;
+import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.RemotePeer;
@@ -147,5 +149,12 @@
 
     bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class);
     bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class);
+
+    install(new LifecycleModule() {
+      @Override
+      protected void configure() {
+        listener().toInstance(registerInParentInjectors());
+      }
+    });
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index eba9ff9..e541b10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 package com.google.gerrit.server.plugins;
+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;
@@ -23,6 +25,7 @@
 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;
@@ -188,40 +191,18 @@
   private void attachSet(Map<TypeLiteral<?>, DynamicSet<?>> sets,
       @Nullable Injector src,
       Plugin plugin) {
-    if (src != null && sets != null && !sets.isEmpty()) {
-      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();
-
-        for (Binding<Object> b : bindings(src, type)) {
-          plugin.add(set.add(b.getKey(), b.getProvider()));
-        }
-      }
+    for (RegistrationHandle h : PrivateInternals_DynamicTypes
+        .attachSets(src, sets)) {
+      plugin.add(h);
     }
   }
 
   private void attachMap(Map<TypeLiteral<?>, DynamicMap<?>> maps,
       @Nullable Injector src,
       Plugin plugin) {
-    if (src != null && maps != null && !maps.isEmpty()) {
-      for (Map.Entry<TypeLiteral<?>, DynamicMap<?>> e : maps.entrySet()) {
-        @SuppressWarnings("unchecked")
-        TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
-
-        @SuppressWarnings("unchecked")
-        PrivateInternals_DynamicMapImpl<Object> set =
-            (PrivateInternals_DynamicMapImpl<Object>) e.getValue();
-
-        for (Binding<Object> b : bindings(src, type)) {
-          plugin.add(set.put(
-              plugin.getName(),
-              b.getKey(),
-              b.getProvider()));
-        }
-      }
+    for (RegistrationHandle h : PrivateInternals_DynamicTypes
+        .attachMaps(src, plugin.getName(), maps)) {
+      plugin.add(h);
     }
   }
 
@@ -385,32 +366,6 @@
     return src.findBindingsByType(type);
   }
 
-  private static Map<TypeLiteral<?>, DynamicSet<?>> dynamicSetsOf(Injector src) {
-    Map<TypeLiteral<?>, DynamicSet<?>> m = Maps.newHashMap();
-    for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
-      TypeLiteral<?> type = e.getKey().getTypeLiteral();
-      if (type.getRawType() == DynamicSet.class) {
-        ParameterizedType p = (ParameterizedType) type.getType();
-        m.put(TypeLiteral.get(p.getActualTypeArguments()[0]),
-            (DynamicSet<?>) e.getValue().getProvider().get());
-      }
-    }
-    return m;
-  }
-
-  private static Map<TypeLiteral<?>, DynamicMap<?>> dynamicMapsOf(Injector src) {
-    Map<TypeLiteral<?>, DynamicMap<?>> m = Maps.newHashMap();
-    for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
-      TypeLiteral<?> type = e.getKey().getTypeLiteral();
-      if (type.getRawType() == DynamicMap.class) {
-        ParameterizedType p = (ParameterizedType) type.getType();
-        m.put(TypeLiteral.get(p.getActualTypeArguments()[0]),
-            (DynamicMap<?>) e.getValue().getProvider().get());
-      }
-    }
-    return m;
-  }
-
   private static Module copy(Injector src) {
     Set<TypeLiteral<?>> dynamicTypes = Sets.newHashSet();
     for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index aeae4f8..dcb7c07 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.sshd;
 
 import static com.google.inject.Scopes.SINGLETON;
+import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
 
 import com.google.common.collect.Maps;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -121,6 +122,7 @@
           .annotatedWith(UniqueAnnotations.create())
           .to(SshPluginStarterCallback.class);
 
+        listener().toInstance(registerInParentInjectors());
         listener().to(SshLog.class);
         listener().to(SshDaemon.class);
       }