Factor out common modules from Reindex

As far as programs go, Reindex has about the most complicated
injection stack. This will be useful for future programs, for example
a Reindex analogue for notedb, so move them to the util package.

Change-Id: I7f980c0918b6893e4d421f0ba8a12e96bb01d20d
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 3cb2c22..cffc0c1 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -61,8 +61,11 @@
   name = 'util',
   srcs = glob([SRCS + 'util/*.java']),
   deps = [
+    '//gerrit-cache-h2:cache-h2',
+    '//gerrit-common:client',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//gerrit-util-cli:cli',
     '//lib:args4j',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index dae11b4..6ade6a9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -15,50 +15,26 @@
 package com.google.gerrit.pgm;
 
 import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
-import static com.google.inject.Scopes.SINGLETON;
 
-import com.google.common.cache.Cache;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.DisabledChangeHooks;
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.lucene.LuceneIndexModule;
+import com.google.gerrit.pgm.util.BatchGitModule;
+import com.google.gerrit.pgm.util.BatchProgramModule;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.pgm.util.ThreadLimiter;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.rules.PrologModule;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCacheImpl;
-import com.google.gerrit.server.account.AccountCacheImpl;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupCacheImpl;
-import com.google.gerrit.server.account.GroupIncludeCacheImpl;
-import com.google.gerrit.server.cache.CacheRemovalListener;
-import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.ChangeKindCacheImpl;
 import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.change.MergeabilityChecksExecutor;
 import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
 import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitModule;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.git.validators.CommitValidationListener;
-import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.group.GroupModule;
 import com.google.gerrit.server.index.ChangeBatchIndexer;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.ChangeSchemas;
@@ -67,26 +43,13 @@
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.NoteDbModule;
-import com.google.gerrit.server.patch.PatchListCacheImpl;
-import com.google.gerrit.server.project.AccessControlModule;
-import com.google.gerrit.server.project.CommentLinkInfo;
-import com.google.gerrit.server.project.CommentLinkProvider;
-import com.google.gerrit.server.project.ProjectCacheImpl;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.SectionSortCache;
-import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.solr.SolrIndexModule;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.AbstractModule;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
-import com.google.inject.Provider;
 import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
 import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
 import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.lib.Config;
@@ -95,7 +58,6 @@
 import org.eclipse.jgit.util.io.NullOutputStream;
 import org.kohsuke.args4j.Option;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -159,8 +121,7 @@
 
   private Injector createSysInjector() {
     List<Module> modules = Lists.newArrayList();
-    modules.add(PatchListCacheImpl.module());
-    AbstractModule changeIndexModule;
+    Module changeIndexModule;
     switch (IndexModule.getIndexType(dbInjector)) {
       case LUCENE:
         changeIndexModule = new LuceneIndexModule(version, threads, outputBase);
@@ -172,39 +133,10 @@
         throw new IllegalStateException("unsupported index.type");
     }
     modules.add(changeIndexModule);
-    modules.add(new ReviewDbModule());
-    modules.add(new FactoryModule() {
-      @SuppressWarnings("rawtypes")
+    modules.add(dbInjector.getInstance(BatchProgramModule.class));
+    modules.add(new AbstractModule() {
       @Override
       protected void configure() {
-        // Plugins are not loaded and we're just running through each change
-        // once, so don't worry about cache removal.
-        bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
-            .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
-        bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
-            .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
-        bind(new TypeLiteral<List<CommentLinkInfo>>() {})
-            .toProvider(CommentLinkProvider.class).in(SINGLETON);
-        bind(String.class).annotatedWith(CanonicalWebUrl.class)
-            .toProvider(CanonicalWebUrlProvider.class);
-        bind(IdentifiedUser.class)
-          .toProvider(Providers. <IdentifiedUser>of(null));
-        bind(CurrentUser.class).to(IdentifiedUser.class);
-        install(new AccessControlModule());
-        install(new DefaultCacheFactory.Module());
-        install(new GroupModule());
-        install(new PrologModule());
-        install(AccountByEmailCacheImpl.module());
-        install(AccountCacheImpl.module());
-        install(GroupCacheImpl.module());
-        install(GroupIncludeCacheImpl.module());
-        install(ProjectCacheImpl.module());
-        install(SectionSortCache.module());
-        install(ChangeKindCacheImpl.module());
-        factory(CapabilityControl.Factory.class);
-        factory(ChangeData.Factory.class);
-        factory(ProjectState.Factory.class);
-
         if (recheckMergeable) {
           install(new MergeabilityModule());
         } else {
@@ -226,61 +158,16 @@
     }
   }
 
-  private class ReviewDbModule extends LifecycleModule {
-    @Override
-    protected void configure() {
-      final SchemaFactory<ReviewDb> schema = dbInjector.getInstance(
-          Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
-      final List<ReviewDb> dbs = Collections.synchronizedList(
-          Lists.<ReviewDb> newArrayListWithCapacity(threads + 1));
-      final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
-
-      bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
-        @Override
-        public ReviewDb get() {
-          ReviewDb db = localDb.get();
-          if (db == null) {
-            try {
-              db = schema.open();
-              dbs.add(db);
-              localDb.set(db);
-            } catch (OrmException e) {
-              throw new ProvisionException("unable to open ReviewDb", e);
-            }
-          }
-          return db;
-        }
-      });
-      listener().toInstance(new LifecycleListener() {
-        @Override
-        public void start() {
-          // Do nothing.
-        }
-
-        @Override
-        public void stop() {
-          for (ReviewDb db : dbs) {
-            db.close();
-          }
-        }
-      });
-    }
-  }
-
   private static class MergeabilityModule extends FactoryModule {
     @Override
     public void configure() {
       factory(PatchSetInserter.Factory.class);
-      bind(ChangeHooks.class).to(DisabledChangeHooks.class);
       bind(ReplacePatchSetSender.Factory.class).toProvider(
           Providers.<ReplacePatchSetSender.Factory>of(null));
 
       factory(MergeUtil.Factory.class);
-      DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
-      DynamicSet.setOf(binder(), CommitValidationListener.class);
-      factory(CommitValidators.Factory.class);
-      install(new GitModule());
       install(new NoteDbModule());
+      install(new BatchGitModule());
     }
 
     @Provides
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
new file mode 100644
index 0000000..11ab073
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
@@ -0,0 +1,36 @@
+// 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.pgm.util;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.DisabledChangeHooks;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.GitModule;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidators;
+
+/** Module for batch programs that need git access. */
+public class BatchGitModule extends FactoryModule {
+  @Override
+  protected void configure() {
+    bind(ChangeHooks.class).to(DisabledChangeHooks.class);
+    DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
+    DynamicSet.setOf(binder(), CommitValidationListener.class);
+    factory(CommitValidators.Factory.class);
+    install(new GitModule());
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
new file mode 100644
index 0000000..4a17e68
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -0,0 +1,99 @@
+// 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.pgm.util;
+
+import static com.google.inject.Scopes.SINGLETON;
+import com.google.common.cache.Cache;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.rules.PrologModule;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountByEmailCacheImpl;
+import com.google.gerrit.server.account.AccountCacheImpl;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupCacheImpl;
+import com.google.gerrit.server.account.GroupIncludeCacheImpl;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.group.GroupModule;
+import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.server.project.AccessControlModule;
+import com.google.gerrit.server.project.CommentLinkInfo;
+import com.google.gerrit.server.project.CommentLinkProvider;
+import com.google.gerrit.server.project.ProjectCacheImpl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SectionSortCache;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.util.Providers;
+
+import java.util.List;
+
+/**
+ * Module for programs that perform batch operations on a site.
+ * <p>
+ * Any program that requires this module likely also requires using
+ * {@link ThreadLimiter} to limit the number of threads accessing the database
+ * concurrently.
+ */
+public class BatchProgramModule extends FactoryModule {
+  private final Module reviewDbModule;
+
+  @Inject
+  BatchProgramModule(PerThreadReviewDbModule reviewDbModule) {
+    this.reviewDbModule = reviewDbModule;
+  }
+
+  @SuppressWarnings("rawtypes")
+  @Override
+  protected void configure() {
+    install(reviewDbModule);
+    install(PatchListCacheImpl.module());
+    // Plugins are not loaded and we're just running through each change
+    // once, so don't worry about cache removal.
+    bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
+        .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+    bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
+        .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
+    bind(new TypeLiteral<List<CommentLinkInfo>>() {})
+        .toProvider(CommentLinkProvider.class).in(SINGLETON);
+    bind(String.class).annotatedWith(CanonicalWebUrl.class)
+        .toProvider(CanonicalWebUrlProvider.class);
+    bind(IdentifiedUser.class)
+      .toProvider(Providers.<IdentifiedUser> of(null));
+    bind(CurrentUser.class).to(IdentifiedUser.class);
+    install(new AccessControlModule());
+    install(new DefaultCacheFactory.Module());
+    install(new GroupModule());
+    install(new PrologModule());
+    install(AccountByEmailCacheImpl.module());
+    install(AccountCacheImpl.module());
+    install(GroupCacheImpl.module());
+    install(GroupIncludeCacheImpl.module());
+    install(ProjectCacheImpl.module());
+    install(SectionSortCache.module());
+    install(ChangeKindCacheImpl.module());
+    factory(CapabilityControl.Factory.class);
+    factory(ChangeData.Factory.class);
+    factory(ProjectState.Factory.class);
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
new file mode 100644
index 0000000..eb12937
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
@@ -0,0 +1,79 @@
+// 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.pgm.util;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Module to bind a single {@link ReviewDb} instance per thread.
+ * <p>
+ * New instances are opened on demand, but are closed only at shutdown.
+ */
+class PerThreadReviewDbModule extends LifecycleModule {
+  private final SchemaFactory<ReviewDb> schema;
+
+  @Inject
+  PerThreadReviewDbModule(SchemaFactory<ReviewDb> schema) {
+    this.schema = schema;
+  }
+
+  @Override
+  protected void configure() {
+    final List<ReviewDb> dbs = Collections.synchronizedList(
+        Lists.<ReviewDb> newArrayList());
+    final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
+
+    bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
+      @Override
+      public ReviewDb get() {
+        ReviewDb db = localDb.get();
+        if (db == null) {
+          try {
+            db = schema.open();
+            dbs.add(db);
+            localDb.set(db);
+          } catch (OrmException e) {
+            throw new ProvisionException("unable to open ReviewDb", e);
+          }
+        }
+        return db;
+      }
+    });
+    listener().toInstance(new LifecycleListener() {
+      @Override
+      public void start() {
+        // Do nothing.
+      }
+
+      @Override
+      public void stop() {
+        for (ReviewDb db : dbs) {
+          db.close();
+        }
+      }
+    });
+  }
+}