Introduce a pluggable indexing libModule type

The pluggability of secondary indexes implementation
needs a specific type of libModule because of the differences
on how the module instance is created.

In addition of the pure Guice injection, the instantiation
needs to rely on the discovery of a static method, hence the
need of a specific module type.

This has been tested E2E with the ElasticSearch libModule
PoC at [1] and it was possible to:
- create a new Gerrit site
- create new indexes definition
- reindex
- start and use Gerrit with ElasticSearch

The config-gerrit.txt documentation is updated with the
reference and example on how to plug ElasticSearch as
an index module.

[1] https://github.com/lucamilanesio/index-elasticsearch

Change-Id: Iec2d11dcc867b0ee74a77c7ac0c2eab97870fda1
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 9eded88..8ad5b9d 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2317,6 +2317,25 @@
 +
 By default unset.
 
+[[gerrit.installIndexModule]]gerrit.installIndexModule::
++
+Class name of the Guice modules to load as alternate implementation
+for the Gerrit indexes backend.
+Classes are resolved using the primary Gerrit class loader, hence the
+class needs to be either declared in Gerrit or an additional JAR
+located under the `/lib` directory.
++
+NOTE: The `gerrit.installIndexModule` has precedence over the
+`index.type`.
++
+By default unset.
++
+Example:
+----
+[gerrit]
+  installIndexModule = com.google.gerrit.elasticsearch.ElasticIndexModule
+----
++
 [[gerrit.installModule]]gerrit.installModule::
 +
 Repeatable list of class name of additional Guice modules to load at
@@ -3136,16 +3155,13 @@
 
 [[index.type]]index.type::
 +
-Type of secondary indexing employed by Gerrit.  The supported
-values are:
+*(DEPRECATED)* The only supported value is `LUCENE`, which is the default,
+that means a link:http://lucene.apache.org/[Lucene]
+index is used.
 +
-* `LUCENE`
+For using other indexing backends (e.g. ElasticSearch), refer to
+`gerrit.installIndexModule` setting.
 +
-A link:http://lucene.apache.org/[Lucene] index is used.
-+
-+
-By default, `LUCENE`.
-
 [[index.threads]]index.threads::
 +
 Number of threads to use for indexing in normal interactive operations. Setting
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 1668018..d77daa1 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -524,6 +524,7 @@
 
     List<Module> libModules =
         LibModuleLoader.loadModules(cfgInjector, LibModuleType.SYS_MODULE_TYPE);
+    libModules.addAll(LibModuleLoader.loadModules(cfgInjector, LibModuleType.INDEX_MODULE_TYPE));
     libModules.addAll(testSysModules);
 
     AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index b4b5ffb..69c7d6f 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -30,6 +30,8 @@
 import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.pgm.util.BatchProgramModule;
 import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.server.LibModuleLoader;
+import com.google.gerrit.server.ModuleOverloader;
 import com.google.gerrit.server.cache.CacheDisplay;
 import com.google.gerrit.server.cache.CacheInfo;
 import com.google.gerrit.server.change.ChangeResource;
@@ -207,7 +209,9 @@
           }
         });
 
-    return dbInjector.createChildInjector(modules);
+    return dbInjector.createChildInjector(
+        ModuleOverloader.override(
+            modules, LibModuleLoader.loadReindexModules(cfgInjector, versions, threads, replica)));
   }
 
   private void overrideConfig() {
diff --git a/java/com/google/gerrit/server/LibModuleLoader.java b/java/com/google/gerrit/server/LibModuleLoader.java
index 0a6fb9f..36765e9 100644
--- a/java/com/google/gerrit/server/LibModuleLoader.java
+++ b/java/com/google/gerrit/server/LibModuleLoader.java
@@ -22,8 +22,10 @@
 import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.ProvisionException;
+import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import org.eclipse.jgit.lib.Config;
 
 /** Loads configured Guice modules from {@code gerrit.installModule}. */
@@ -37,6 +39,33 @@
         .collect(toList());
   }
 
+  public static List<Module> loadReindexModules(
+      Injector parent, Map<String, Integer> versions, int threads, boolean replica) {
+    Config cfg = getConfig(parent);
+    return Arrays.stream(
+            cfg.getStringList(
+                "gerrit", null, "install" + LibModuleType.INDEX_MODULE_TYPE.getConfigKey()))
+        .map(m -> createReindexModule(m, versions, threads, replica))
+        .collect(toList());
+  }
+
+  private static Module createReindexModule(
+      String className, Map<String, Integer> versions, int threads, boolean replica) {
+    Class<Module> clazz = loadModule(className);
+    try {
+
+      Method m =
+          clazz.getMethod("singleVersionWithExplicitVersions", Map.class, int.class, boolean.class);
+
+      Module module = (Module) m.invoke(null, versions, threads, replica);
+      logger.atInfo().log("Installed module %s", className);
+      return module;
+    } catch (Exception e) {
+      logger.atSevere().withCause(e).log("Unable to load libModule for %s", className);
+      throw new IllegalStateException(e);
+    }
+  }
+
   private static Config getConfig(Injector i) {
     return i.getInstance(Key.get(Config.class, GerritServerConfig.class));
   }
diff --git a/java/com/google/gerrit/server/LibModuleType.java b/java/com/google/gerrit/server/LibModuleType.java
index 4c70ce7..57206aa 100644
--- a/java/com/google/gerrit/server/LibModuleType.java
+++ b/java/com/google/gerrit/server/LibModuleType.java
@@ -24,7 +24,10 @@
   SYS_BATCH_MODULE_TYPE("BatchModule"),
 
   /** Module for the dbInjector. */
-  DB_MODULE_TYPE("DbModule");
+  DB_MODULE_TYPE("DbModule"),
+
+  /** Module for the implementation of the indexing backend. */
+  INDEX_MODULE_TYPE("IndexModule");
 
   private final String configKey;