Add plugin JS resource paths to /config/server/info

This requires WebUiPlugins to be bound in the system module.  Existing
plugins that do this binding in the HttpModule are supported by
copying the DynamicSet from the system module.

The Maven plugin archetype is adapted to generate a correct example
for WebUiPlugin bindings.

Bug: issue 3915
Change-Id: Ic976ba1b4a3fc8d08975fd841e5013f7348b32cf
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index a656760..7f5860b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance.rest.config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
@@ -27,6 +28,8 @@
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.GetServerInfo.ServerInfo;
 
+import java.nio.file.Path;
+import java.nio.file.Files;
 import org.junit.Test;
 
 public class ServerInfoIT extends AbstractDaemonTest {
@@ -107,6 +110,9 @@
     // gitweb
     assertThat(i.gitweb).isNull();
 
+    // plugin
+    assertThat(i.plugin.jsResourcePaths).isEmpty();
+
     // sshd
     assertThat(i.sshd).isNotNull();
 
@@ -118,6 +124,21 @@
   }
 
   @Test
+  @GerritConfig(name = "plugins.allowRemoteAdmin", value = "true")
+  public void serverConfigWithPlugin() throws Exception {
+    Path plugins = tempSiteDir.newFolder("plugins").toPath();
+    Path jsplugin = plugins.resolve("js-plugin-1.js");
+    Files.write(jsplugin, "Gerrit.install(function(self){});\n".getBytes(UTF_8));
+    sshSession.exec("gerrit plugin reload");
+
+    RestResponse r = adminSession.get("/config/server/info/");
+    ServerInfo i = newGson().fromJson(r.getReader(), ServerInfo.class);
+
+    // plugin
+    assertThat(i.plugin.jsResourcePaths).hasSize(1);
+  }
+
+  @Test
   public void serverConfigWithDefaults() throws Exception {
     RestResponse r = adminSession.get("/config/server/info/");
     ServerInfo i = newGson().fromJson(r.getReader(), ServerInfo.class);
@@ -157,6 +178,9 @@
     // gitweb
     assertThat(i.gitweb).isNull();
 
+    // plugin
+    assertThat(i.plugin.jsResourcePaths).isEmpty();
+
     // sshd
     assertThat(i.sshd).isNotNull();
 
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ServerInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ServerInfo.java
index b0af73d..66a859a 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ServerInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ServerInfo.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -68,6 +69,8 @@
 
   public static class PluginConfigInfo extends JavaScriptObject {
     public final native boolean hasAvatars() /*-{ return this.has_avatars || false; }-*/;
+    public final native JsArrayString jsResourcePaths() /*-{
+        return this.js_resource_paths || []; }-*/;
 
     protected PluginConfigInfo() {
     }
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 422a83d..3e3b7c4 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
@@ -16,8 +16,6 @@
 
 import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.httpd.auth.become.BecomeAnyAccountModule;
 import com.google.gerrit.httpd.auth.container.HttpAuthModule;
 import com.google.gerrit.httpd.auth.container.HttpsClientSslCertModule;
@@ -71,8 +69,6 @@
       install(new GitwebModule());
     }
 
-    DynamicSet.setOf(binder(), WebUiPlugin.class);
-
     install(new AsyncReceiveCommits.Module());
 
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
index 0e81a0d..d1c617f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
@@ -14,18 +14,14 @@
 
 package com.google.gerrit.httpd.plugins;
 
-import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
 
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.gerrit.extensions.annotations.Export;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.webui.JavaScriptPlugin;
-import com.google.gerrit.extensions.webui.WebUiPlugin;
-import com.google.gerrit.server.plugins.HttpModuleGenerator;
 import com.google.gerrit.server.plugins.InvalidPluginException;
+import com.google.gerrit.server.plugins.ModuleGenerator;
 import com.google.inject.Module;
 import com.google.inject.Scopes;
 import com.google.inject.TypeLiteral;
@@ -37,10 +33,9 @@
 import javax.servlet.http.HttpServlet;
 
 class HttpAutoRegisterModuleGenerator extends ServletModule
-    implements HttpModuleGenerator {
+    implements ModuleGenerator {
   private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
   private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
-  private String javascript;
 
   @Override
   protected void configureServlets() {
@@ -58,10 +53,6 @@
       Annotation n = calculateBindAnnotation(impl);
       bind(type).annotatedWith(n).to(impl);
     }
-    if (javascript != null) {
-      DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(
-          new JavaScriptPlugin(javascript));
-    }
   }
 
   @Override
@@ -89,14 +80,6 @@
   }
 
   @Override
-  public void export(String javascript) {
-    checkState(this.javascript == null,
-        "Multiple JavaScript plugins detected: %s, %s", this.javascript,
-        javascript);
-    this.javascript = javascript;
-  }
-
-  @Override
   public void listen(TypeLiteral<?> tl, Class<?> clazz) {
     listeners.put(tl, clazz);
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
index 6b66bbd..3c72ec5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
@@ -18,7 +18,7 @@
 import com.google.gerrit.httpd.resources.ResourceKey;
 import com.google.gerrit.httpd.resources.ResourceWeigher;
 import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.plugins.HttpModuleGenerator;
+import com.google.gerrit.server.plugins.ModuleGenerator;
 import com.google.gerrit.server.plugins.ReloadPluginListener;
 import com.google.gerrit.server.plugins.StartPluginListener;
 import com.google.inject.internal.UniqueAnnotations;
@@ -51,7 +51,7 @@
       .annotatedWith(UniqueAnnotations.create())
       .to(LfsPluginServlet.class);
 
-    bind(HttpModuleGenerator.class)
+    bind(ModuleGenerator.class)
       .to(HttpAutoRegisterModuleGenerator.class);
 
     install(new CacheModule() {
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
index b224bf6..f33929d 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
@@ -10,7 +10,6 @@
     'Gerrit-ApiType: plugin',
     'Gerrit-ApiVersion: ${gerritApiVersion}',
     'Gerrit-Module: ${package}.Module',
-    'Gerrit-HttpModule: ${package}.HttpModule',
   ],
 )
 
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
deleted file mode 100644
index 4f043d0..0000000
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2015 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 ${package};
-
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.webui.GwtPlugin;
-import com.google.gerrit.extensions.webui.WebUiPlugin;
-import com.google.gerrit.httpd.plugins.HttpPluginModule;
-
-public class HttpModule extends HttpPluginModule {
-
-  @Override
-  protected void configureServlets() {
-    DynamicSet.bind(binder(), WebUiPlugin.class)
-        .toInstance(new GwtPlugin("hello_gwt_plugin"));
-  }
-}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
index c734bb7..73e5695 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
@@ -15,7 +15,9 @@
 package ${package};
 
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.GwtPlugin;
 import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.inject.AbstractModule;
 
 public class Module extends AbstractModule {
@@ -23,5 +25,7 @@
   @Override
   protected void configure() {
     DynamicSet.bind(binder(), TopMenu.class).to(HelloMenu.class);
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new GwtPlugin("hello_gwt_plugin"));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 5a1fdc6..49f69b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -45,6 +45,7 @@
 import com.google.gerrit.extensions.webui.PatchSetWebLink;
 import com.google.gerrit.extensions.webui.ProjectWebLink;
 import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.rules.PrologModule;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.AnonymousUser;
@@ -309,6 +310,7 @@
     DynamicSet.setOf(binder(), BranchWebLink.class);
     DynamicMap.mapOf(binder(), OAuthLoginProvider.class);
     DynamicSet.setOf(binder(), AccountExternalIdCreator.class);
+    DynamicSet.setOf(binder(), WebUiPlugin.class);
 
     factory(UploadValidators.Factory.class);
     DynamicSet.setOf(binder(), UploadValidationListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index e5ac370..d8a3984 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -26,7 +26,9 @@
 import com.google.gerrit.extensions.config.DownloadScheme;
 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.restapi.RestReadView;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.server.EnableSignedPush;
@@ -58,6 +60,7 @@
   private final DynamicMap<DownloadScheme> downloadSchemes;
   private final DynamicMap<DownloadCommand> downloadCommands;
   private final DynamicMap<CloneCommand> cloneCommands;
+  private final DynamicSet<WebUiPlugin> plugins;
   private final GetArchive.AllowedFormats archiveFormats;
   private final AllProjectsName allProjectsName;
   private final AllUsersName allUsersName;
@@ -75,6 +78,7 @@
       DynamicMap<DownloadScheme> downloadSchemes,
       DynamicMap<DownloadCommand> downloadCommands,
       DynamicMap<CloneCommand> cloneCommands,
+      DynamicSet<WebUiPlugin> webUiPlugins,
       GetArchive.AllowedFormats archiveFormats,
       AllProjectsName allProjectsName,
       AllUsersName allUsersName,
@@ -89,6 +93,7 @@
     this.downloadSchemes = downloadSchemes;
     this.downloadCommands = downloadCommands;
     this.cloneCommands = cloneCommands;
+    this.plugins = webUiPlugins;
     this.archiveFormats = archiveFormats;
     this.allProjectsName = allProjectsName;
     this.allUsersName = allUsersName;
@@ -270,6 +275,12 @@
   private PluginConfigInfo getPluginInfo() {
     PluginConfigInfo info = new PluginConfigInfo();
     info.hasAvatars = toBoolean(avatar.get() != null);
+    info.jsResourcePaths = new ArrayList<>();
+    for (WebUiPlugin u : plugins) {
+      info.jsResourcePaths.add(String.format("plugins/%s/%s",
+          u.getPluginName(),
+          u.getJavaScriptResourcePath()));
+    }
     return info;
   }
 
@@ -385,6 +396,7 @@
 
   public static class PluginConfigInfo {
     public Boolean hasAvatars;
+    public List<String> jsResourcePaths;
   }
 
   public static class SshdInfo {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index 0eaddb3..f719e8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.plugins;
 
+import static com.google.gerrit.extensions.webui.JavaScriptPlugin.STATIC_INIT_JS;
 import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
 import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is;
 
@@ -23,7 +24,9 @@
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
 import com.google.inject.AbstractModule;
 import com.google.inject.Module;
@@ -48,10 +51,11 @@
   private final PluginContentScanner scanner;
   private final ClassLoader classLoader;
   private final ModuleGenerator sshGen;
-  private final HttpModuleGenerator httpGen;
+  private final ModuleGenerator httpGen;
 
   private Set<Class<?>> sysSingletons;
   private Multimap<TypeLiteral<?>, Class<?>> sysListen;
+  private String initJs;
 
   Module sysModule;
   Module sshModule;
@@ -70,19 +74,20 @@
         : new ModuleGenerator.NOP();
     this.httpGen = env.hasHttpModule()
         ? env.newHttpModuleGenerator()
-        : new HttpModuleGenerator.NOP();
+        : new ModuleGenerator.NOP();
   }
 
   AutoRegisterModules discover() throws InvalidPluginException {
     sysSingletons = Sets.newHashSet();
     sysListen = LinkedListMultimap.create();
+    initJs = null;
 
     sshGen.setPluginName(pluginName);
     httpGen.setPluginName(pluginName);
 
     scan();
 
-    if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) {
+    if (!sysSingletons.isEmpty() || !sysListen.isEmpty() || initJs != null) {
       sysModule = makeSystemModule();
     }
     sshModule = sshGen.create();
@@ -107,6 +112,10 @@
           Annotation n = calculateBindAnnotation(impl);
           bind(type).annotatedWith(n).to(impl);
         }
+        if (initJs != null) {
+          DynamicSet.bind(binder(), WebUiPlugin.class)
+              .toInstance(new JavaScriptPlugin(initJs));
+        }
       }
     };
   }
@@ -120,18 +129,20 @@
     for (ExtensionMetaData listener : extensions.get(Listen.class)) {
       listen(listener);
     }
-    exportInitJs();
+    if (env.hasHttpModule()) {
+      exportInitJs();
+    }
   }
 
   private void exportInitJs() {
     try {
-      if (scanner.getEntry(JavaScriptPlugin.STATIC_INIT_JS).isPresent()) {
-        httpGen.export(JavaScriptPlugin.INIT_JS);
+      if (scanner.getEntry(STATIC_INIT_JS).isPresent()) {
+        initJs = STATIC_INIT_JS;
       }
     } catch (IOException e) {
       log.warn(String.format("Cannot access %s from plugin %s: "
           + "JavaScript auto-discovered plugin will not be registered",
-          JavaScriptPlugin.STATIC_INIT_JS, pluginName), e);
+          STATIC_INIT_JS, pluginName), e);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
deleted file mode 100644
index 73effa1..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2014 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;
-
-
-public interface HttpModuleGenerator extends ModuleGenerator {
-  void export(String javascript);
-
-  class NOP extends ModuleGenerator.NOP
-      implements HttpModuleGenerator {
-    @Override
-    public void export(String javascript) {
-      // do nothing
-    }
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
index ea81f17..544cc5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
@@ -30,7 +30,7 @@
 import java.nio.file.Path;
 
 class JsPlugin extends Plugin {
-  private Injector httpInjector;
+  private Injector sysInjector;
 
   JsPlugin(String name, Path srcFile, PluginUser pluginUser,
       FileSnapshot snapshot) {
@@ -52,7 +52,7 @@
   public void start(PluginGuiceEnvironment env) throws Exception {
     manager = new LifecycleManager();
     String fileName = getSrcFile().getFileName().toString();
-    httpInjector =
+    sysInjector =
         Guice.createInjector(new StandaloneJsPluginModule(getName(), fileName));
     manager.start();
   }
@@ -61,13 +61,13 @@
   protected void stop(PluginGuiceEnvironment env) {
     if (manager != null) {
       manager.stop();
-      httpInjector = null;
+      sysInjector = null;
     }
   }
 
   @Override
   public Injector getSysInjector() {
-    return null;
+    return sysInjector;
   }
 
   @Override
@@ -79,7 +79,7 @@
   @Override
   @Nullable
   public Injector getHttpInjector() {
-    return httpInjector;
+    return null;
   }
 
   @Override
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 6cc8c00..bc471e4 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
@@ -14,6 +14,7 @@
 
 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;
@@ -34,6 +35,7 @@
 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.metrics.MetricMaker;
 import com.google.gerrit.server.util.PluginRequestContext;
 import com.google.gerrit.server.util.RequestContext;
@@ -52,6 +54,8 @@
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.ParameterizedType;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -85,7 +89,7 @@
   private Module httpModule;
 
   private Provider<ModuleGenerator> sshGen;
-  private Provider<HttpModuleGenerator> httpGen;
+  private Provider<ModuleGenerator> httpGen;
 
   private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
   private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
@@ -197,15 +201,27 @@
 
   public void setHttpInjector(Injector injector) {
     httpModule = copy(injector);
-    httpGen = injector.getProvider(HttpModuleGenerator.class);
+    httpGen = injector.getProvider(ModuleGenerator.class);
     httpItems = dynamicItemsOf(injector);
-    httpSets = dynamicSetsOf(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;
   }
@@ -214,7 +230,7 @@
     return httpModule;
   }
 
-  HttpModuleGenerator newHttpModuleGenerator() {
+  ModuleGenerator newHttpModuleGenerator() {
     return httpGen.get();
   }