Auto register static/init.js as JavaScript plugin

When plugin does not expose Guice Modules explicitly,
auto discover and register static/init.js as WebUi extension
if found by the plugin content scanner.
This simplify JavaScript development from now on
no Java (or other scripting) code/classes are
required to extend Gerrit WebUi.

(based on the idea and initial patch by Dariusz: Ia5b3cb63f62)

Change-Id: I8c793764ac1876dc62d740d28c0d4cf6b9409b10
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
index 89a4f33..4619a06 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
@@ -16,6 +16,9 @@
 
 /** Configures a web UI plugin written using JavaScript. */
 public class JavaScriptPlugin extends WebUiPlugin {
+  public static final String INIT_JS = "init.js";
+  public static final String STATIC_INIT_JS = "static/" + INIT_JS;
+
   private final String fileName;
 
   /**
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 d1c617f..0e81a0d 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,14 +14,18 @@
 
 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;
@@ -33,9 +37,10 @@
 import javax.servlet.http.HttpServlet;
 
 class HttpAutoRegisterModuleGenerator extends ServletModule
-    implements ModuleGenerator {
+    implements HttpModuleGenerator {
   private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
   private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
+  private String javascript;
 
   @Override
   protected void configureServlets() {
@@ -53,6 +58,10 @@
       Annotation n = calculateBindAnnotation(impl);
       bind(type).annotatedWith(n).to(impl);
     }
+    if (javascript != null) {
+      DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(
+          new JavaScriptPlugin(javascript));
+    }
   }
 
   @Override
@@ -80,6 +89,14 @@
   }
 
   @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 5dc7e2e..ba5df51 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
@@ -15,6 +15,7 @@
 package com.google.gerrit.httpd.plugins;
 
 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;
@@ -37,7 +38,7 @@
       .annotatedWith(UniqueAnnotations.create())
       .to(HttpPluginServlet.class);
 
-    bind(ModuleGenerator.class)
+    bind(HttpModuleGenerator.class)
       .to(HttpAutoRegisterModuleGenerator.class);
 
     install(new CacheModule() {
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 6f1204b..759700e 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
@@ -23,12 +23,17 @@
 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.webui.JavaScriptPlugin;
 import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
 import com.google.inject.AbstractModule;
 import com.google.inject.Module;
 import com.google.inject.Scopes;
 import com.google.inject.TypeLiteral;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.ParameterizedType;
 import java.util.Arrays;
@@ -36,12 +41,14 @@
 import java.util.Set;
 
 class AutoRegisterModules {
+  private static final Logger log = LoggerFactory.getLogger(AutoRegisterModules.class);
+
   private final String pluginName;
   private final PluginGuiceEnvironment env;
   private final PluginContentScanner scanner;
   private final ClassLoader classLoader;
   private final ModuleGenerator sshGen;
-  private final ModuleGenerator httpGen;
+  private final HttpModuleGenerator httpGen;
 
   private Set<Class<?>> sysSingletons;
   private Multimap<TypeLiteral<?>, Class<?>> sysListen;
@@ -117,6 +124,19 @@
     for (ExtensionMetaData listener : extensions.get(Listen.class)) {
       listen(listener);
     }
+    exportInitJs();
+  }
+
+  private void exportInitJs() {
+    try {
+      if (scanner.getEntry(JavaScriptPlugin.STATIC_INIT_JS).isPresent()) {
+        httpGen.export(JavaScriptPlugin.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);
+    }
   }
 
   private void export(ExtensionMetaData def) throws InvalidPluginException {
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
new file mode 100644
index 0000000..44b2434
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
@@ -0,0 +1,19 @@
+// 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);
+}
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 5b63a215..fbc95c9 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
@@ -83,7 +83,7 @@
   private Module httpModule;
 
   private Provider<ModuleGenerator> sshGen;
-  private Provider<ModuleGenerator> httpGen;
+  private Provider<HttpModuleGenerator> httpGen;
 
   private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
   private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
@@ -187,7 +187,7 @@
 
   public void setHttpInjector(Injector injector) {
     httpModule = copy(injector);
-    httpGen = injector.getProvider(ModuleGenerator.class);
+    httpGen = injector.getProvider(HttpModuleGenerator.class);
     httpItems = dynamicItemsOf(injector);
     httpSets = dynamicSetsOf(injector);
     httpMaps = dynamicMapsOf(injector);
@@ -204,7 +204,7 @@
     return httpModule;
   }
 
-  ModuleGenerator newHttpModuleGenerator() {
+  HttpModuleGenerator newHttpModuleGenerator() {
     return httpGen.get();
   }