Merge branch 'stable-2.16'

* stable-2.16:
  Design documentation
  Remove empty unused file
  Healthcheck: remove from the configuration
  AutoReindex: remove from the configuration
  Fix eclipse warning about missing serialVersionUID
  Remove obsolete configuration parameters
  Delete rest forwarder and autoreindexer modules
  ForwardedEventHandler: open request context when dispatching
  Ensure proper context during deserialization
  CacheEvictionEventRouter: Fix unneeded cast warning in eclipse
  Allow configuration of kafka specific props
  Flogger: rely on native cause formatting
  Fix serialization of comment events

Change-Id: I7008a75bd1c98426f3c4d144e401482588ca0b9e
diff --git a/BUILD b/BUILD
index bab2335..08d9d5c 100644
--- a/BUILD
+++ b/BUILD
@@ -12,7 +12,6 @@
     manifest_entries = [
         "Gerrit-PluginName: multi-site",
         "Gerrit-Module: com.googlesource.gerrit.plugins.multisite.Module",
-        "Gerrit-HttpModule: com.googlesource.gerrit.plugins.multisite.HttpModule",
         "Implementation-Title: multi-site plugin",
         "Implementation-URL: https://review.gerrithub.io/admin/repos/GerritForge/plugins_multi-site",
     ],
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
index 483c3b2..f6c617d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -15,12 +15,9 @@
 package com.googlesource.gerrit.plugins.multisite;
 
 import com.google.common.base.CaseFormat;
-import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -30,12 +27,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Properties;
-import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 import org.apache.kafka.common.serialization.StringSerializer;
 import org.eclipse.jgit.lib.Config;
 import org.slf4j.Logger;
@@ -46,14 +39,13 @@
   private static final Logger log = LoggerFactory.getLogger(Configuration.class);
 
   static final String INSTANCE_ID_FILE = "instanceId.data";
-  // common parameter to peerInfo section
-  static final String PEER_INFO_SECTION = "peerInfo";
 
   // common parameters to cache and index sections
   static final String THREAD_POOL_SIZE_KEY = "threadPoolSize";
 
   static final int DEFAULT_INDEX_MAX_TRIES = 2;
   static final int DEFAULT_INDEX_RETRY_INTERVAL = 30000;
+  private static final int DEFAULT_POLLING_INTERVAL_MS = 1000;
   static final int DEFAULT_THREAD_POOL_SIZE = 4;
   static final String NUM_STRIPED_LOCKS = "numStripedLocks";
   static final int DEFAULT_NUM_STRIPED_LOCKS = 10;
@@ -61,18 +53,14 @@
   static final String DEFAULT_KAFKA_BOOTSTRAP_SERVERS = "localhost:9092";
   static final boolean DEFAULT_ENABLE_PROCESSING = true;
   static final String KAFKA_SECTION = "kafka";
+  public static final String KAFKA_PROPERTY_PREFIX = "KafkaProp-";
 
-  private final AutoReindex autoReindex;
-  private final PeerInfo peerInfo;
   private final KafkaPublisher publisher;
-  private final Http http;
   private final Cache cache;
   private final Event event;
   private final Index index;
   private final KafkaSubscriber subscriber;
   private final Kafka kafka;
-  private PeerInfoStatic peerInfoStatic;
-  private HealthCheck healthCheck;
 
   public enum PeerInfoStrategy {
     STATIC
@@ -81,23 +69,12 @@
   @Inject
   Configuration(PluginConfigFactory pluginConfigFactory, @PluginName String pluginName) {
     Config cfg = pluginConfigFactory.getGlobalPluginConfig(pluginName);
-    autoReindex = new AutoReindex(cfg);
-    peerInfo = new PeerInfo(cfg);
-    switch (peerInfo.strategy()) {
-      case STATIC:
-        peerInfoStatic = new PeerInfoStatic(cfg);
-        break;
-      default:
-        throw new IllegalArgumentException("Not supported strategy: " + peerInfo.strategy);
-    }
     kafka = new Kafka(cfg);
     publisher = new KafkaPublisher(cfg);
-    http = new Http(cfg);
+    subscriber = new KafkaSubscriber(cfg);
     cache = new Cache(cfg);
     event = new Event(cfg);
     index = new Index(cfg);
-    healthCheck = new HealthCheck(cfg);
-    subscriber = new KafkaSubscriber(cfg);
   }
 
   public Kafka getKafka() {
@@ -108,22 +85,6 @@
     return publisher;
   }
 
-  public AutoReindex autoReindex() {
-    return autoReindex;
-  }
-
-  public PeerInfo peerInfo() {
-    return peerInfo;
-  }
-
-  public PeerInfoStatic peerInfoStatic() {
-    return peerInfoStatic;
-  }
-
-  public Http http() {
-    return http;
-  }
-
   public Cache cache() {
     return cache;
   }
@@ -136,10 +97,6 @@
     return index;
   }
 
-  public HealthCheck healthCheck() {
-    return healthCheck;
-  }
-
   public KafkaSubscriber kafkaSubscriber() {
     return subscriber;
   }
@@ -163,76 +120,6 @@
     return defaultValue;
   }
 
-  public static class AutoReindex {
-    static final String AUTO_REINDEX_SECTION = "autoReindex";
-    static final String DELAY = "delay";
-    static final String POLL_INTERVAL = "pollInterval";
-
-    private final boolean enabled;
-    private final long delaySec;
-    private final long pollSec;
-
-    public AutoReindex(Config cfg) {
-      this.enabled = cfg.getBoolean(AUTO_REINDEX_SECTION, ENABLE_KEY, false);
-      this.delaySec =
-          ConfigUtil.getTimeUnit(cfg, AUTO_REINDEX_SECTION, null, DELAY, 10L, TimeUnit.SECONDS);
-      this.pollSec =
-          ConfigUtil.getTimeUnit(
-              cfg, AUTO_REINDEX_SECTION, null, POLL_INTERVAL, 0L, TimeUnit.SECONDS);
-    }
-
-    public boolean enabled() {
-      return enabled;
-    }
-
-    public long delaySec() {
-      return delaySec;
-    }
-
-    public long pollSec() {
-      return pollSec;
-    }
-  }
-
-  public static class PeerInfo {
-    static final PeerInfoStrategy DEFAULT_PEER_INFO_STRATEGY = PeerInfoStrategy.STATIC;
-    static final String STRATEGY_KEY = "strategy";
-
-    private final PeerInfoStrategy strategy;
-
-    private PeerInfo(Config cfg) {
-      strategy = cfg.getEnum(PEER_INFO_SECTION, null, STRATEGY_KEY, DEFAULT_PEER_INFO_STRATEGY);
-      if (log.isDebugEnabled()) {
-        log.debug("Strategy: {}", strategy.name());
-      }
-    }
-
-    public PeerInfoStrategy strategy() {
-      return strategy;
-    }
-  }
-
-  public static class PeerInfoStatic {
-    public static final String STATIC_SUBSECTION = PeerInfoStrategy.STATIC.name().toLowerCase();
-    public static final String URL_KEY = "url";
-
-    private final Set<String> urls;
-
-    private PeerInfoStatic(Config cfg) {
-      urls =
-          Arrays.stream(cfg.getStringList(PEER_INFO_SECTION, STATIC_SUBSECTION, URL_KEY))
-              .filter(Objects::nonNull)
-              .filter(s -> !s.isEmpty())
-              .map(s -> CharMatcher.is('/').trimTrailingFrom(s))
-              .collect(Collectors.toSet());
-      log.debug("Urls: {}", urls);
-    }
-
-    public Set<String> urls() {
-      return ImmutableSet.copyOf(urls);
-    }
-  }
-
   private static Map<EventFamily, Boolean> eventsEnabled(Config config, String subsection) {
     Map<EventFamily, Boolean> eventsEnabled = new HashMap<>();
     for (EventFamily eventFamily : EventFamily.values()) {
@@ -250,10 +137,16 @@
     for (String section : config.getSubsections(KAFKA_SECTION)) {
       if (section.equals(subsectionName)) {
         for (String name : config.getNames(KAFKA_SECTION, section, true)) {
-          Object value = config.getString(KAFKA_SECTION, subsectionName, name);
-          String propName =
-              CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, name).replaceAll("-", ".");
-          target.put(propName, value);
+          if (name.startsWith(KAFKA_PROPERTY_PREFIX)) {
+            Object value = config.getString(KAFKA_SECTION, subsectionName, name);
+            String configProperty = name.replaceFirst(KAFKA_PROPERTY_PREFIX, "");
+            String propName =
+                CaseFormat.LOWER_CAMEL
+                    .to(CaseFormat.LOWER_HYPHEN, configProperty)
+                    .replaceAll("-", ".");
+            log.info("[{}] Setting kafka property: {} = {}", subsectionName, propName, value);
+            target.put(propName, value);
+          }
         }
       }
     }
@@ -341,25 +234,32 @@
     }
   }
 
-  public class KafkaSubscriber {
+  public class KafkaSubscriber extends Properties {
+    private static final long serialVersionUID = 1L;
+
     static final String KAFKA_SUBSCRIBER_SUBSECTION = "subscriber";
 
     private final boolean enabled;
     private final Integer pollingInterval;
-    private final Properties props = new Properties();
     private Map<EventFamily, Boolean> eventsEnabled;
     private final Config cfg;
 
     public KafkaSubscriber(Config cfg) {
       this.pollingInterval =
-          cfg.getInt(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, "pollingIntervalMs", 1000);
+          cfg.getInt(
+              KAFKA_SECTION,
+              KAFKA_SUBSCRIBER_SUBSECTION,
+              "pollingIntervalMs",
+              DEFAULT_POLLING_INTERVAL_MS);
       this.cfg = cfg;
 
       enabled = cfg.getBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, false);
 
       eventsEnabled = eventsEnabled(cfg, KAFKA_SUBSCRIBER_SUBSECTION);
 
-      applyKafkaConfig(cfg, KAFKA_SUBSCRIBER_SUBSECTION, props);
+      if (enabled) {
+        applyKafkaConfig(cfg, KAFKA_SUBSCRIBER_SUBSECTION, this);
+      }
     }
 
     public boolean enabled() {
@@ -370,13 +270,13 @@
       return eventsEnabled.get(eventFamily);
     }
 
-    public Properties getProps(UUID instanceId) {
+    public Properties initPropsWith(UUID instanceId) {
       String groupId =
           getString(
               cfg, KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, "groupId", instanceId.toString());
-      props.put("group.id", groupId);
+      this.put("group.id", groupId);
 
-      return props;
+      return this;
     }
 
     public Integer getPollingInterval() {
@@ -393,77 +293,6 @@
     }
   }
 
-  public static class Http {
-    static final String HTTP_SECTION = "http";
-    static final String USER_KEY = "user";
-    static final String PASSWORD_KEY = "password";
-    static final String CONNECTION_TIMEOUT_KEY = "connectionTimeout";
-    static final String SOCKET_TIMEOUT_KEY = "socketTimeout";
-    static final String MAX_TRIES_KEY = "maxTries";
-    static final String RETRY_INTERVAL_KEY = "retryInterval";
-    static final boolean DEFAULT_HTTP_ENABLED = true;
-
-    static final int DEFAULT_TIMEOUT_MS = 5000;
-    static final int DEFAULT_MAX_TRIES = 360;
-    static final int DEFAULT_RETRY_INTERVAL = 10000;
-
-    private final boolean enabled;
-    private final String user;
-    private final String password;
-    private final int connectionTimeout;
-    private final int socketTimeout;
-    private final int maxTries;
-    private final int retryInterval;
-    private final Map<EventFamily, Boolean> eventsEnabled;
-
-    private Http(Config cfg) {
-      enabled = cfg.getBoolean(HTTP_SECTION, ENABLE_KEY, DEFAULT_HTTP_ENABLED);
-      user = Strings.nullToEmpty(cfg.getString(HTTP_SECTION, null, USER_KEY));
-      password = Strings.nullToEmpty(cfg.getString(HTTP_SECTION, null, PASSWORD_KEY));
-      connectionTimeout = getInt(cfg, HTTP_SECTION, CONNECTION_TIMEOUT_KEY, DEFAULT_TIMEOUT_MS);
-      socketTimeout = getInt(cfg, HTTP_SECTION, SOCKET_TIMEOUT_KEY, DEFAULT_TIMEOUT_MS);
-      maxTries = getInt(cfg, HTTP_SECTION, MAX_TRIES_KEY, DEFAULT_MAX_TRIES);
-      retryInterval = getInt(cfg, HTTP_SECTION, RETRY_INTERVAL_KEY, DEFAULT_RETRY_INTERVAL);
-      eventsEnabled = new HashMap<>();
-      for (EventFamily eventFamily : EventFamily.values()) {
-        String enabledConfigKey = eventFamily.lowerCamelName() + "Enabled";
-        eventsEnabled.put(eventFamily, cfg.getBoolean(HTTP_SECTION, null, enabledConfigKey, true));
-      }
-    }
-
-    public boolean enabled() {
-      return enabled;
-    }
-
-    public String user() {
-      return user;
-    }
-
-    public String password() {
-      return password;
-    }
-
-    public int connectionTimeout() {
-      return connectionTimeout;
-    }
-
-    public int socketTimeout() {
-      return socketTimeout;
-    }
-
-    public int maxTries() {
-      return maxTries;
-    }
-
-    public int retryInterval() {
-      return retryInterval;
-    }
-
-    public boolean enabledEvent(EventFamily eventFamily) {
-      return eventsEnabled.get(eventFamily);
-    }
-  }
-
   /** Common parameters to cache, event, index */
   public abstract static class Forwarding {
     static final boolean DEFAULT_SYNCHRONIZE = true;
@@ -556,19 +385,4 @@
       return numStripedLocks;
     }
   }
-
-  public static class HealthCheck {
-    static final String HEALTH_CHECK_SECTION = "healthCheck";
-    static final boolean DEFAULT_HEALTH_CHECK_ENABLED = true;
-
-    private final boolean enabled;
-
-    private HealthCheck(Config cfg) {
-      enabled = cfg.getBoolean(HEALTH_CHECK_SECTION, ENABLE_KEY, DEFAULT_HEALTH_CHECK_ENABLED);
-    }
-
-    public boolean enabled() {
-      return enabled;
-    }
-  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/HttpModule.java
deleted file mode 100644
index c58eb90..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/HttpModule.java
+++ /dev/null
@@ -1,25 +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 com.googlesource.gerrit.plugins.multisite;
-
-import com.google.gerrit.httpd.plugins.HttpPluginModule;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.RestForwarderServletModule;
-
-class HttpModule extends HttpPluginModule {
-  @Override
-  protected void configureServlets() {
-    install(new RestForwarderServletModule());
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index 8bbf68b..0b4efaa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -20,17 +20,14 @@
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.autoreindex.AutoReindexModule;
 import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
 import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
 import com.googlesource.gerrit.plugins.multisite.event.EventModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerForwarderModule;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.RestForwarderModule;
 import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
 import com.googlesource.gerrit.plugins.multisite.kafka.consumer.KafkaConsumerModule;
 import com.googlesource.gerrit.plugins.multisite.kafka.router.ForwardedEventRouterModule;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfoModule;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.FileReader;
@@ -55,9 +52,6 @@
 
     install(new ForwarderModule());
 
-    if (config.http().enabled()) {
-      install(new RestForwarderModule(config.http()));
-    }
     if (config.cache().synchronize()) {
       install(new CacheModule());
     }
@@ -67,10 +61,6 @@
     if (config.index().synchronize()) {
       install(new IndexModule());
     }
-    if (config.autoReindex().enabled()) {
-      install(new AutoReindexModule());
-    }
-    install(new PeerInfoModule(config.peerInfo().strategy()));
 
     if (config.kafkaSubscriber().enabled()) {
       install(new KafkaConsumerModule(config.kafkaSubscriber()));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Setup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Setup.java
index d2f7456..c101dfd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Setup.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Setup.java
@@ -16,21 +16,7 @@
 
 import static com.googlesource.gerrit.plugins.multisite.Configuration.Cache.CACHE_SECTION;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.DEFAULT_THREAD_POOL_SIZE;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.CONNECTION_TIMEOUT_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.DEFAULT_MAX_TRIES;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.DEFAULT_RETRY_INTERVAL;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.DEFAULT_TIMEOUT_MS;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.HTTP_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.PASSWORD_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.RETRY_INTERVAL_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.SOCKET_TIMEOUT_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.USER_KEY;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.Index.INDEX_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Index.MAX_TRIES_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PEER_INFO_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PeerInfo.STRATEGY_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PeerInfoStatic.STATIC_SUBSECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PeerInfoStatic.URL_KEY;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.THREAD_POOL_SIZE_KEY;
 
 import com.google.common.base.Strings;
@@ -40,9 +26,7 @@
 import com.google.gerrit.pgm.init.api.InitStep;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.Configuration.PeerInfoStrategy;
 import java.nio.file.Path;
-import java.util.EnumSet;
 import java.util.Objects;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
@@ -74,43 +58,12 @@
       Path pluginConfigFile = site.etc_dir.resolve(pluginName + ".config");
       config = new FileBasedConfig(pluginConfigFile.toFile(), FS.DETECTED);
       config.load();
-      configurePeerInfoSection();
-      configureHttp();
       configureCacheSection();
       configureIndexSection();
       flags.cfg.setBoolean("database", "h2", "autoServer", true);
     }
   }
 
-  private void configurePeerInfoSection() {
-    ui.header("PeerInfo section");
-    PeerInfoStrategy strategy =
-        ui.readEnum(
-            PeerInfoStrategy.STATIC, EnumSet.allOf(PeerInfoStrategy.class), "Peer info strategy");
-    config.setEnum(PEER_INFO_SECTION, null, STRATEGY_KEY, strategy);
-    if (strategy == PeerInfoStrategy.STATIC) {
-      promptAndSetString(
-          titleWithNote("Peer URL", "urls"), PEER_INFO_SECTION, STATIC_SUBSECTION, URL_KEY, null);
-    }
-  }
-
-  private void configureHttp() {
-    ui.header("Http section");
-    promptAndSetString("User", HTTP_SECTION, USER_KEY, null);
-    promptAndSetString("Password", HTTP_SECTION, PASSWORD_KEY, null);
-    promptAndSetString(
-        "Max number of tries to forward to remote peer",
-        HTTP_SECTION,
-        MAX_TRIES_KEY,
-        str(DEFAULT_MAX_TRIES));
-    promptAndSetString(
-        "Retry interval [ms]", HTTP_SECTION, RETRY_INTERVAL_KEY, str(DEFAULT_RETRY_INTERVAL));
-    promptAndSetString(
-        "Connection timeout [ms]", HTTP_SECTION, CONNECTION_TIMEOUT_KEY, str(DEFAULT_TIMEOUT_MS));
-    promptAndSetString(
-        "Socket timeout [ms]", HTTP_SECTION, SOCKET_TIMEOUT_KEY, str(DEFAULT_TIMEOUT_MS));
-  }
-
   private void configureCacheSection() {
     ui.header("Cache section");
     promptAndSetString(
@@ -152,10 +105,6 @@
     return Integer.toString(n);
   }
 
-  private static String titleWithNote(String prefix, String suffix) {
-    return prefix + "; manually repeat this line to configure more " + suffix;
-  }
-
   @Override
   public void postRun() {}
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SetupLocalHAReplica.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SetupLocalHAReplica.java
deleted file mode 100644
index ed927dc..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SetupLocalHAReplica.java
+++ /dev/null
@@ -1 +0,0 @@
-package com.googlesource.gerrit.plugins.multisite;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AccountReindexRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AccountReindexRunnable.java
deleted file mode 100644
index 3396301..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AccountReindexRunnable.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.account.Accounts;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexAccountHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet;
-
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Optional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class AccountReindexRunnable extends ReindexRunnable<AccountState> {
-  private static final Logger log = LoggerFactory.getLogger(AccountReindexRunnable.class);
-
-  private final ForwardedIndexAccountHandler accountIdx;
-
-  private final Accounts accounts;
-
-  @Inject
-  public AccountReindexRunnable(
-      ForwardedIndexAccountHandler accountIdx,
-      IndexTs indexTs,
-      OneOffRequestContext ctx,
-      Accounts accounts) {
-    super(AbstractIndexRestApiServlet.IndexName.ACCOUNT, indexTs, ctx);
-    this.accountIdx = accountIdx;
-    this.accounts = accounts;
-  }
-
-  @Override
-  protected Iterable<AccountState> fetchItems() throws Exception {
-    return accounts.all();
-  }
-
-  @Override
-  protected Optional<Timestamp> indexIfNeeded(AccountState as, Timestamp sinceTs) {
-    try {
-      Account a = as.getAccount();
-      Timestamp accountTs = a.getRegisteredOn();
-      if (accountTs.after(sinceTs)) {
-        log.info("Index {}/{}/{}/{}", a.getId(), a.getFullName(), a.getPreferredEmail(), accountTs);
-        accountIdx.index(a.getId(), Operation.INDEX, Optional.empty());
-        return Optional.of(accountTs);
-      }
-    } catch (IOException | OrmException e) {
-      log.error("Reindex failed", e);
-    }
-    return Optional.empty();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AutoReindexModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AutoReindexModule.java
deleted file mode 100644
index b78123e..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AutoReindexModule.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.gerrit.extensions.events.AccountIndexedListener;
-import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.events.GroupIndexedListener;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.inject.AbstractModule;
-
-public class AutoReindexModule extends AbstractModule {
-
-  @Override
-  protected void configure() {
-    DynamicSet.bind(binder(), LifecycleListener.class).to(AutoReindexScheduler.class);
-    DynamicSet.bind(binder(), ChangeIndexedListener.class).to(IndexTs.class);
-    DynamicSet.bind(binder(), AccountIndexedListener.class).to(IndexTs.class);
-    DynamicSet.bind(binder(), GroupIndexedListener.class).to(IndexTs.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AutoReindexScheduler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AutoReindexScheduler.java
deleted file mode 100644
index e7ee7a8..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/AutoReindexScheduler.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-public class AutoReindexScheduler implements LifecycleListener {
-  private static final Logger log = LoggerFactory.getLogger(AutoReindexScheduler.class);
-  private final Configuration.AutoReindex cfg;
-  private final ChangeReindexRunnable changeReindex;
-  private final AccountReindexRunnable accountReindex;
-  private final GroupReindexRunnable groupReindex;
-  private final ProjectReindexRunnable projectReindex;
-  private final ScheduledExecutorService executor;
-  private final List<Future<?>> futureTasks = new ArrayList<>();
-
-  @Inject
-  public AutoReindexScheduler(
-      Configuration cfg,
-      WorkQueue workQueue,
-      ChangeReindexRunnable changeReindex,
-      AccountReindexRunnable accountReindex,
-      GroupReindexRunnable groupReindex,
-      ProjectReindexRunnable projectReindex) {
-    this.cfg = cfg.autoReindex();
-    this.changeReindex = changeReindex;
-    this.accountReindex = accountReindex;
-    this.groupReindex = groupReindex;
-    this.projectReindex = projectReindex;
-    this.executor = workQueue.createQueue(1, "MultiSite-AutoReindex");
-  }
-
-  @Override
-  public void start() {
-    if (cfg.pollSec() > 0) {
-      log.info("Scheduling auto-reindex after {}s and every {}s", cfg.delaySec(), cfg.pollSec());
-      futureTasks.add(
-          executor.scheduleAtFixedRate(
-              changeReindex, cfg.delaySec(), cfg.pollSec(), TimeUnit.SECONDS));
-      futureTasks.add(
-          executor.scheduleAtFixedRate(
-              accountReindex, cfg.delaySec(), cfg.pollSec(), TimeUnit.SECONDS));
-      futureTasks.add(
-          executor.scheduleAtFixedRate(
-              groupReindex, cfg.delaySec(), cfg.pollSec(), TimeUnit.SECONDS));
-      futureTasks.add(
-          executor.scheduleAtFixedRate(
-              projectReindex, cfg.delaySec(), cfg.pollSec(), TimeUnit.SECONDS));
-    } else {
-      log.info("Scheduling auto-reindex after {}s", cfg.delaySec());
-      futureTasks.add(executor.schedule(changeReindex, cfg.delaySec(), TimeUnit.SECONDS));
-      futureTasks.add(executor.schedule(accountReindex, cfg.delaySec(), TimeUnit.SECONDS));
-      futureTasks.add(executor.schedule(groupReindex, cfg.delaySec(), TimeUnit.SECONDS));
-      futureTasks.add(executor.schedule(projectReindex, cfg.delaySec(), TimeUnit.SECONDS));
-    }
-  }
-
-  @Override
-  public void stop() {
-    futureTasks.forEach(t -> t.cancel(true));
-    executor.shutdown();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ChangeReindexRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ChangeReindexRunnable.java
deleted file mode 100644
index a99efa6..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ChangeReindexRunnable.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.common.collect.Streams;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexChangeHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet;
-
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Iterator;
-import java.util.Optional;
-import java.util.stream.Stream;
-import org.eclipse.jgit.lib.Repository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ChangeReindexRunnable extends ReindexRunnable<Change> {
-  private static final Logger log = LoggerFactory.getLogger(ChangeReindexRunnable.class);
-
-  private final ForwardedIndexChangeHandler changeIdx;
-
-  private final ProjectCache projectCache;
-
-  private final GitRepositoryManager repoManager;
-
-  private final ChangeNotes.Factory notesFactory;
-
-  private static class StreamIterable implements Iterable<Change> {
-
-    private final Stream<Change> stream;
-
-    public StreamIterable(Stream<Change> stream) {
-      this.stream = stream;
-    }
-
-    @Override
-    public Iterator<Change> iterator() {
-      return stream.iterator();
-    }
-  }
-
-  @Inject
-  public ChangeReindexRunnable(
-      ForwardedIndexChangeHandler changeIdx,
-      IndexTs indexTs,
-      OneOffRequestContext ctx,
-      ProjectCache projectCache,
-      GitRepositoryManager repoManager,
-      ChangeNotes.Factory notesFactory) {
-    super(AbstractIndexRestApiServlet.IndexName.CHANGE, indexTs, ctx);
-    this.changeIdx = changeIdx;
-    this.projectCache = projectCache;
-    this.repoManager = repoManager;
-    this.notesFactory = notesFactory;
-  }
-
-  @Override
-  protected Iterable<Change> fetchItems() throws Exception {
-    Stream<Change> allChangesStream = Stream.empty();
-    Iterable<Project.NameKey> projects = projectCache.all();
-    for (Project.NameKey projectName : projects) {
-      try (Repository repo = repoManager.openRepository(projectName)) {
-        Stream<Change> projectChangesStream =
-            notesFactory
-                .scan(repo, projectName)
-                .map(
-                    (ChangeNotesResult changeNotes) -> {
-                      return changeNotes.notes().getChange();
-                    });
-        allChangesStream = Streams.concat(allChangesStream, projectChangesStream);
-      }
-    }
-    return new StreamIterable(allChangesStream);
-  }
-
-  @Override
-  protected Optional<Timestamp> indexIfNeeded(Change c, Timestamp sinceTs) {
-    try {
-      Timestamp changeTs = c.getLastUpdatedOn();
-      if (changeTs.after(sinceTs)) {
-        log.info(
-            "Index {}/{}/{} was updated after {}", c.getProject(), c.getId(), changeTs, sinceTs);
-        changeIdx.index(c.getProject() + "~" + c.getId(), Operation.INDEX, Optional.empty());
-        return Optional.of(changeTs);
-      }
-    } catch (OrmException | IOException e) {
-      log.error("Reindex failed", e);
-    }
-    return Optional.empty();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/GroupReindexRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/GroupReindexRunnable.java
deleted file mode 100644
index 9990c26..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/GroupReindexRunnable.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.server.group.db.Groups;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet;
-import java.sql.Timestamp;
-import java.util.Optional;
-
-public class GroupReindexRunnable extends ReindexRunnable<GroupReference> {
-  private final Groups groups;
-
-  @Inject
-  public GroupReindexRunnable(IndexTs indexTs, OneOffRequestContext ctx, Groups groups) {
-    super(AbstractIndexRestApiServlet.IndexName.GROUP, indexTs, ctx);
-    this.groups = groups;
-  }
-
-  @Override
-  protected Iterable<GroupReference> fetchItems() throws Exception {
-    return groups.getAllGroupReferences()::iterator;
-  }
-
-  @Override
-  protected Optional<Timestamp> indexIfNeeded(GroupReference g, Timestamp sinceTs) {
-    return Optional.empty();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/IndexTs.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/IndexTs.java
deleted file mode 100644
index e25adc2..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/IndexTs.java
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.gerrit.extensions.annotations.PluginData;
-import com.google.gerrit.extensions.events.AccountIndexedListener;
-import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.events.GroupIndexedListener;
-import com.google.gerrit.server.change.ChangeFinder;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet.IndexName;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.Optional;
-import java.util.concurrent.ScheduledExecutorService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import com.google.gerrit.extensions.events.ProjectIndexedListener;
-
-@Singleton
-public class IndexTs
-    implements ChangeIndexedListener,
-        AccountIndexedListener,
-        GroupIndexedListener,
-        ProjectIndexedListener {
-  private static final Logger log = LoggerFactory.getLogger(IndexTs.class);
-  private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
-
-  private final Path dataDir;
-  private final ScheduledExecutorService exec;
-  private final FlusherRunner flusher;
-  private final ChangeFinder changeFinder;
-
-  private volatile LocalDateTime changeTs;
-  private volatile LocalDateTime accountTs;
-  private volatile LocalDateTime groupTs;
-  private volatile LocalDateTime projectTs;
-
-  class FlusherRunner implements Runnable {
-
-    @Override
-    public void run() {
-      store(AbstractIndexRestApiServlet.IndexName.CHANGE, changeTs);
-      store(AbstractIndexRestApiServlet.IndexName.ACCOUNT, accountTs);
-      store(AbstractIndexRestApiServlet.IndexName.GROUP, groupTs);
-      store(AbstractIndexRestApiServlet.IndexName.PROJECT, projectTs);
-    }
-
-    private void store(AbstractIndexRestApiServlet.IndexName index, LocalDateTime latestTs) {
-      Optional<LocalDateTime> currTs = IndexTs.this.getUpdateTs(index);
-      if (!currTs.isPresent() || latestTs.isAfter(currTs.get())) {
-        Path indexTsFile = dataDir.resolve(index.name().toLowerCase());
-        try {
-          Files.write(indexTsFile, latestTs.format(formatter).getBytes(StandardCharsets.UTF_8));
-        } catch (IOException e) {
-          log.error("Unable to update last timestamp for index " + index, e);
-        }
-      }
-    }
-  }
-
-  @Inject
-  public IndexTs(@PluginData Path dataDir, WorkQueue queue, ChangeFinder changeFinder) {
-    this.dataDir = dataDir;
-    this.exec = queue.getDefaultQueue();
-    this.flusher = new FlusherRunner();
-    this.changeFinder = changeFinder;
-  }
-
-  @Override
-  public void onProjectIndexed(String project) {
-    update(IndexName.PROJECT, LocalDateTime.now());
-  }
-
-  @Override
-  public void onGroupIndexed(String uuid) {
-    update(IndexName.GROUP, LocalDateTime.now());
-  }
-
-  @Override
-  public void onAccountIndexed(int id) {
-    update(IndexName.ACCOUNT, LocalDateTime.now());
-  }
-
-  @Override
-  public void onChangeIndexed(String projectName, int id) {
-    try {
-      ChangeNotes changeNotes = changeFinder.findOne(projectName + "~" + id);
-      update(
-          IndexName.CHANGE,
-          changeNotes == null
-              ? LocalDateTime.now()
-              : changeNotes.getChange().getLastUpdatedOn().toLocalDateTime());
-    } catch (Exception e) {
-      log.warn("Unable to update the latest TS for change {}", e);
-    }
-  }
-
-  @Override
-  public void onChangeDeleted(int id) {
-    update(IndexName.CHANGE, LocalDateTime.now());
-  }
-
-  public Optional<LocalDateTime> getUpdateTs(AbstractIndexRestApiServlet.IndexName index) {
-    try {
-      Path indexTsFile = dataDir.resolve(index.name().toLowerCase());
-      if (indexTsFile.toFile().exists()) {
-        String tsString = Files.readAllLines(indexTsFile).get(0);
-        return Optional.of(LocalDateTime.parse(tsString, formatter));
-      }
-    } catch (Exception e) {
-      log.warn("Unable to read last timestamp for index {}", index, e);
-    }
-    return Optional.empty();
-  }
-
-  void update(AbstractIndexRestApiServlet.IndexName index, LocalDateTime dateTime) {
-    switch (index) {
-      case CHANGE:
-        changeTs = dateTime;
-        break;
-      case ACCOUNT:
-        accountTs = dateTime;
-        break;
-      case GROUP:
-        groupTs = dateTime;
-        break;
-      case PROJECT:
-        projectTs = dateTime;
-        break;
-      default:
-        throw new IllegalArgumentException("Unsupported index " + index);
-    }
-    exec.execute(flusher);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ProjectReindexRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ProjectReindexRunnable.java
deleted file mode 100644
index 8db9db0..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ProjectReindexRunnable.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet;
-import java.sql.Timestamp;
-import java.util.Optional;
-
-public class ProjectReindexRunnable extends ReindexRunnable<Project.NameKey> {
-
-  private final ProjectCache projectCache;
-
-  @Inject
-  public ProjectReindexRunnable(
-      IndexTs indexTs, OneOffRequestContext ctx, ProjectCache projectCache) {
-    super(AbstractIndexRestApiServlet.IndexName.PROJECT, indexTs, ctx);
-    this.projectCache = projectCache;
-  }
-
-  @Override
-  protected Iterable<Project.NameKey> fetchItems() {
-    return projectCache.all();
-  }
-
-  @Override
-  protected Optional<Timestamp> indexIfNeeded(Project.NameKey g, Timestamp sinceTs) {
-    return Optional.empty();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ReindexRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ReindexRunnable.java
deleted file mode 100644
index 44139a8..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/autoreindex/ReindexRunnable.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.autoreindex;
-
-import com.google.common.base.Stopwatch;
-import com.google.gerrit.server.util.ManualRequestContext;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.AbstractIndexRestApiServlet;
-
-import java.sql.Timestamp;
-import java.time.LocalDateTime;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-abstract class ReindexRunnable<T> implements Runnable {
-  private static final Logger log = LoggerFactory.getLogger(ReindexRunnable.class);
-
-  private final AbstractIndexRestApiServlet.IndexName itemName;
-  private final OneOffRequestContext ctx;
-  private final IndexTs indexTs;
-  private Timestamp newLastIndexTs;
-
-  @Inject
-  public ReindexRunnable(
-      AbstractIndexRestApiServlet.IndexName itemName, IndexTs indexTs, OneOffRequestContext ctx) {
-    this.itemName = itemName;
-    this.ctx = ctx;
-    this.indexTs = indexTs;
-  }
-
-  @Override
-  public void run() {
-    Optional<LocalDateTime> maybeIndexTs = indexTs.getUpdateTs(itemName);
-    String itemNameString = itemName.name().toLowerCase();
-    if (maybeIndexTs.isPresent()) {
-      newLastIndexTs = maxTimestamp(newLastIndexTs, Timestamp.valueOf(maybeIndexTs.get()));
-      log.debug("Scanning for all the {}s after {}", itemNameString, newLastIndexTs);
-      try (ManualRequestContext mctx = ctx.open()) {
-        int count = 0;
-        int errors = 0;
-        Stopwatch stopwatch = Stopwatch.createStarted();
-        for (T c : fetchItems()) {
-          try {
-            Optional<Timestamp> itemTs = indexIfNeeded(c, newLastIndexTs);
-            if (itemTs.isPresent()) {
-              count++;
-              newLastIndexTs = maxTimestamp(newLastIndexTs, itemTs.get());
-            }
-          } catch (Exception e) {
-            log.error("Unable to reindex {} {}", itemNameString, c, e);
-            errors++;
-          }
-        }
-        long elapsedNanos = stopwatch.stop().elapsed(TimeUnit.NANOSECONDS);
-        if (count > 0) {
-          log.info(
-              "{} {}s reindexed in {} msec ({}/sec), {} failed",
-              count,
-              itemNameString,
-              elapsedNanos / 1000000L,
-              (count * 1000L) / (elapsedNanos / 1000000L),
-              errors);
-        } else if (errors > 0) {
-          log.info("{} {}s failed to reindex", errors, itemNameString);
-        } else {
-          log.debug("Scanning finished");
-        }
-        indexTs.update(itemName, newLastIndexTs.toLocalDateTime());
-      } catch (Exception e) {
-        log.error("Unable to scan " + itemNameString + "s", e);
-      }
-    }
-  }
-
-  protected abstract Iterable<T> fetchItems() throws Exception;
-
-  protected abstract Optional<Timestamp> indexIfNeeded(T item, Timestamp sinceTs);
-
-  private Timestamp maxTimestamp(Timestamp ts1, Timestamp ts2) {
-    if (ts1 == null) {
-      return ts2;
-    }
-
-    if (ts2 == null) {
-      return ts1;
-    }
-
-    if (ts1.after(ts2)) {
-      return ts1;
-    }
-    return ts2;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerPublisher.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerPublisher.java
index cb84398..db13229 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerPublisher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerPublisher.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.broker;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.events.Event;
 import com.google.gson.Gson;
@@ -72,7 +73,8 @@
         body);
   }
 
-  private JsonObject eventToJson(Event event) {
+  @VisibleForTesting
+  public JsonObject eventToJson(Event event) {
     return gson.toJsonTree(event).getAsJsonObject();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java
index bc15c4b..0791e6a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDeserializer;
 import com.google.gerrit.server.events.SupplierDeserializer;
+import com.google.gerrit.server.events.SupplierSerializer;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.inject.Provider;
@@ -27,6 +28,7 @@
   public Gson get() {
     return new GsonBuilder()
         .registerTypeAdapter(Event.class, new EventDeserializer())
+        .registerTypeAdapter(Supplier.class, new SupplierSerializer())
         .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
         .create();
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
index e15b7e3..85dab30 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
@@ -17,6 +17,8 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -33,10 +35,12 @@
   private static final Logger log = LoggerFactory.getLogger(ForwardedEventHandler.class);
 
   private final EventDispatcher dispatcher;
+  private final OneOffRequestContext oneOffCtx;
 
   @Inject
-  public ForwardedEventHandler(EventDispatcher dispatcher) {
+  public ForwardedEventHandler(EventDispatcher dispatcher, OneOffRequestContext oneOffCtx) {
     this.dispatcher = dispatcher;
+    this.oneOffCtx = oneOffCtx;
   }
 
   /**
@@ -46,7 +50,7 @@
    * @throws OrmException If an error occur while retrieving the change the event belongs to.
    */
   public void dispatch(Event event) throws OrmException, PermissionBackendException {
-    try {
+    try (ManualRequestContext ctx = oneOffCtx.open()) {
       Context.setForwardedEvent(true);
       log.debug("dispatching event {}", event.getType());
       dispatcher.postEvent(event);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/GsonParser.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParser.java
similarity index 97%
rename from src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/GsonParser.java
rename to src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParser.java
index dbd9175..7930207 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/GsonParser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParser.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.forwarder.rest;
+package com.googlesource.gerrit.plugins.multisite.forwarder;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.reviewdb.client.Account;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractIndexRestApiServlet.java
deleted file mode 100644
index 2c3941a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractIndexRestApiServlet.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-
-import com.google.common.base.Charsets;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gwtorm.server.OrmException;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Optional;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-public abstract class AbstractIndexRestApiServlet<T, E> extends AbstractRestApiServlet {
-  private static final long serialVersionUID = -1L;
-  protected static final Gson gson = new GsonBuilder().create();
-
-  private final ForwardedIndexingHandler<T, E> forwardedIndexingHandler;
-  private final IndexName indexName;
-  private final boolean allowDelete;
-
-  public enum IndexName {
-    CHANGE,
-    ACCOUNT,
-    GROUP,
-    PROJECT;
-
-    @Override
-    public String toString() {
-      return name().toLowerCase();
-    }
-  }
-
-  abstract T parse(String id);
-
-  AbstractIndexRestApiServlet(
-      ForwardedIndexingHandler<T, E> forwardedIndexingHandler,
-      IndexName indexName,
-      boolean allowDelete) {
-    this.forwardedIndexingHandler = forwardedIndexingHandler;
-    this.indexName = indexName;
-    this.allowDelete = allowDelete;
-  }
-
-  AbstractIndexRestApiServlet(
-      ForwardedIndexingHandler<T, E> forwardedIndexingHandler, IndexName indexName) {
-    this(forwardedIndexingHandler, indexName, false);
-  }
-
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse rsp) {
-    process(req, rsp, Operation.INDEX);
-  }
-
-  @Override
-  protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) {
-    if (!allowDelete) {
-      sendError(
-          rsp, SC_METHOD_NOT_ALLOWED, String.format("cannot delete %s from index", indexName));
-    } else {
-      process(req, rsp, Operation.DELETE);
-    }
-  }
-
-  private void process(HttpServletRequest req, HttpServletResponse rsp, Operation operation) {
-    setHeaders(rsp);
-    String path = req.getRequestURI();
-    T id = parse(path.substring(path.lastIndexOf('/') + 1));
-    try {
-      forwardedIndexingHandler.index(id, operation, parseBody(req));
-      rsp.setStatus(SC_NO_CONTENT);
-    } catch (IOException e) {
-      sendError(rsp, SC_CONFLICT, e.getMessage());
-      log.error("Unable to update {} index", indexName, e);
-    } catch (OrmException e) {
-      String msg = String.format("Error trying to find %s", indexName);
-      sendError(rsp, SC_NOT_FOUND, msg);
-      log.debug(msg, e);
-    }
-  }
-
-  protected Optional<E> parseBody(HttpServletRequest req) throws IOException {
-    String contentType = req.getContentType();
-    if (contentType != null && contentType.contains("application/json")) {
-      return Optional.ofNullable(
-          fromJson(new InputStreamReader(req.getInputStream(), Charsets.UTF_8)));
-    }
-    return Optional.empty();
-  }
-
-  protected abstract E fromJson(Reader reader) throws IOException;
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractRestApiServlet.java
deleted file mode 100644
index 1646999..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractRestApiServlet.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public abstract class AbstractRestApiServlet extends HttpServlet {
-  private static final long serialVersionUID = 1L;
-  protected final Logger log = LoggerFactory.getLogger(getClass());
-
-  protected static void setHeaders(HttpServletResponse rsp) {
-    rsp.setContentType("text/plain");
-    rsp.setCharacterEncoding(UTF_8.name());
-  }
-
-  protected void sendError(HttpServletResponse rsp, int statusCode, String message) {
-    try {
-      rsp.sendError(statusCode, message);
-    } catch (IOException e) {
-      log.error("Failed to send error messsage: {}", e.getMessage(), e);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractRestForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractRestForwarder.java
deleted file mode 100644
index a82629b..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/AbstractRestForwarder.java
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.io.IOException;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
-import javax.net.ssl.SSLException;
-import org.apache.http.HttpException;
-import org.apache.http.client.ClientProtocolException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class AbstractRestForwarder {
-  enum RequestMethod {
-    POST,
-    DELETE
-  }
-
-  private static final Logger log = LoggerFactory.getLogger(AbstractRestForwarder.class);
-
-  private final HttpSession httpSession;
-  private final String pluginRelativePath;
-  private final Configuration cfg;
-  private final Provider<Set<PeerInfo>> peerInfoProvider;
-
-  AbstractRestForwarder(
-      HttpSession httpClient,
-      @PluginName String pluginName,
-      Configuration cfg,
-      Provider<Set<PeerInfo>> peerInfoProvider) {
-    this.httpSession = httpClient;
-    this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName);
-    this.cfg = cfg;
-    this.peerInfoProvider = peerInfoProvider;
-  }
-
-  protected boolean post(String action, String endpoint, Object id, Object payload) {
-    return execute(RequestMethod.POST, action, endpoint, id, payload);
-  }
-
-  protected boolean delete(String action, String endpoint, Object id, Object payload) {
-    return execute(RequestMethod.DELETE, action, endpoint, id, payload);
-  }
-
-  protected boolean execute(RequestMethod method, String action, String endpoint, Object id) {
-    return execute(method, action, endpoint, id, null);
-  }
-
-  private boolean execute(
-      RequestMethod method, String action, String endpoint, Object id, Object payload) {
-    List<CompletableFuture<Boolean>> futures =
-        peerInfoProvider
-            .get()
-            .stream()
-            .map(peer -> createRequest(method, peer, action, endpoint, id, payload))
-            .map(request -> CompletableFuture.supplyAsync(() -> request.execute()))
-            .collect(Collectors.toList());
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-    return futures.stream().allMatch(CompletableFuture::join);
-  }
-
-  private Request createRequest(
-      RequestMethod method,
-      PeerInfo peer,
-      String action,
-      String endpoint,
-      Object id,
-      Object payload) {
-    String destination = peer.getDirectUrl();
-    return new Request(action, id, destination) {
-      @Override
-      HttpResult send() throws IOException {
-        String request = Joiner.on("/").join(destination, pluginRelativePath, endpoint, id);
-        if (RequestMethod.POST == method) {
-          return httpSession.post(request, payload);
-        }
-        return httpSession.delete(request, payload);
-      }
-    };
-  }
-
-  private abstract class Request {
-    private final String action;
-    private final Object key;
-    private final String destination;
-
-    private int execCnt;
-
-    Request(String action, Object key, String destination) {
-      this.action = action;
-      this.key = key;
-      this.destination = destination;
-    }
-
-    boolean execute() {
-      log.debug("Executing {} {} towards {}", action, key, destination);
-      for (; ; ) {
-        try {
-          execCnt++;
-          tryOnce();
-          log.debug("{} {} towards {} OK", action, key, destination);
-          return true;
-        } catch (ForwardingException e) {
-          int maxTries = cfg.http().maxTries();
-          log.debug(
-              "Failed to {} {} on {} [{}/{}]", action, key, destination, execCnt, maxTries, e);
-          if (!e.isRecoverable()) {
-            log.error(
-                "{} {} towards {} failed with unrecoverable error; giving up",
-                action,
-                key,
-                destination,
-                e);
-            return false;
-          }
-          if (execCnt >= maxTries) {
-            log.error(
-                "Failed to {} {} on {} after {} tries; giving up",
-                action,
-                key,
-                destination,
-                maxTries);
-            return false;
-          }
-
-          log.debug("Retrying to {} {} on {}", action, key, destination);
-          try {
-            Thread.sleep(cfg.http().retryInterval());
-          } catch (InterruptedException ie) {
-            log.error("{} {} towards {} was interrupted; giving up", action, key, destination, ie);
-            Thread.currentThread().interrupt();
-            return false;
-          }
-        }
-      }
-    }
-
-    void tryOnce() throws ForwardingException {
-      try {
-        HttpResult result = send();
-        if (!result.isSuccessful()) {
-          throw new ForwardingException(
-              true, String.format("Unable to %s %s : %s", action, key, result.getMessage()));
-        }
-      } catch (IOException e) {
-        throw new ForwardingException(isRecoverable(e), e.getMessage(), e);
-      }
-    }
-
-    abstract HttpResult send() throws IOException;
-
-    boolean isRecoverable(IOException e) {
-      Throwable cause = e.getCause();
-      return !(e instanceof SSLException
-          || cause instanceof HttpException
-          || cause instanceof ClientProtocolException);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/CacheRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/CacheRestApiServlet.java
deleted file mode 100644
index 53fe653..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/CacheRestApiServlet.java
+++ /dev/null
@@ -1,62 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-
-import com.google.common.base.Splitter;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEntry;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheNotFoundException;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedCacheEvictionHandler;
-
-import java.io.IOException;
-import java.util.List;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-@Singleton
-class CacheRestApiServlet extends AbstractRestApiServlet {
-  private static final int CACHENAME_INDEX = 1;
-  private static final long serialVersionUID = -1L;
-
-  private final ForwardedCacheEvictionHandler forwardedCacheEvictionHandler;
-
-  @Inject
-  CacheRestApiServlet(ForwardedCacheEvictionHandler forwardedCacheEvictionHandler) {
-    this.forwardedCacheEvictionHandler = forwardedCacheEvictionHandler;
-  }
-
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse rsp) {
-    setHeaders(rsp);
-    try {
-      List<String> params = Splitter.on('/').splitToList(req.getPathInfo());
-      String cacheName = params.get(CACHENAME_INDEX);
-      String json = req.getReader().readLine();
-      forwardedCacheEvictionHandler.evict(
-          CacheEntry.from(cacheName, GsonParser.fromJson(cacheName, json)));
-      rsp.setStatus(SC_NO_CONTENT);
-    } catch (CacheNotFoundException e) {
-      log.error("Failed to process eviction request: {}", e.getMessage());
-      sendError(rsp, SC_BAD_REQUEST, e.getMessage());
-    } catch (IOException e) {
-      log.error("Failed to process eviction request: {}", e.getMessage(), e);
-      sendError(rsp, SC_BAD_REQUEST, e.getMessage());
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/EventRestApiServlet.java
deleted file mode 100644
index 5298e05..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/EventRestApiServlet.java
+++ /dev/null
@@ -1,80 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.net.MediaType.JSON_UTF_8;
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
-
-import com.google.common.base.Supplier;
-import com.google.common.io.CharStreams;
-import com.google.common.net.MediaType;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.EventDeserializer;
-import com.google.gerrit.server.events.SupplierDeserializer;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-@Singleton
-class EventRestApiServlet extends AbstractRestApiServlet {
-  private static final long serialVersionUID = -1L;
-
-  private final ForwardedEventHandler forwardedEventHandler;
-
-  @Inject
-  EventRestApiServlet(ForwardedEventHandler forwardedEventHandler) {
-    this.forwardedEventHandler = forwardedEventHandler;
-  }
-
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse rsp) {
-    setHeaders(rsp);
-    try {
-      if (!MediaType.parse(req.getContentType()).is(JSON_UTF_8)) {
-        sendError(rsp, SC_UNSUPPORTED_MEDIA_TYPE, "Expecting " + JSON_UTF_8 + " content type");
-        return;
-      }
-      forwardedEventHandler.dispatch(getEventFromRequest(req));
-      rsp.setStatus(SC_NO_CONTENT);
-    } catch (OrmException e) {
-      log.debug("Error trying to find a change ", e);
-      sendError(rsp, SC_NOT_FOUND, "Change not found\n");
-    } catch (IOException | PermissionBackendException e) {
-      log.error("Unable to re-trigger event", e);
-      sendError(rsp, SC_BAD_REQUEST, e.getMessage());
-    }
-  }
-
-  private static Event getEventFromRequest(HttpServletRequest req) throws IOException {
-    String jsonEvent = CharStreams.toString(req.getReader());
-    Gson gson =
-        new GsonBuilder()
-            .registerTypeAdapter(Event.class, new EventDeserializer())
-            .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
-            .create();
-    return gson.fromJson(jsonEvent, Event.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ForwardingException.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ForwardingException.java
deleted file mode 100644
index a902271..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ForwardingException.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-class ForwardingException extends Exception {
-  private static final long serialVersionUID = 1L;
-
-  private final boolean isRecoverable;
-
-  ForwardingException(boolean isRecoverable, String message) {
-    super(message);
-    this.isRecoverable = isRecoverable;
-  }
-
-  ForwardingException(boolean isRecoverable, String message, Throwable cause) {
-    super(message, cause);
-    this.isRecoverable = isRecoverable;
-  }
-
-  boolean isRecoverable() {
-    return isRecoverable;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpClientProvider.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpClientProvider.java
deleted file mode 100644
index b8b506e..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpClientProvider.java
+++ /dev/null
@@ -1,133 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.X509Certificate;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.config.Registry;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.HttpClientConnectionManager;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-import org.apache.http.conn.socket.PlainConnectionSocketFactory;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** Provides an HTTP client with SSL capabilities. */
-class HttpClientProvider implements Provider<CloseableHttpClient> {
-  private static final Logger log = LoggerFactory.getLogger(HttpClientProvider.class);
-  private static final int CONNECTIONS_PER_ROUTE = 100;
-  // Up to 2 target instances with the max number of connections per host:
-  private static final int MAX_CONNECTIONS = 2 * CONNECTIONS_PER_ROUTE;
-
-  private static final int MAX_CONNECTION_INACTIVITY = 10000;
-
-  private final Configuration cfg;
-  private final SSLConnectionSocketFactory sslSocketFactory;
-
-  @Inject
-  HttpClientProvider(Configuration cfg) {
-    this.cfg = cfg;
-    this.sslSocketFactory = buildSslSocketFactory();
-  }
-
-  @Override
-  public CloseableHttpClient get() {
-    return HttpClients.custom()
-        .setSSLSocketFactory(sslSocketFactory)
-        .setConnectionManager(customConnectionManager())
-        .setDefaultCredentialsProvider(buildCredentials())
-        .setDefaultRequestConfig(customRequestConfig())
-        .build();
-  }
-
-  private RequestConfig customRequestConfig() {
-    return RequestConfig.custom()
-        .setConnectTimeout(cfg.http().connectionTimeout())
-        .setSocketTimeout(cfg.http().socketTimeout())
-        .setConnectionRequestTimeout(cfg.http().connectionTimeout())
-        .build();
-  }
-
-  private HttpClientConnectionManager customConnectionManager() {
-    Registry<ConnectionSocketFactory> socketFactoryRegistry =
-        RegistryBuilder.<ConnectionSocketFactory>create()
-            .register("https", sslSocketFactory)
-            .register("http", PlainConnectionSocketFactory.INSTANCE)
-            .build();
-    PoolingHttpClientConnectionManager connManager =
-        new PoolingHttpClientConnectionManager(socketFactoryRegistry);
-    connManager.setDefaultMaxPerRoute(CONNECTIONS_PER_ROUTE);
-    connManager.setMaxTotal(MAX_CONNECTIONS);
-    connManager.setValidateAfterInactivity(MAX_CONNECTION_INACTIVITY);
-    return connManager;
-  }
-
-  private static SSLConnectionSocketFactory buildSslSocketFactory() {
-    return new SSLConnectionSocketFactory(buildSslContext(), NoopHostnameVerifier.INSTANCE);
-  }
-
-  private static SSLContext buildSslContext() {
-    try {
-      TrustManager[] trustAllCerts = new TrustManager[] {new DummyX509TrustManager()};
-      SSLContext context = SSLContext.getInstance("TLS");
-      context.init(null, trustAllCerts, null);
-      return context;
-    } catch (KeyManagementException | NoSuchAlgorithmException e) {
-      log.warn("Error building SSLContext object", e);
-      return null;
-    }
-  }
-
-  private BasicCredentialsProvider buildCredentials() {
-    BasicCredentialsProvider creds = new BasicCredentialsProvider();
-    creds.setCredentials(
-        AuthScope.ANY, new UsernamePasswordCredentials(cfg.http().user(), cfg.http().password()));
-    return creds;
-  }
-
-  private static class DummyX509TrustManager implements X509TrustManager {
-    @Override
-    public X509Certificate[] getAcceptedIssuers() {
-      return new X509Certificate[0];
-    }
-
-    @Override
-    public void checkClientTrusted(X509Certificate[] certs, String authType) {
-      // no check
-    }
-
-    @Override
-    public void checkServerTrusted(X509Certificate[] certs, String authType) {
-      // no check
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpResponseHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpResponseHandler.java
deleted file mode 100644
index 1a1d576..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpResponseHandler.java
+++ /dev/null
@@ -1,72 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-
-import java.io.IOException;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.util.EntityUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-
-class HttpResponseHandler implements ResponseHandler<HttpResult> {
-
-  static class HttpResult {
-    private final boolean successful;
-    private final String message;
-
-    HttpResult(boolean successful, String message) {
-      this.successful = successful;
-      this.message = message;
-    }
-
-    boolean isSuccessful() {
-      return successful;
-    }
-
-    String getMessage() {
-      return message;
-    }
-  }
-
-  private static final Logger log = LoggerFactory.getLogger(HttpResponseHandler.class);
-
-  @Override
-  public HttpResult handleResponse(HttpResponse response) {
-    return new HttpResult(isSuccessful(response), parseResponse(response));
-  }
-
-  private static boolean isSuccessful(HttpResponse response) {
-    return response.getStatusLine().getStatusCode() == SC_NO_CONTENT;
-  }
-
-  private static String parseResponse(HttpResponse response) {
-    HttpEntity entity = response.getEntity();
-    String asString = "";
-    if (entity != null) {
-      try {
-        asString = EntityUtils.toString(entity);
-      } catch (IOException e) {
-        log.error("Error parsing entity", e);
-      }
-    }
-    return asString;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpSession.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpSession.java
deleted file mode 100644
index 8319d0d..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpSession.java
+++ /dev/null
@@ -1,88 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Supplier;
-import com.google.common.net.MediaType;
-import com.google.gerrit.server.events.SupplierSerializer;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-
-import java.io.IOException;
-import java.net.URI;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-
-class HttpSession {
-  private final CloseableHttpClient httpClient;
-  private final Gson gson =
-      new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create();
-
-  @Inject
-  HttpSession(CloseableHttpClient httpClient) {
-    this.httpClient = httpClient;
-  }
-
-  HttpResult post(String endpoint) throws IOException {
-    return post(endpoint, null);
-  }
-
-  HttpResult post(String uri, Object content) throws IOException {
-    HttpPost post = new HttpPost(uri);
-    setContent(post, content);
-    return httpClient.execute(post, new HttpResponseHandler());
-  }
-
-  HttpResult delete(String uri) throws IOException {
-    return httpClient.execute(new HttpDelete(uri), new HttpResponseHandler());
-  }
-
-  HttpResult delete(String uri, Object content) throws IOException {
-    HttpDeleteWithBody delete = new HttpDeleteWithBody(uri);
-    setContent(delete, content);
-    return httpClient.execute(delete, new HttpResponseHandler());
-  }
-
-  private void setContent(HttpEntityEnclosingRequestBase request, Object content) {
-    if (content != null) {
-      request.addHeader("Content-Type", MediaType.JSON_UTF_8.toString());
-      request.setEntity(new StringEntity(jsonEncode(content), Charsets.UTF_8));
-    }
-  }
-
-  private String jsonEncode(Object content) {
-    if (content instanceof String) {
-      return (String) content;
-    }
-    return gson.toJson(content);
-  }
-
-  private class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {
-    @Override
-    public String getMethod() {
-      return HttpDelete.METHOD_NAME;
-    }
-
-    private HttpDeleteWithBody(String uri) {
-      setURI(URI.create(uri));
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexAccountRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexAccountRestApiServlet.java
deleted file mode 100644
index 8c63611..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexAccountRestApiServlet.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexAccountHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.AccountIndexEvent;
-import java.io.IOException;
-import java.io.Reader;
-
-@Singleton
-class IndexAccountRestApiServlet
-    extends AbstractIndexRestApiServlet<Account.Id, AccountIndexEvent> {
-  private static final long serialVersionUID = -1L;
-
-  @Inject
-  IndexAccountRestApiServlet(ForwardedIndexAccountHandler handler) {
-    super(handler, IndexName.ACCOUNT);
-  }
-
-  @Override
-  Account.Id parse(String id) {
-    return new Account.Id(Integer.parseInt(id));
-  }
-
-  @Override
-  protected AccountIndexEvent fromJson(Reader reader) throws IOException {
-    return gson.fromJson(reader, AccountIndexEvent.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexChangeRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexChangeRestApiServlet.java
deleted file mode 100644
index 7ead4bd..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexChangeRestApiServlet.java
+++ /dev/null
@@ -1,43 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexChangeHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
-import java.io.IOException;
-import java.io.Reader;
-
-@Singleton
-class IndexChangeRestApiServlet extends AbstractIndexRestApiServlet<String, ChangeIndexEvent> {
-  private static final long serialVersionUID = -1L;
-
-  @Inject
-  IndexChangeRestApiServlet(ForwardedIndexChangeHandler handler) {
-    super(handler, IndexName.CHANGE, true);
-  }
-
-  @Override
-  String parse(String id) {
-    return Url.decode(id);
-  }
-
-  @Override
-  protected ChangeIndexEvent fromJson(Reader reader) throws IOException {
-    return gson.fromJson(reader, ChangeIndexEvent.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexGroupRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexGroupRestApiServlet.java
deleted file mode 100644
index 51d64d5..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexGroupRestApiServlet.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexGroupHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.GroupIndexEvent;
-import java.io.IOException;
-import java.io.Reader;
-
-@Singleton
-class IndexGroupRestApiServlet
-    extends AbstractIndexRestApiServlet<AccountGroup.UUID, GroupIndexEvent> {
-  private static final long serialVersionUID = -1L;
-
-  @Inject
-  IndexGroupRestApiServlet(ForwardedIndexGroupHandler handler) {
-    super(handler, IndexName.GROUP);
-  }
-
-  @Override
-  AccountGroup.UUID parse(String id) {
-    return new AccountGroup.UUID(id);
-  }
-
-  @Override
-  protected GroupIndexEvent fromJson(Reader reader) throws IOException {
-    return gson.fromJson(reader, GroupIndexEvent.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexProjectRestApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexProjectRestApiServlet.java
deleted file mode 100644
index dd94e3f..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexProjectRestApiServlet.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexProjectHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectIndexEvent;
-import java.io.IOException;
-import java.io.Reader;
-
-@Singleton
-class IndexProjectRestApiServlet
-    extends AbstractIndexRestApiServlet<Project.NameKey, ProjectIndexEvent> {
-  private static final long serialVersionUID = -1L;
-
-  @Inject
-  IndexProjectRestApiServlet(ForwardedIndexProjectHandler handler) {
-    super(handler, IndexName.PROJECT);
-  }
-
-  @Override
-  Project.NameKey parse(String projectName) {
-    return new Project.NameKey(Url.decode(projectName));
-  }
-
-  @Override
-  protected ProjectIndexEvent fromJson(Reader reader) throws IOException {
-    return gson.fromJson(reader, ProjectIndexEvent.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/PeerInfoNotAvailableException.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/PeerInfoNotAvailableException.java
deleted file mode 100644
index b656ebf..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/PeerInfoNotAvailableException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import java.io.IOException;
-
-public class PeerInfoNotAvailableException extends IOException {
-  private static final long serialVersionUID = 1L;
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListApiServlet.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListApiServlet.java
deleted file mode 100644
index 9f55f32..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListApiServlet.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedProjectListUpdateHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-@Singleton
-class ProjectListApiServlet extends AbstractRestApiServlet {
-  private static final long serialVersionUID = -1L;
-
-  private final ForwardedProjectListUpdateHandler forwardedProjectListUpdateHandler;
-
-  @Inject
-  ProjectListApiServlet(ForwardedProjectListUpdateHandler forwardedProjectListUpdateHandler) {
-    this.forwardedProjectListUpdateHandler = forwardedProjectListUpdateHandler;
-  }
-
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse rsp) {
-    process(req, rsp, false);
-  }
-
-  @Override
-  protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) {
-    process(req, rsp, true);
-  }
-
-  private void process(HttpServletRequest req, HttpServletResponse rsp, boolean delete) {
-    setHeaders(rsp);
-    String requestURI = req.getRequestURI();
-    String projectName = requestURI.substring(requestURI.lastIndexOf('/') + 1);
-    try {
-      forwardedProjectListUpdateHandler.update(
-          new ProjectListUpdateEvent(Url.decode(projectName), delete));
-      rsp.setStatus(SC_NO_CONTENT);
-    } catch (IOException e) {
-      log.error("Unable to update project list", e);
-      sendError(rsp, SC_BAD_REQUEST, e.getMessage());
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestCacheEvictionForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestCacheEvictionForwarder.java
deleted file mode 100644
index 3277d71..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestCacheEvictionForwarder.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.util.Set;
-
-@Singleton
-public class RestCacheEvictionForwarder extends AbstractRestForwarder
-    implements CacheEvictionForwarder {
-  @Inject
-  RestCacheEvictionForwarder(
-      HttpSession httpClient,
-      @PluginName String pluginName,
-      Configuration cfg,
-      Provider<Set<PeerInfo>> peerInfoProvider) {
-    super(httpClient, pluginName, cfg, peerInfoProvider);
-  }
-
-  @Override
-  public boolean evict(CacheEvictionEvent cacheEvictionEvent) {
-    String json = GsonParser.toJson(cacheEvictionEvent.cacheName, cacheEvictionEvent.key);
-    return post(
-        "invalidate cache " + cacheEvictionEvent.cacheName,
-        "cache",
-        cacheEvictionEvent.cacheName,
-        json);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestForwarderModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestForwarderModule.java
deleted file mode 100644
index 834529d..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestForwarderModule.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.inject.AbstractModule;
-import com.google.inject.Scopes;
-import com.googlesource.gerrit.plugins.multisite.Configuration.Http;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventFamily;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
-import org.apache.http.impl.client.CloseableHttpClient;
-
-public class RestForwarderModule extends AbstractModule {
-  private final Http http;
-
-  public RestForwarderModule(Http http) {
-    this.http = http;
-  }
-
-  @Override
-  protected void configure() {
-    bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class).in(Scopes.SINGLETON);
-    bind(HttpSession.class);
-    DynamicSet.bind(binder(), ProjectListUpdateForwarder.class).to(RestProjectListUpdateForwarder.class);
-    if (http.enabledEvent(EventFamily.INDEX_EVENT)) {
-      DynamicSet.bind(binder(), IndexEventForwarder.class).to(RestIndexEventForwarder.class);
-    }
-    if (http.enabledEvent(EventFamily.CACHE_EVENT)) {
-      DynamicSet.bind(binder(), CacheEvictionForwarder.class).to(RestCacheEvictionForwarder.class);
-    }
-    if (http.enabledEvent(EventFamily.STREAM_EVENT)) {
-      DynamicSet.bind(binder(), ProjectListUpdateForwarder.class)
-          .to(RestProjectListUpdateForwarder.class);
-    }
-    if (http.enabledEvent(EventFamily.STREAM_EVENT)) {
-      DynamicSet.bind(binder(), StreamEventForwarder.class).to(RestStreamEventForwarder.class);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestForwarderServletModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestForwarderServletModule.java
deleted file mode 100644
index cd66c83..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestForwarderServletModule.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.httpd.plugins.HttpPluginModule;
-
-public class RestForwarderServletModule extends HttpPluginModule {
-  @Override
-  protected void configureServlets() {
-    serveRegex("/index/account/\\d+$").with(IndexAccountRestApiServlet.class);
-    serveRegex("/index/change/.*$").with(IndexChangeRestApiServlet.class);
-    serveRegex("/index/group/\\w+$").with(IndexGroupRestApiServlet.class);
-    serveRegex("/index/project/.*$").with(IndexProjectRestApiServlet.class);
-    serve("/event/*").with(EventRestApiServlet.class);
-    serve("/cache/project_list/*").with(ProjectListApiServlet.class);
-    serve("/cache/*").with(CacheRestApiServlet.class);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestIndexEventForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestIndexEventForwarder.java
deleted file mode 100644
index f2a4d21..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestIndexEventForwarder.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.AccountIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.GroupIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.util.Set;
-
-@Singleton
-public class RestIndexEventForwarder extends AbstractRestForwarder implements IndexEventForwarder {
-  @Inject
-  RestIndexEventForwarder(
-      HttpSession httpClient,
-      @PluginName String pluginName,
-      Configuration cfg,
-      Provider<Set<PeerInfo>> peerInfoProvider) {
-    super(httpClient, pluginName, cfg, peerInfoProvider);
-  }
-
-  @Override
-  public boolean indexAccount(AccountIndexEvent event) {
-    return post("index account", "index/account", event.accountId, event);
-  }
-
-  @Override
-  public boolean indexChange(ChangeIndexEvent event) {
-    return post(
-        "index change",
-        "index/change",
-        Url.encode(event.projectName) + "~" + event.changeId,
-        event);
-  }
-
-  @Override
-  public boolean deleteChangeFromIndex(ChangeIndexEvent event) {
-    return delete("delete change", "index/change", "~" + event.changeId, event);
-  }
-
-  @Override
-  public boolean indexGroup(GroupIndexEvent event) {
-    return post("index group", "index/group", event.groupUUID, event);
-  }
-
-  @Override
-  public boolean indexProject(ProjectIndexEvent event) {
-    return post("index project", "index/project", Url.encode(event.projectName), event);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestProjectListUpdateForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestProjectListUpdateForwarder.java
deleted file mode 100644
index 59cc288..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestProjectListUpdateForwarder.java
+++ /dev/null
@@ -1,55 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.util.Set;
-
-@Singleton
-class RestProjectListUpdateForwarder extends AbstractRestForwarder
-    implements ProjectListUpdateForwarder {
-
-  @Inject
-  RestProjectListUpdateForwarder(
-      HttpSession httpClient,
-      @PluginName String pluginName,
-      Configuration cfg,
-      Provider<Set<PeerInfo>> peerInfoProvider) {
-    super(httpClient, pluginName, cfg, peerInfoProvider);
-  }
-
-  @Override
-  public boolean updateProjectList(ProjectListUpdateEvent event) {
-    return execute(
-        event.remove ? RequestMethod.DELETE : RequestMethod.POST,
-        String.format("Update project_list, %s ", event.remove ? "remove" : "add"),
-        buildProjectListEndpoint(),
-        Url.encode(event.projectName));
-  }
-
-  private static String buildProjectListEndpoint() {
-    return Joiner.on("/").join("cache", Constants.PROJECT_LIST);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestStreamEventForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestStreamEventForwarder.java
deleted file mode 100644
index 0f2e665..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestStreamEventForwarder.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.events.Event;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.util.Set;
-
-@Singleton
-public class RestStreamEventForwarder extends AbstractRestForwarder
-    implements StreamEventForwarder {
-  @Inject
-  RestStreamEventForwarder(
-      HttpSession httpClient,
-      @PluginName String pluginName,
-      Configuration cfg,
-      Provider<Set<PeerInfo>> peerInfoProvider) {
-    super(httpClient, pluginName, cfg, peerInfoProvider);
-  }
-
-  @Override
-  public boolean send(final Event event) {
-    return post("send event", "event", event.type, event);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java
index bde629e..3af9047 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java
@@ -16,6 +16,8 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
@@ -45,6 +47,7 @@
   private final AtomicBoolean closed = new AtomicBoolean(false);
   private final Deserializer<SourceAwareEventWrapper> valueDeserializer;
   private final Configuration configuration;
+  private final OneOffRequestContext oneOffCtx;
 
   public AbstractKafkaSubcriber(
       Configuration configuration,
@@ -52,17 +55,19 @@
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       ForwardedEventRouter eventRouter,
       Provider<Gson> gsonProvider,
-      @InstanceId UUID instanceId) {
+      @InstanceId UUID instanceId,
+      OneOffRequestContext oneOffCtx) {
     this.configuration = configuration;
     this.eventRouter = eventRouter;
     this.gsonProvider = gsonProvider;
     this.instanceId = instanceId;
+    this.oneOffCtx = oneOffCtx;
     final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
     try {
       Thread.currentThread().setContextClassLoader(AbstractKafkaSubcriber.class.getClassLoader());
       this.consumer =
           new KafkaConsumer<>(
-              configuration.kafkaSubscriber().getProps(instanceId),
+              configuration.kafkaSubscriber().initPropsWith(instanceId),
               keyDeserializer,
               new ByteArrayDeserializer());
     } finally {
@@ -95,7 +100,7 @@
   protected abstract EventFamily getEventFamily();
 
   private void processRecord(ConsumerRecord<byte[], byte[]> consumerRecord) {
-    try {
+    try (ManualRequestContext ctx = oneOffCtx.open()) {
 
       SourceAwareEventWrapper event =
           valueDeserializer.deserialize(consumerRecord.topic(), consumerRecord.value());
@@ -109,16 +114,16 @@
           logger.atInfo().log("Header[%s] Body[%s]", event.getHeader(), event.getBody());
           eventRouter.route(event.getEventBody(gsonProvider));
         } catch (IOException e) {
-          logger.atSevere().log(
-              "Malformed event '%s': [Exception: %s]", event.getHeader().getEventType(), e);
+          logger.atSevere().withCause(e).log(
+              "Malformed event '%s': [Exception: %s]", event.getHeader().getEventType());
         } catch (PermissionBackendException | OrmException e) {
-          logger.atSevere().log(
-              "Cannot handle message %s: [Exception: %s]", event.getHeader().getEventType(), e);
+          logger.atSevere().withCause(e).log(
+              "Cannot handle message %s: [Exception: %s]", event.getHeader().getEventType());
         }
       }
     } catch (Exception e) {
-      logger.atSevere().log(
-          "Malformed event '%s': [Exception: %s]", new String(consumerRecord.value()), e);
+      logger.atSevere().withCause(e).log(
+          "Malformed event '%s': [Exception: %s]", new String(consumerRecord.value()));
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventSubscriber.java
index 564a7d7..0c5dac9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventSubscriber.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
 
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -34,8 +35,16 @@
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       StreamEventRouter eventRouter,
       Provider<Gson> gsonProvider,
-      @InstanceId UUID instanceId) {
-    super(configuration, keyDeserializer, valueDeserializer, eventRouter, gsonProvider, instanceId);
+      @InstanceId UUID instanceId,
+      OneOffRequestContext oneOffCtx) {
+    super(
+        configuration,
+        keyDeserializer,
+        valueDeserializer,
+        eventRouter,
+        gsonProvider,
+        instanceId,
+        oneOffCtx);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java
index 7ee04b9..0720cf2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
 
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -34,8 +35,16 @@
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       IndexEventRouter eventRouter,
       Provider<Gson> gsonProvider,
-      @InstanceId UUID instanceId) {
-    super(configuration, keyDeserializer, valueDeserializer, eventRouter, gsonProvider, instanceId);
+      @InstanceId UUID instanceId,
+      OneOffRequestContext oneOffCtx) {
+    super(
+        configuration,
+        keyDeserializer,
+        valueDeserializer,
+        eventRouter,
+        gsonProvider,
+        instanceId,
+        oneOffCtx);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java
index 727bb91..581dc2f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
 
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -34,8 +35,16 @@
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       ProjectListUpdateRouter eventRouter,
       Provider<Gson> gsonProvider,
-      @InstanceId UUID instanceId) {
-    super(configuration, keyDeserializer, valueDeserializer, eventRouter, gsonProvider, instanceId);
+      @InstanceId UUID instanceId,
+      OneOffRequestContext oneOffCtx) {
+    super(
+        configuration,
+        keyDeserializer,
+        valueDeserializer,
+        eventRouter,
+        gsonProvider,
+        instanceId,
+        oneOffCtx);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/SourceAwareEventWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/SourceAwareEventWrapper.java
index 47c06e4..4f3a4d6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/SourceAwareEventWrapper.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/SourceAwareEventWrapper.java
@@ -76,12 +76,17 @@
 
     @Override
     public String toString() {
-      return "{" +
-                     "eventId=" + eventId +
-                     ", eventType='" + eventType + '\'' +
-                     ", sourceInstanceId=" + sourceInstanceId +
-                     ", eventCreatedOn=" + eventCreatedOn +
-                     '}';
+      return "{"
+          + "eventId="
+          + eventId
+          + ", eventType='"
+          + eventType
+          + '\''
+          + ", sourceInstanceId="
+          + sourceInstanceId
+          + ", eventCreatedOn="
+          + eventCreatedOn
+          + '}';
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java
index 29cc533..3ac08d5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
 
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -34,8 +35,16 @@
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       StreamEventRouter eventRouter,
       Provider<Gson> gsonProvider,
-      @InstanceId UUID instanceId) {
-    super(configuration, keyDeserializer, valueDeserializer, eventRouter, gsonProvider, instanceId);
+      @InstanceId UUID instanceId,
+      OneOffRequestContext oneOffCtx) {
+    super(
+        configuration,
+        keyDeserializer,
+        valueDeserializer,
+        eventRouter,
+        gsonProvider,
+        instanceId,
+        oneOffCtx);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/router/CacheEvictionEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/router/CacheEvictionEventRouter.java
index 95cef94..c2a06d2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/router/CacheEvictionEventRouter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/router/CacheEvictionEventRouter.java
@@ -19,8 +19,8 @@
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEntry;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheNotFoundException;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedCacheEvictionHandler;
+import com.googlesource.gerrit.plugins.multisite.forwarder.GsonParser;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.GsonParser;
 
 @Singleton
 public class CacheEvictionEventRouter implements ForwardedCacheEvictionEventRouter {
@@ -32,8 +32,7 @@
   }
 
   @Override
-  public void route(CacheEvictionEvent sourceEvent) throws CacheNotFoundException {
-    CacheEvictionEvent cacheEvictionEvent = (CacheEvictionEvent) sourceEvent;
+  public void route(CacheEvictionEvent cacheEvictionEvent) throws CacheNotFoundException {
     Object parsedKey =
         GsonParser.fromJson(cacheEvictionEvent.cacheName, cacheEvictionEvent.key.toString());
     cacheEvictionHanlder.evict(CacheEntry.from(cacheEvictionEvent.cacheName, parsedKey));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PeerInfo.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PeerInfo.java
deleted file mode 100644
index 1cd2d0e..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PeerInfo.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.peers;
-
-public class PeerInfo {
-
-  private final String directUrl;
-
-  public PeerInfo(String directUrl) {
-    this.directUrl = directUrl;
-  }
-
-  public String getDirectUrl() {
-    return directUrl;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PeerInfoModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PeerInfoModule.java
deleted file mode 100644
index 719b5a0..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PeerInfoModule.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.peers;
-
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.inject.TypeLiteral;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-
-import java.util.Set;
-
-public class PeerInfoModule extends LifecycleModule {
-
-  private final Configuration.PeerInfoStrategy strategy;
-
-  public PeerInfoModule(Configuration.PeerInfoStrategy strategy) {
-    this.strategy = strategy;
-  }
-
-  @Override
-  protected void configure() {
-    switch (strategy) {
-      case STATIC:
-        bind(new TypeLiteral<Set<PeerInfo>>() {}).toProvider(PluginConfigPeerInfoProvider.class);
-        break;
-      default:
-        throw new IllegalArgumentException("Unsupported peer info strategy: " + strategy);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PluginConfigPeerInfoProvider.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PluginConfigPeerInfoProvider.java
deleted file mode 100644
index f698002..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/PluginConfigPeerInfoProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.peers;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-
-import java.util.HashSet;
-import java.util.Set;
-
-@Singleton
-public class PluginConfigPeerInfoProvider implements Provider<Set<PeerInfo>> {
-
-  private final Set<PeerInfo> peers = new HashSet<>();
-
-  @Inject
-  PluginConfigPeerInfoProvider(Configuration cfg) {
-    for (String url : cfg.peerInfoStatic().urls()) {
-      peers.add(new PeerInfo(url));
-    }
-  }
-
-  @Override
-  public Set<PeerInfo> get() {
-    return peers;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/jgroups/JGroupsPeerInfoProvider.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/jgroups/JGroupsPeerInfoProvider.java
deleted file mode 100644
index 803ad0c..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/peers/jgroups/JGroupsPeerInfoProvider.java
+++ /dev/null
@@ -1 +0,0 @@
-package com.googlesource.gerrit.plugins.multisite.peers.jgroups;
diff --git a/src/main/resources/Documentation/architecture-first-iteration.png b/src/main/resources/Documentation/architecture-first-iteration.png
new file mode 100644
index 0000000..1a9fe36
--- /dev/null
+++ b/src/main/resources/Documentation/architecture-first-iteration.png
Binary files differ
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 3b22941..b10d790 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -8,22 +8,9 @@
 File '@PLUGIN@.config'
 --------------------
 
-### Static definition of the multi-site nodes.
+## Sample configuration.
 
 ```
-[autoReindex]
-  enabled = false
-[peerInfo]
-  strategy = static
-[peerInfo "static"]
-  url = first_target_instance_url
-  url = second_target_instance_url
-[http]
-  enabled = true
-  
-  user = username
-  password = password
-
 [kafka]
   bootstrapServers = kafka-1:9092,kafka-2:9092,kafka-3:9092
 
@@ -35,91 +22,29 @@
 [kafka "publisher"]
   enabled = true
 
+  indexEventEnabled = true
+  cacheEventEnabled = true
+  projectListEventEnabled = true
+  streamEventEnabled = true
+
+  KafkaProp-compressionType = none
+  KafkaProp-deliveryTimeoutMs = 60000
+
 [kafka "subscriber"]
   enabled = true
-  
   pollingIntervalMs = 1000
-  autoCommitIntervalMs = 1000
 
+  KafkaProp-enableAutoCommit = true
+  KafkaProp-autoCommitIntervalMs = 1000
+  KafkaProp-autoCommitIntervalMs = 5000
+
+  indexEventEnabled = true
+  cacheEventEnabled = true
+  projectListEventEnabled = true
+  streamEventEnabled = true
 ```
 
-```autoReindex.enabled```
-:   Enable the tracking of the latest change indexed under data/multi-site
-    for each of the indexes. At startup scans all the changes, accounts and groups
-    and reindex the ones that have been updated by other nodes while the server was down.
-    When not specified, the default is "false", that means no automatic tracking
-    and indexing at start.
-
-```autoReindex.delay```
-:   When autoReindex is enabled, indicates the delay aftere the plugin startup,
-    before triggering the conditional reindexing of all changes, accounts and groups.
-    Delay is expressed in Gerrit time values:
-    * s, sec, second, seconds
-    * m, min, minute, minutes
-    * h, hr, hour, hours
-    * d, day, days
-    * w, week, weeks (`1 week` is treated as `7 days`)
-    * mon, month, months (`1 month` is treated as `30 days`)
-    * y, year, years (`1 year` is treated as `365 days`)
-    If a time unit suffix is not specified, `hours` is assumed.
-    Defaults to 24 hours.
-
-    When not specified, the default is "10 seconds".
-
-```autoReindex.pollInterval```
-:   When autoReindex is enabled, indicates the interval between the conditional
-    reindexing of all changes, accounts and groups.
-    Delay is expressed in Gerrit time values as in [autoReindex.delay](#autoReindex.delay).
-    When not specified, polling of conditional reindexing is disabled.
-
-```autoReindex.interval```
-:   Enable the tracking of the latest change indexed under data/multi-site
-    for each of the indexes. At startup scans all the changes, accounts and groups
-    and reindex the ones that have been updated by other nodes while the server was down.
-    When not specified, the default is "false", that means no automatic tracking
-    and indexing at start.
-
-```peerInfo.strategy```
-:   Strategy to find other peers. The only supported strategy is `static`.
-    Defaults to `static`.
-* The `static` strategy allows to staticly configure the peer gerrit instance using
-the configuration parameter `peerInfo.static.url`.
-
-```peerInfo.static.url```
-:   Specify the URL for the peer instance. If more than one peer instance is to be
-    configured, add as many url entries as necessary.
-
-```http.user```
-:   Username to connect to the peer instance.
-
-```http.password```
-:   Password to connect to the peer instance.
-
-@PLUGIN@ plugin uses REST API calls to keep the target instance in-sync. It
-is possible to customize the parameters of the underlying http client doing these
-calls by specifying the following fields:
-
-```http.connectionTimeout```
-:   Maximum interval of time in milliseconds the plugin waits for a connection
-    to the target instance. When not specified, the default value is set to 5000ms.
-
-```http.socketTimeout```
-:   Maximum interval of time in milliseconds the plugin waits for a response from the
-    target instance once the connection has been established. When not specified,
-    the default value is set to 5000ms.
-
-```http.maxTries```
-:   Maximum number of times the plugin should attempt when calling a REST API in
-    the target instance. Setting this value to 0 will disable retries. When not
-    specified, the default value is 360. After this number of failed tries, an
-    error is logged.
-
-```http.retryInterval```
-:   The interval of time in milliseconds between the subsequent auto-retries.
-    When not specified, the default value is set to 10000ms.
-
-NOTE: the default settings for `http.timeout` and `http.maxTries` ensure that
-the plugin will keep retrying to forward a message for one hour.
+## Configuration parameters
 
 ```cache.synchronize```
 :   Whether to synchronize cache evictions.
@@ -163,9 +88,6 @@
 :   The interval of time in milliseconds between the subsequent auto-retries.
     Defaults to 30000 (30 seconds).
 
-```healthcheck.enable```
-:   Whether to enable the health check endpoint. Defaults to 'true'.
-
 ```kafka.bootstrapServers```
 :	List of Kafka broker hosts:port to use for publishing events to the message broker
 
@@ -190,43 +112,58 @@
     Defaults: false
 
 ```kafka.publisher.indexEventEnabled```
-:   Enable publication of index events
+:   Enable publication of index events, ignored when `kafka.publisher.enabled` is false
     Defaults: true
 
 ```kafka.publisher.cacheEventEnabled```
-:   Enable publication of cache events
+:   Enable publication of cache events, ignored when `kafka.publisher.enabled` is false
     Defaults: true
 
 ```kafka.publisher.projectListEventEnabled```
-:   Enable publication of project list events
+:   Enable publication of project list events, ignored when `kafka.publisher.enabled` is false
     Defaults: true
 
 ```kafka.publisher.streamEventEnabled```    
-:   Enable publication of stream events
+:   Enable publication of stream events, ignored when `kafka.publisher.enabled` is false
     Defaults: true
 
 ```kafka.subscriber.enabled```
-:   Enable consuming of Kafka events
+:   Enable consuming of events from Kafka
     Defaults: false
 
 ```kafka.subscriber.indexEventEnabled```
-:   Enable consumption of index events
+:   Enable consumption of index events, ignored when `kafka.subscriber.enabled` is false
     Defaults: true
 
 ```kafka.subscriber.cacheEventEnabled```
-:   Enable consumption of cache events
+:   Enable consumption of cache events, ignored when `kafka.subscriber.enabled` is false
     Defaults: true
 
 ```kafka.subscriber.projectListEventEnabled```
-:   Enable consumption of project list events
+:   Enable consumption of project list events, ignored when `kafka.subscriber.enabled` is false
     Defaults: true
 
 ```kafka.subscriber.streamEventEnabled```    
-:   Enable consumption of stream events
+:   Enable consumption of stream events, ignored when `kafka.subscriber.enabled` is false
     Defaults: true
 
 ```kafka.subscriber.pollingIntervalMs```
 :   Polling interval for checking incoming events
+    Defaults: 1000
 
-```kafka.subscriber.autoCommitIntervalMs```
-:   Interval for committing incoming events automatically after consumption
+#### Custom kafka properties:
+
+In addition to the above settings, custom Kafka properties can be explicitly set for `publisher` and `subscriber`.
+In order to be acknowledged, these properties need to be prefixed with the `KafkaProp-` prefix and then camelCased,
+as follows: `KafkaProp-yourPropertyValue`
+
+For example, if you want to set the `auto.commit.interval.ms` property for your consumers, you will need to configure
+this property as `KafkaProp-autoCommitIntervalMs`.
+
+**NOTE**: custom kafka properties will be ignored when the relevant subsection is disabled (i.e. `kafka.subscriber.enabled`
+and/or `kafka.publisher.enabled` are set to `false`).
+
+The complete list of available settings can be found directly in the kafka website:
+
+* **Publisher**: https://kafka.apache.org/documentation/#producerconfigs
+* **Subscriber**: https://kafka.apache.org/documentation/#consumerconfigs
\ No newline at end of file
diff --git a/src/main/resources/Documentation/design.md b/src/main/resources/Documentation/design.md
new file mode 100644
index 0000000..e3b7ecc
--- /dev/null
+++ b/src/main/resources/Documentation/design.md
@@ -0,0 +1,378 @@
+# Gerrit Multi-Site Plugin Design
+
+This document aims at helping in collecting and organizing the thoughts about
+the design of the Gerrit multi-site plugin and supporting the definition of the
+[implementation roadmap](#next-steps-in-the-road-map).
+
+It starts presenting a background of the problem that is trying to address and
+the tools currently available in the Gerrit ecosystem that helps to support the
+solution. It then gives an overall roadmap of the support for Gerrit
+multi-site and a snapshot of the current status of the design and its associated
+limitations and constraints.
+
+## Approaches to highly scalable and available Gerrit
+
+Offering a highly available and scalable service is a challenging problem. There
+are trade-offs to be made because of the constraints defined by the
+[CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem), and therefore designing a
+performant and scalable solution is a real challenge.
+
+Companies that adopt Gerrit as the center of their development and review
+pipeline often have the requirement to be available on a 24/7 basis and possibly
+serving large and geographically distributed teams in different continents.
+
+### Vertical scaling and high-availability
+
+Vertical scaling is one of the options to support a high load and a large number
+of users.  Having a big powerful server with multiple cores and plenty of RAM to
+potentially fit the most frequently used repositories simplifies the design and
+implementation of the system. Nowadays the cost of hardware and the availability
+of multi-core CPUs have made this solution highly attractive to some large
+Gerrit setups. The central Gerrit server can also be duplicated with an
+active/passive or active/active high-availability setup where the storage of the
+Git repositories is shared across nodes through dedicated fibre-channel lines or
+SANs.
+
+This approach can be suitable for mid to large-sized Gerrit Installations where
+teams are co-located or connected via high-speed dedicated networks. However,
+then teams can be located on the other side of the planet, the speed of light
+would still limit the highest theoretical fire-channel direct connection (e.g.,
+from San Francisco to Bangalore the  theoretical absolute minimum latency is 50
+msec, but in practical terms, it is often around 150/200 msec in the best case
+scenarios).
+
+### Horizontal scaling and multi-site
+
+One alternative approach is horizontal scaling, where the workload can be spread
+across several nodes distributed to different locations. This solution offers a
+higher level of scalability and lower latency across locations but requires
+a more complex design.
+
+Two teams located one in San Francisco and the other in Bangalore would access a
+set of Gerrit masters located closer to their geographical position, with higher
+bandwidth and lower latency. The number of Gerrit masters can be scaled up and
+down on-demand, reducing the potential operational costs due to the
+proliferation of multiple servers.
+
+### Multi-master and multi-site, the best of both worlds
+
+The vertical and horizontal approaches can be also combined together to achieve
+both high performances on the same location and low latency across
+geographically distributed sites.
+
+The geographical locations with larger teams and projects can have a bigger
+Gerrit server in a high-availability configuration, while the ones that have
+less critical service levels can use a lower-spec setup.
+
+## Focus of the multi-site plugin
+
+The  multi-site plugin is intended to enable the  OpenSource version of Gerrit
+Code Review code-base to support horizontal scalability across sites.
+
+Gerrit has been already been deployed in a multi-site configuration at
+[Google](https://www.youtube.com/watch?v=wlHpqfnIBWE) and a multi-master fashion
+at [Qualcomm](https://www.youtube.com/watch?v=X_rmI8TbKmY). Both implementations
+included fixes and extensions that were focussed in addressing the specific
+infrastructure requirements of the Google and Qualcomm global networks. Those
+requirements may or may not be shared with the rest of the OpenSource Community.
+
+Qualcomm's version of Gerrit is a fork of v2.7, Google's deployment is
+proprietary and would not be suitable for any environment outside the Google's
+data-centers.
+
+The multi-site plugin, instead, is based on standard OpenSource components and
+is deployed on a standard cloud environment. It is currently used in a multi-
+master and multi-site deployment on GerritHub.io, serving two continents (Europe
+and Americas) in a high-availability setup on each site.
+
+# The road to multi-site
+
+The development of the multi-site support for Gerrit is complex and thus has
+been deliberately broken down into incremental steps. The starting point is a
+single Gerrit master deployment, and the end goal is a fully distributed set of
+cooperating Gerrit masters across the globe.
+
+1. 1x master / single location.
+2. 2x masters (active/standby) / single location - shared disks
+3. 2x masters (active/passive) / single location - shared disks
+4. 2x masters (active RW/active RO) / single location - shared disks
+5. 2x masters (active RW/active RO) / single location - separate disks
+6. 2x masters (active RW/active RO) / active + disaster recovery location
+7. 2x masters (active RW/active RO) / two locations
+8. 2x masters (active RW/active RW) sharded / two locations
+9. 3x masters (active RW/active RW) sharded with auto-election / two locations
+10. Multiple masters (active RW/active RW) with quorum / multiple locations
+
+The transition between steps does require not only an evolution of the Gerrit
+setup and the set of plugins but also a different maturity level in the way the
+servers are provision, maintained and versioned across the network. Qualcomm
+pointed out the evolution of the company culture and the ability to consistently
+version and provision the different server environments as a winning factor of
+their multi-master setup.
+
+Google is currently running at stage #10, Qualcomm is at stage #4 with the
+difference that both masters are serving RW traffic, due to the specifics
+of their underlying storage, NFS and JGit implementation that allows concurrent
+locking at filesystem level.
+
+## History and maturity level of the multi-site plugin
+
+This plugin is coming from the excellent work on the high-availability plugin,
+introduced by Ericsson for solving a mutli-master at stage #4. The git log history
+of this projects still shows the 'branching point' on where it started.
+
+The current version of the multi-site plugin is at stage #7, which is a pretty
+advanced stage in the Gerrit multi-master/multi-site configuration.
+
+Thanks to the multi-site plugin, it is possible to have Gerrit configured and
+available in two separate geo-locations (e.g. San Francisco and Bangalore),
+where both of them are serving local traffic through the local instances with
+minimum latency.
+
+### Why another plugin from a high-availability fork?
+
+By reading this design document you may be wondering the reason behind
+creating yet another plugin for solving multi-master instead of just keeping
+a single code-base with the high-availability plugin.
+The reason can be found in the initial part of design that described the two
+different approaches to scalability: vertical (single site) and horizonal (multi-site).
+
+You could in theory keep a single code-base to manage both of them, however the
+result would have been very complicated and difficult to configure and install.
+Having two more focussed plugins, one for high-availability and another for
+multi-site, would allow to have a simpler and more usable experience for developers
+of the plugin and for the Gerrit administrators using it.
+
+### Benefits
+
+There are some advantages in implementing multi-site at stage #7:
+
+- Optimal latency of the read-only operations on both sites, which makes around 90%
+  of the Gerrit traffic overall.
+
+- High SLA (99.99% or higher, source: GerritHub.io) due to the possibility of
+  implementing both high-availability inside the local site and automatic site
+  failover in case of a catastrophe in one of the two sites.
+
+- Transparency of access through a single Gerrit URL entry-point.
+
+- Automatic failover, disaster recovery and leader re-election.
+
+- The two sites have local consistency and, on a global level, eventual consistency.
+
+### Limitations
+
+The current limitations of stage #7 are:
+
+- Only one of the two sites can be RW and thus accepting modifications on the
+  Git repositories or the review data.
+
+- It can easily support only two sites.
+  You could potentially use it for more sites, however the configuration
+  and maintenance efforts are more than linear to the number of nodes.
+
+- Switch between the RO to RW site is defined by a unique decision point, which
+  is a Single-Point-of-Failure
+
+- Lack of transactionality between sites.
+  Data written to one site is acknowledged
+  before its replication to the other location.
+
+- The solution requires a Server completely based on NoteDb and thus requires
+  Gerrit v2.16 or later.
+
+**NOTE:** If you are not familiar with NoteDb, please read the relevant
+[section in the Gerrit documentation](https://gerrit-documentation.storage.googleapis.com/Documentation/2.16.5/note-db.html).
+
+### Example of multi-site operations
+
+Let's suppose the RW site is San Francisco and the RO site Bangalore. The
+modifications of data will always come to San Francisco and flow to Bangalore
+with a latency that can be anywhere between seconds and minutes, depending on
+the network infrastructure between the two sites. A developer located in
+Bangalore will always see a "snapshot in the past" of the data from both the
+Gerrit UI and on the Git repository served locally, while a developer located in
+San Francisco will always see the "latest and greatest" of everything.
+
+Should the central site in San Francisco collapse or not become available for a
+significant period of time, the Bangalore site will take over as main RW Gerrit
+site and will be able to serve any operation. The roles will then be inverted
+where the people in San Francisco will have to use the remote Gerrit server
+located in Bangalore while the local system is down. Once the San Francisco site
+is back, it will need to pass the "necessary checks" to be re-elected as the
+main RW site.
+
+# Plugin design
+
+This section goes into the high-level design of the current solution and lists
+the components involved and how they interact with each other.
+
+## What to replicate across Gerrit sites
+
+There are several distinct classes of information that have to be kept
+consistent across different sites to guarantee seamless operation of the
+distributed system.
+
+- Git repositories: they are stored on disk and are the most important
+Information to maintain.
+
+  * Git BLOBs, objects, refs and trees.
+
+  * NoteDb, including Groups, Accounts and review data
+
+  * Projects configuration and ACLs
+
+  * Projects submit rules
+
+- Indexes: this is a series of secondary indexes to allow search and quick access
+  to the Git repository data. Indexes are persistent across restarts.
+
+- Caches: is a set of in-memory and persisted designed to reduce CPU and disk
+  utilization and improve performance
+
+- Web Sessions: define an active user session with Gerrit allowing to reduce the
+  load to the underlying authentication system.
+  Sessions are stored by default on the local filesystem in an H2 table but can
+  be externalized via plugins, like the WebSession Flatfile.
+
+To achieve a stage #7 multi-site configuration, all the above information needs
+to replicate transparently across sites.
+
+## Overall high-level architecture
+
+The multi-site solution described here is based on the combined use of different
+components:
+
+- **multi-site plugin**: enables the replication of Gerrit _indexes_, _caches_,
+  and _stream events_ across sites
+
+- **replication plugin**: enables the replication of the _Git repositories_ across
+  sites
+
+- **web-session flat file plugin**: supports the storage of _active sessions_
+  to an external file that can be shared and synchronized across sites.
+
+- **health check plugin**: supports the automatic election of the RW site based
+  on a number of underlying conditions of the data and the systems.
+
+- **HA Proxy**: provides the single entry-point to all Gerrit functionality across sites.
+
+The combination of the above components makes the Gerrit multi-site
+configuration possible.
+
+![Initial Multi-Site Plugin Architecture](./architecture-first-iteration.png)
+
+## Current implementation Details
+
+The multi-site plugin adopts an event-sourcing pattern and is based on an
+external message broker. The current implementation is based on Apache Kafka,
+however, it is potentially extensible to many others like RabbitMQ or NATS.
+
+### Eventual consistency on Git, indexes, caches, and stream events
+
+The replication of the Git repositories, indexes, cache and stream events happen
+on different channels and at different speeds. Git data is typically larger than
+meta-data and has higher latency than reindexing, cache evictions or stream
+events. That means that when someone pushes a new change to Gerrit on one site,
+the Git data (commits, BLOBs, trees, and refs) may arrive later than the
+associated reindexing or cache eviction events.
+
+It is, therefore, necessary to handle the lack of synchronization of those
+channels in the multi-site plugin and reconcile the events at the destination
+ends.
+
+The solution adopted by the multi-site plugin supports eventual consistency at
+rest at the data level, thanks to the following two components:
+
+* A mechanism to recognize _not-yet-processable events_ related to data not yet
+available (based on the timestamp information available on both the metadata
+update and the data event)
+
+* A queue of *not-yet-processable events* and an *asynchronous processor*
+to check if they became processable. The system also is configured to discard
+events that have been in the queue for too long.
+
+### Avoiding event replication loops
+
+Stream events also are wrapped into an event header containing a source identifier,
+so that events originated by the same node in the broker-based channel are silently
+dropped to prevent the loop.
+The events originated by the same node in the broker-based channel are
+dropped to prevent the loop. Stream events also are wrapped into an event header
+containing a source identifier, so that they are not replicated multiple times.
+
+Gerrit has the concept of server-id, which, unfortunately, would not help us for
+solving this problem:  all the nodes in a Gerrit cluster must have the same
+server-id to allow interoperability of the data stored in NoteDb.
+
+The multi-site plugin introduces a new concept of instance-id, which is a UUID
+generated during startup and saved into the data folder of the Gerrit site. If
+the Gerrit site is cleared or removed, a new id is generated and the multi-site
+plugin will start consuming all events that have been previously produced.
+
+The concept of the instance-id is very useful and other plugins could benefit
+from it. It would be the first candidate to be moved into the Gerrit core and
+generated and maintained with the rest of the configuration.
+
+Once Gerrit will start having an instance-id, that information could then be
+included in all stream events also, making the multi-site plugin "enveloping of
+events" redundant.
+
+### Managing failures
+
+The broker based solutions improve the resilience and scalability of the system,
+but still has a point of failure in the availability of the broker. However, the
+choice of the broker allows having a high-level of redundancy and a multi-master
+/ multi-site configuration at transport and storage level.
+
+At the moment the acknowledge level for publication can be controlled via
+configuration and allows to tune the QoS of the publication process. Failures
+are explicitly not handled at the moment, and they are just logged as errors.
+There is no retry mechanism to handle temporary failures.
+
+# Next steps in the road-map
+
+## Step-1: fill the gaps of multi-site stage #7:
+
+- Detection of a stale site. The health check plugin has no awareness that one
+  site that can be "too outdated" because it is still technically "healthy." A
+  stale site needs to be put outside the balancing and all traffic needs to go
+  to the more up-to-date site.
+
+- Web session replication. Currently needs to be implemented at filesystem level
+  using rsync across sites, which can be a problem because of the delay
+  introduced. Should a site fail, some of the users may lose their sessions
+  because the rsync was not executed yet.
+
+- Index rebuild in case of broker failure. In the catastrophic event of a global
+  failure at the broker level, the indexes of the two sites would be out of
+  sync. A mechanism is needed to be put in place to recover the situation
+  without having to necessarily reindex both sites offline, which would require
+  even days for huge installations.
+
+- Git/SSH redirection. Local users relying on Git/SSH protocol would not be able
+  to use the local site for serving their requests, because HAProxy would not be
+  able to understand the type of traffic and would be forced always to use the
+  RW site, even though the operation was RO.
+
+- Support for different brokers: the current multi-site plugin supports Kafka.
+  More brokers would need to be supported in a fashion similar to the
+  [ITS-* plugins framework](https://gerrit-review.googlesource.com/admin/repos/q/filter:plugins%252Fits).
+  The multi-site plugin would not have anymore the explicit
+  references to Kafka, but other plugins may contribute the implementation to
+  the broker extension point.
+
+- Splitting the publishing and subscribing part of this plugin in two separate
+  plugins: the generation of the events would be combined to the current kafka-
+  events plugin while the multi-site will be more focussed in supporting the
+  consumption and sorting out the replication issues.
+
+## Step-2: move to multi-site stage #8.
+
+- Auto-reconfigure HAProxy rules based on the projects sharding policy
+
+- Serve RW/RW traffic based on the project name/ref-name.
+
+- Balance traffic with "locally-aware" policies based on historical data
+
+- Preventing split-brain in case of temporary sites isolation
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java
index 12c5142..a4bebb7 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java
@@ -22,29 +22,16 @@
 import static com.googlesource.gerrit.plugins.multisite.Configuration.Event.EVENT_SECTION;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.Forwarding.DEFAULT_SYNCHRONIZE;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.Forwarding.SYNCHRONIZE_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.HealthCheck.DEFAULT_HEALTH_CHECK_ENABLED;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.HealthCheck.HEALTH_CHECK_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.CONNECTION_TIMEOUT_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.DEFAULT_MAX_TRIES;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.DEFAULT_RETRY_INTERVAL;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.DEFAULT_TIMEOUT_MS;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.HTTP_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.MAX_TRIES_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.PASSWORD_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.RETRY_INTERVAL_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.SOCKET_TIMEOUT_KEY;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.Http.USER_KEY;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.Index.INDEX_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PEER_INFO_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PeerInfoStatic.STATIC_SUBSECTION;
-import static com.googlesource.gerrit.plugins.multisite.Configuration.PeerInfoStatic.URL_KEY;
+import static com.googlesource.gerrit.plugins.multisite.Configuration.KAFKA_PROPERTY_PREFIX;
+import static com.googlesource.gerrit.plugins.multisite.Configuration.KAFKA_SECTION;
+import static com.googlesource.gerrit.plugins.multisite.Configuration.KafkaPublisher.KAFKA_PUBLISHER_SUBSECTION;
+import static com.googlesource.gerrit.plugins.multisite.Configuration.KafkaSubscriber.KAFKA_SUBSCRIBER_SUBSECTION;
 import static com.googlesource.gerrit.plugins.multisite.Configuration.THREAD_POOL_SIZE_KEY;
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.server.config.PluginConfigFactory;
-import java.io.IOException;
-import java.util.List;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
@@ -57,20 +44,13 @@
   private static final String INVALID_BOOLEAN = "invalidBoolean";
   private static final String INVALID_INT = "invalidInt";
   private static final String PLUGIN_NAME = "multi-site";
-  private static final String PASS = "fakePass";
-  private static final String USER = "fakeUser";
-  private static final String URL = "http://fakeUrl";
-  private static final List<String> URLS = ImmutableList.of(URL, "http://anotherUrl/");
-  private static final int TIMEOUT = 5000;
-  private static final int MAX_TRIES = 5;
-  private static final int RETRY_INTERVAL = 1000;
   private static final int THREAD_POOL_SIZE = 1;
 
   @Mock private PluginConfigFactory pluginConfigFactoryMock;
   private Config globalPluginConfig;
 
   @Before
-  public void setUp() throws IOException {
+  public void setUp() {
     globalPluginConfig = new Config();
     when(pluginConfigFactoryMock.getGlobalPluginConfig(PLUGIN_NAME)).thenReturn(globalPluginConfig);
   }
@@ -80,75 +60,6 @@
   }
 
   @Test
-  public void testGetUrls() throws Exception {
-    assertThat(getConfiguration().peerInfoStatic().urls()).isEmpty();
-
-    globalPluginConfig.setStringList(PEER_INFO_SECTION, STATIC_SUBSECTION, URL_KEY, URLS);
-    assertThat(getConfiguration().peerInfoStatic().urls())
-        .containsAllIn(ImmutableList.of(URL, "http://anotherUrl"));
-  }
-
-  @Test
-  public void testGetUser() throws Exception {
-    assertThat(getConfiguration().http().user()).isEmpty();
-
-    globalPluginConfig.setString(HTTP_SECTION, null, USER_KEY, USER);
-    assertThat(getConfiguration().http().user()).isEqualTo(USER);
-  }
-
-  @Test
-  public void testGetPassword() throws Exception {
-    assertThat(getConfiguration().http().password()).isEmpty();
-
-    globalPluginConfig.setString(HTTP_SECTION, null, PASSWORD_KEY, PASS);
-    assertThat(getConfiguration().http().password()).isEqualTo(PASS);
-  }
-
-  @Test
-  public void testGetConnectionTimeout() throws Exception {
-    assertThat(getConfiguration().http().connectionTimeout()).isEqualTo(DEFAULT_TIMEOUT_MS);
-
-    globalPluginConfig.setInt(HTTP_SECTION, null, CONNECTION_TIMEOUT_KEY, TIMEOUT);
-    assertThat(getConfiguration().http().connectionTimeout()).isEqualTo(TIMEOUT);
-
-    globalPluginConfig.setString(HTTP_SECTION, null, CONNECTION_TIMEOUT_KEY, INVALID_INT);
-    assertThat(getConfiguration().http().connectionTimeout()).isEqualTo(DEFAULT_TIMEOUT_MS);
-  }
-
-  @Test
-  public void testGetSocketTimeout() throws Exception {
-    assertThat(getConfiguration().http().socketTimeout()).isEqualTo(DEFAULT_TIMEOUT_MS);
-
-    globalPluginConfig.setInt(HTTP_SECTION, null, SOCKET_TIMEOUT_KEY, TIMEOUT);
-    assertThat(getConfiguration().http().socketTimeout()).isEqualTo(TIMEOUT);
-
-    globalPluginConfig.setString(HTTP_SECTION, null, SOCKET_TIMEOUT_KEY, INVALID_INT);
-    assertThat(getConfiguration().http().socketTimeout()).isEqualTo(DEFAULT_TIMEOUT_MS);
-  }
-
-  @Test
-  public void testGetMaxTries() throws Exception {
-    assertThat(getConfiguration().http().maxTries()).isEqualTo(DEFAULT_MAX_TRIES);
-
-    globalPluginConfig.setInt(HTTP_SECTION, null, MAX_TRIES_KEY, MAX_TRIES);
-    assertThat(getConfiguration().http().maxTries()).isEqualTo(MAX_TRIES);
-
-    globalPluginConfig.setString(HTTP_SECTION, null, MAX_TRIES_KEY, INVALID_INT);
-    assertThat(getConfiguration().http().maxTries()).isEqualTo(DEFAULT_MAX_TRIES);
-  }
-
-  @Test
-  public void testGetRetryInterval() throws Exception {
-    assertThat(getConfiguration().http().retryInterval()).isEqualTo(DEFAULT_RETRY_INTERVAL);
-
-    globalPluginConfig.setInt(HTTP_SECTION, null, RETRY_INTERVAL_KEY, RETRY_INTERVAL);
-    assertThat(getConfiguration().http().retryInterval()).isEqualTo(RETRY_INTERVAL);
-
-    globalPluginConfig.setString(HTTP_SECTION, null, RETRY_INTERVAL_KEY, INVALID_INT);
-    assertThat(getConfiguration().http().retryInterval()).isEqualTo(DEFAULT_RETRY_INTERVAL);
-  }
-
-  @Test
   public void testGetIndexThreadPoolSize() throws Exception {
     assertThat(getConfiguration().index().threadPoolSize()).isEqualTo(DEFAULT_THREAD_POOL_SIZE);
 
@@ -222,13 +133,80 @@
   }
 
   @Test
-  public void testHealthCheckEnabled() throws Exception {
-    assertThat(getConfiguration().healthCheck().enabled()).isEqualTo(DEFAULT_HEALTH_CHECK_ENABLED);
+  public void kafkaSubscriberPropertiesAreSetWhenSectionIsEnabled() {
+    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
+    final String kafkaPropertyValue = "aValue";
+    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, true);
+    globalPluginConfig.setString(
+        KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
 
-    globalPluginConfig.setBoolean(HEALTH_CHECK_SECTION, null, ENABLE_KEY, false);
-    assertThat(getConfiguration().healthCheck().enabled()).isFalse();
+    final String property = getConfiguration().kafkaSubscriber().getProperty("foo.bar.baz");
 
-    globalPluginConfig.setBoolean(HEALTH_CHECK_SECTION, null, ENABLE_KEY, true);
-    assertThat(getConfiguration().healthCheck().enabled()).isTrue();
+    assertThat(property.equals(kafkaPropertyValue)).isTrue();
+  }
+
+  @Test
+  public void kafkaSubscriberPropertiesAreNotSetWhenSectionIsDisabled() {
+    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
+    final String kafkaPropertyValue = "aValue";
+    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, false);
+    globalPluginConfig.setString(
+        KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
+
+    final String property = getConfiguration().kafkaSubscriber().getProperty("foo.bar.baz");
+
+    assertThat(property).isNull();
+  }
+
+  @Test
+  public void kafkaSubscriberPropertiesAreIgnoredWhenPrefixIsNotSet() {
+    final String kafkaPropertyName = "fooBarBaz";
+    final String kafkaPropertyValue = "aValue";
+    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, true);
+    globalPluginConfig.setString(
+        KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
+
+    final String property = getConfiguration().kafkaSubscriber().getProperty("foo.bar.baz");
+
+    assertThat(property).isNull();
+  }
+
+  @Test
+  public void kafkaPublisherPropertiesAreSetWhenSectionIsEnabled() {
+    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
+    final String kafkaPropertyValue = "aValue";
+    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, true);
+    globalPluginConfig.setString(
+        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
+
+    final String property = getConfiguration().kafkaPublisher().getProperty("foo.bar.baz");
+
+    assertThat(property.equals(kafkaPropertyValue)).isTrue();
+  }
+
+  @Test
+  public void kafkaPublisherPropertiesAreIgnoredWhenPrefixIsNotSet() {
+    final String kafkaPropertyName = "fooBarBaz";
+    final String kafkaPropertyValue = "aValue";
+    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, true);
+    globalPluginConfig.setString(
+        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
+
+    final String property = getConfiguration().kafkaPublisher().getProperty("foo.bar.baz");
+
+    assertThat(property).isNull();
+  }
+
+  @Test
+  public void kafkaPublisherPropertiesAreNotSetWhenSectionIsDisabled() {
+    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
+    final String kafkaPropertyValue = "aValue";
+    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, false);
+    globalPluginConfig.setString(
+        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
+
+    final String property = getConfiguration().kafkaPublisher().getProperty("foo.bar.baz");
+
+    assertThat(property).isNull();
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisherTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisherTest.java
new file mode 100644
index 0000000..329774e
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisherTest.java
@@ -0,0 +1,158 @@
+// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.broker.kafka;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.extensions.client.ChangeKind;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ApprovalAttribute;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.googlesource.gerrit.plugins.multisite.broker.BrokerPublisher;
+import com.googlesource.gerrit.plugins.multisite.broker.BrokerSession;
+import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventFamily;
+import java.util.UUID;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BrokerPublisherTest {
+  private BrokerPublisher publisher;
+  private Gson gson = new GsonProvider().get();
+
+  @Before
+  public void setUp() {
+    publisher = new BrokerPublisher(new TestBrokerSession(), gson, UUID.randomUUID());
+  }
+
+  @Test
+  public void shouldSerializeCommentAddedEvent() {
+
+    final String accountName = "Foo Bar";
+    final String accountEmail = "foo@bar.com";
+    final String accountUsername = "foobar";
+    final String approvalType = ChangeKind.REWORK.toString();
+
+    final String approvalDescription = "ApprovalDescription";
+    final String approvalValue = "+2";
+    final String oldApprovalValue = "+1";
+    final Long approvalGrantedOn = 123L;
+    final String commentDescription = "Patch Set 1: Code-Review+2";
+    final String projectName = "project";
+    final String refName = "refs/heads/master";
+    final String changeId = "Iabcd1234abcd1234abcd1234abcd1234abcd1234";
+    final Long eventCreatedOn = 123L;
+
+    final Change change =
+        new Change(
+            new Change.Key(changeId),
+            new Change.Id(1),
+            new Account.Id(1),
+            new Branch.NameKey(projectName, refName),
+            TimeUtil.nowTs());
+
+    CommentAddedEvent event = new CommentAddedEvent(change);
+    AccountAttribute accountAttribute = new AccountAttribute();
+    accountAttribute.email = accountEmail;
+    accountAttribute.name = accountName;
+    accountAttribute.username = accountUsername;
+
+    event.eventCreatedOn = eventCreatedOn;
+    event.approvals =
+        () -> {
+          ApprovalAttribute approvalAttribute = new ApprovalAttribute();
+          approvalAttribute.value = approvalValue;
+          approvalAttribute.oldValue = oldApprovalValue;
+          approvalAttribute.description = approvalDescription;
+          approvalAttribute.by = accountAttribute;
+          approvalAttribute.type = ChangeKind.REWORK.toString();
+          approvalAttribute.grantedOn = approvalGrantedOn;
+
+          return new ApprovalAttribute[] {approvalAttribute};
+        };
+
+    event.author = () -> accountAttribute;
+    event.comment = commentDescription;
+
+    String expectedSerializedCommentEvent =
+        "{\"author\": {\"name\": \""
+            + accountName
+            + "\",\"email\": \""
+            + accountEmail
+            + "\",\"username\": \""
+            + accountUsername
+            + "\"},\"approvals\": [{\"type\": \""
+            + approvalType
+            + "\",\"description\": \""
+            + approvalDescription
+            + "\",\"value\": \""
+            + approvalValue
+            + "\",\"oldValue\": \""
+            + oldApprovalValue
+            + "\",\"grantedOn\": "
+            + approvalGrantedOn
+            + ",\"by\": {\"name\": \""
+            + accountName
+            + "\",\"email\": \""
+            + accountEmail
+            + "\",\"username\": \""
+            + accountUsername
+            + "\"}}],\"comment\": \""
+            + commentDescription
+            + "\",\""
+            + projectName
+            + "\": {\"name\": \""
+            + projectName
+            + "\"},\"refName\": \""
+            + refName
+            + "\",\"changeKey\": {\"id\": \""
+            + changeId
+            + "\""
+            + "  },\"type\": \"comment-added\",\"eventCreatedOn\": "
+            + eventCreatedOn
+            + "}";
+
+    JsonObject expectedCommentEventJsonObject =
+        gson.fromJson(expectedSerializedCommentEvent, JsonElement.class).getAsJsonObject();
+
+    assertThat(publisher.eventToJson(event).equals(expectedCommentEventJsonObject)).isTrue();
+  }
+
+  private class TestBrokerSession implements BrokerSession {
+
+    @Override
+    public boolean isOpen() {
+      return false;
+    }
+
+    @Override
+    public void connect() {}
+
+    @Override
+    public void disconnect() {}
+
+    @Override
+    public boolean publishEvent(EventFamily eventFamily, String payload) {
+      return false;
+    }
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionIT.java
deleted file mode 100644
index 83ec0ff..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionIT.java
+++ /dev/null
@@ -1,78 +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 com.googlesource.gerrit.plugins.multisite.cache;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.any;
-import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
-import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
-import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
-import static com.github.tomakehurst.wiremock.client.WireMock.verify;
-import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
-import static com.google.common.truth.Truth.assertThat;
-
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.google.gerrit.acceptance.GlobalPluginConfig;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.acceptance.UseLocalDisk;
-import com.google.gerrit.acceptance.UseSsh;
-import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.HttpStatus;
-import org.junit.Rule;
-import org.junit.Test;
-
-@NoHttpd
-@UseSsh
-@TestPlugin(
-    name = "multi-site",
-    sysModule = "com.googlesource.gerrit.plugins.multisite.Module",
-    httpModule = "com.googlesource.gerrit.plugins.multisite.HttpModule")
-public class CacheEvictionIT extends LightweightPluginDaemonTest {
-  private static final int PORT = 18888;
-  private static final String URL = "http://localhost:" + PORT;
-
-  @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
-
-  @Override
-  public void setUpTestPlugin() throws Exception {
-    givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
-    super.setUpTestPlugin();
-  }
-
-  @Test
-  @UseLocalDisk
-  @GlobalPluginConfig(pluginName = "multi-site", name = "peerInfo.static.url", value = URL)
-  @GlobalPluginConfig(pluginName = "multi-site", name = "http.retryInterval", value = "100")
-  public void flushAndSendPost() throws Exception {
-    final String flushRequest = "/plugins/multi-site/cache/" + Constants.PROJECTS;
-    final CountDownLatch expectedRequestLatch = new CountDownLatch(1);
-    wireMockRule.addMockServiceRequestListener(
-        (request, response) -> {
-          if (request.getAbsoluteUrl().contains(flushRequest)) {
-            expectedRequestLatch.countDown();
-          }
-        });
-
-    adminSshSession.exec("gerrit flush-caches --cache " + Constants.PROJECTS);
-    assertThat(expectedRequestLatch.await(5, TimeUnit.SECONDS)).isTrue();
-    verify(postRequestedFor(urlEqualTo(flushRequest)));
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListIT.java
deleted file mode 100644
index 9908618..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListIT.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.cache;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.any;
-import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
-import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
-import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
-import static com.github.tomakehurst.wiremock.client.WireMock.verify;
-import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
-import static com.google.common.truth.Truth.assertThat;
-
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.google.gerrit.acceptance.GlobalPluginConfig;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.acceptance.UseLocalDisk;
-import com.google.gerrit.extensions.restapi.Url;
-import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.HttpStatus;
-import org.junit.Rule;
-import org.junit.Test;
-
-@NoHttpd
-@TestPlugin(
-    name = "multi-site",
-    sysModule = "com.googlesource.gerrit.plugins.multisite.Module",
-    httpModule = "com.googlesource.gerrit.plugins.multisite.HttpModule")
-public class ProjectListIT extends LightweightPluginDaemonTest {
-  private static final int PORT = 18888;
-  private static final String URL = "http://localhost:" + PORT;
-
-  @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
-
-  @Override
-  public void setUpTestPlugin() throws Exception {
-    givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
-    super.setUpTestPlugin();
-  }
-
-  @Test
-  @UseLocalDisk
-  @GlobalPluginConfig(pluginName = "multi-site", name = "peerInfo.static.url", value = URL)
-  @GlobalPluginConfig(pluginName = "multi-site", name = "http.retryInterval", value = "100")
-  public void addToProjectListAreForwarded() throws Exception {
-    String createdProjectEncoded = Url.encode("org-a/some-project");
-    String expectedRequest =
-        "/plugins/multi-site/cache/" + Constants.PROJECT_LIST + "/" + createdProjectEncoded;
-    CountDownLatch expectedRequestLatch = new CountDownLatch(1);
-    wireMockRule.addMockServiceRequestListener(
-        (request, response) -> {
-          if (request.getAbsoluteUrl().contains(expectedRequest)) {
-            expectedRequestLatch.countDown();
-          }
-        });
-
-    adminRestSession.put("/projects/" + createdProjectEncoded).assertCreated();
-    assertThat(expectedRequestLatch.await(5, TimeUnit.SECONDS)).isTrue();
-    verify(postRequestedFor(urlEqualTo(expectedRequest)));
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
index 8516ce4..3b01c61 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
@@ -22,10 +22,8 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
+import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gwtorm.server.OrmException;
-import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -40,11 +38,12 @@
 
   @Rule public ExpectedException exception = ExpectedException.none();
   @Mock private EventDispatcher dispatcherMock;
+  @Mock OneOffRequestContext oneOffCtxMock;
   private ForwardedEventHandler handler;
 
   @Before
   public void setUp() throws Exception {
-    handler = new ForwardedEventHandler(dispatcherMock);
+    handler = new ForwardedEventHandler(dispatcherMock, oneOffCtxMock);
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandlerTest.java
index ad2a770..68edd88 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandlerTest.java
@@ -22,11 +22,10 @@
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexGroupHandler;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-
 import java.io.IOException;
 import java.util.Optional;
 import org.junit.Before;
@@ -102,6 +101,8 @@
 
     assertThat(Context.isForwardedEvent()).isFalse();
     try {
+      // Had to put this here to avoid a NPE during the index call
+      KeyUtil.setEncoderImpl(new StandardKeyEncoder());
       handler.index(uuid, Operation.INDEX, Optional.empty());
       fail("should have thrown an IOException");
     } catch (IOException e) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/GsonParserTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParserTest.java
similarity index 93%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/GsonParserTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParserTest.java
index df6d618..0a1be19 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/GsonParserTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/GsonParserTest.java
@@ -12,15 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.forwarder.rest;
+package com.googlesource.gerrit.plugins.multisite.forwarder;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.GsonParser;
-
 import org.junit.Test;
 
 public class GsonParserTest {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/CacheRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/CacheRestApiServletTest.java
deleted file mode 100644
index 0c0e05c..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/CacheRestApiServletTest.java
+++ /dev/null
@@ -1,140 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheNotFoundException;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedCacheEvictionHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.CacheRestApiServlet;
-
-@RunWith(MockitoJUnitRunner.class)
-public class CacheRestApiServletTest {
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-  @Mock private BufferedReader readerMock;
-  @Mock private ForwardedCacheEvictionHandler forwardedCacheEvictionHandlerMock;
-  private CacheRestApiServlet servlet;
-
-  @Before
-  public void setUp() {
-    servlet = new CacheRestApiServlet(forwardedCacheEvictionHandlerMock);
-  }
-
-  @Test
-  public void evictAccounts() throws Exception {
-    configureMocksFor(Constants.ACCOUNTS);
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void evictProjectList() throws Exception {
-    configureMocksFor(Constants.PROJECT_LIST);
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void evictGroups() throws Exception {
-    configureMocksFor(Constants.GROUPS);
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void evictGroupsByInclude() throws Exception {
-    configureMocksFor(Constants.GROUPS_BYINCLUDE);
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void evictGroupsMembers() throws Exception {
-    configureMocksFor(Constants.GROUPS_MEMBERS);
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void evictPluginCache() throws Exception {
-    configureMocksFor("my-plugin", "my-cache");
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void evictDefault() throws Exception {
-    configureMocksFor(Constants.PROJECTS);
-    verifyResponseIsOK();
-  }
-
-  @Test
-  public void badRequest() throws Exception {
-    when(requestMock.getPathInfo()).thenReturn("/someCache");
-    String errorMessage = "someError";
-    doThrow(new IOException(errorMessage)).when(requestMock).getReader();
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_BAD_REQUEST, errorMessage);
-  }
-
-  @Test
-  public void badRequestCausedByCacheNotFound() throws Exception {
-    String pluginName = "somePlugin";
-    String cacheName = "nonexistingCache";
-    configureMocksFor(pluginName, cacheName);
-    CacheNotFoundException e = new CacheNotFoundException(pluginName, cacheName);
-    doThrow(e).when(forwardedCacheEvictionHandlerMock).evict(any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_BAD_REQUEST, e.getMessage());
-  }
-
-  private void verifyResponseIsOK() throws Exception {
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  private void configureMocksFor(String cacheName) throws Exception {
-    configureMocksFor(Constants.GERRIT, cacheName);
-  }
-
-  private void configureMocksFor(String pluginName, String cacheName) throws Exception {
-    if (Constants.GERRIT.equals(pluginName)) {
-      when(requestMock.getPathInfo()).thenReturn("/" + cacheName);
-    } else {
-      when(requestMock.getPathInfo()).thenReturn("/" + pluginName + "." + cacheName);
-    }
-    when(requestMock.getReader()).thenReturn(readerMock);
-
-    if (Constants.PROJECTS.equals(cacheName)) {
-      when(readerMock.readLine()).thenReturn("abc");
-    } else if (Constants.GROUPS_BYINCLUDE.equals(cacheName)
-        || Constants.GROUPS_MEMBERS.equals(cacheName)) {
-      when(readerMock.readLine()).thenReturn("{\"uuid\":\"abcd1234\"}");
-    } else {
-      when(readerMock.readLine()).thenReturn("{}");
-    }
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/EventRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/EventRestApiServletTest.java
deleted file mode 100644
index a940c23..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/EventRestApiServletTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (C) 2016 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.net.MediaType.JSON_UTF_8;
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.common.net.MediaType;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.events.EventTypes;
-import com.google.gerrit.server.events.RefEvent;
-import com.google.gwtorm.server.OrmException;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.EventRestApiServlet;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class EventRestApiServletTest {
-  private static final String ERR_MSG = "some Error";
-
-  @Mock private ForwardedEventHandler forwardedEventHandlerMock;
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-  private EventRestApiServlet eventRestApiServlet;
-
-  @BeforeClass
-  public static void setup() {
-    EventTypes.register(RefReplicationDoneEvent.TYPE, RefReplicationDoneEvent.class);
-  }
-
-  @Before
-  public void createEventsRestApiServlet() throws Exception {
-    eventRestApiServlet = new EventRestApiServlet(forwardedEventHandlerMock);
-    when(requestMock.getContentType()).thenReturn(MediaType.JSON_UTF_8.toString());
-  }
-
-  @Test
-  public void testDoPostRefReplicationDoneEvent() throws Exception {
-    String event =
-        "{\"project\":\"gerrit/some-project\",\"ref\":"
-            + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":"
-            + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}";
-    when(requestMock.getReader()).thenReturn(new BufferedReader(new StringReader(event)));
-
-    eventRestApiServlet.doPost(requestMock, responseMock);
-
-    verify(forwardedEventHandlerMock).dispatch(any(RefReplicationDoneEvent.class));
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void testDoPostDispatcherFailure() throws Exception {
-    String event =
-        "{\"project\":\"gerrit/some-project\",\"ref\":"
-            + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":"
-            + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}";
-    when(requestMock.getReader()).thenReturn(new BufferedReader(new StringReader(event)));
-    doThrow(new OrmException(ERR_MSG))
-        .when(forwardedEventHandlerMock)
-        .dispatch(any(RefReplicationDoneEvent.class));
-    eventRestApiServlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_NOT_FOUND, "Change not found\n");
-  }
-
-  @Test
-  public void testDoPostBadRequest() throws Exception {
-    doThrow(new IOException(ERR_MSG)).when(requestMock).getReader();
-    eventRestApiServlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_BAD_REQUEST, ERR_MSG);
-  }
-
-  @Test
-  public void testDoPostWrongMediaType() throws Exception {
-    when(requestMock.getContentType()).thenReturn(MediaType.APPLICATION_XML_UTF_8.toString());
-    eventRestApiServlet.doPost(requestMock, responseMock);
-    verify(responseMock)
-        .sendError(
-            SC_UNSUPPORTED_MEDIA_TYPE, "Expecting " + JSON_UTF_8.toString() + " content type");
-  }
-
-  @Test
-  public void testDoPostErrorWhileSendingErrorMessage() throws Exception {
-    doThrow(new IOException(ERR_MSG)).when(requestMock).getReader();
-    doThrow(new IOException("someOtherError"))
-        .when(responseMock)
-        .sendError(SC_BAD_REQUEST, ERR_MSG);
-    eventRestApiServlet.doPost(requestMock, responseMock);
-  }
-
-  static class RefReplicationDoneEvent extends RefEvent {
-    public static final String TYPE = "ref-replication-done";
-    public final String project;
-    public final String ref;
-    public final int nodesCount;
-
-    public RefReplicationDoneEvent(String project, String ref, int nodesCount) {
-      super(TYPE);
-      this.project = project;
-      this.ref = ref;
-      this.nodesCount = nodesCount;
-    }
-
-    @Override
-    public Project.NameKey getProjectNameKey() {
-      return new Project.NameKey(project);
-    }
-
-    @Override
-    public String getRefName() {
-      return ref;
-    }
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpClientProviderTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpClientProviderTest.java
deleted file mode 100644
index a326a3b..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpClientProviderTest.java
+++ /dev/null
@@ -1,69 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
-
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Scopes;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpClientProvider;
-
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class HttpClientProviderTest {
-  private static final int TIME_INTERVAL = 1000;
-  private static final String EMPTY = "";
-
-  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-  private Configuration configMock;
-
-  @Before
-  public void setUp() throws Exception {
-    when(configMock.http().user()).thenReturn(EMPTY);
-    when(configMock.http().password()).thenReturn(EMPTY);
-    when(configMock.http().connectionTimeout()).thenReturn(TIME_INTERVAL);
-    when(configMock.http().socketTimeout()).thenReturn(TIME_INTERVAL);
-  }
-
-  @Test
-  public void testGet() throws Exception {
-    Injector injector = Guice.createInjector(new TestModule());
-    try (CloseableHttpClient httpClient1 = injector.getInstance(CloseableHttpClient.class)) {
-      assertThat(httpClient1).isNotNull();
-      try (CloseableHttpClient httpClient2 = injector.getInstance(CloseableHttpClient.class)) {
-        assertThat(httpClient1).isEqualTo(httpClient2);
-      }
-    }
-  }
-
-  class TestModule extends AbstractModule {
-    @Override
-    protected void configure() {
-      bind(Configuration.class).toInstance(configMock);
-      bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class).in(Scopes.SINGLETON);
-    }
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpResponseHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpResponseHandlerTest.java
deleted file mode 100644
index 6e1d741..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpResponseHandlerTest.java
+++ /dev/null
@@ -1,69 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.UnsupportedEncodingException;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.entity.StringEntity;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-
-public class HttpResponseHandlerTest {
-  private static final int ERROR = 400;
-  private static final int NO_CONTENT = 204;
-  private static final String EMPTY_ENTITY = "";
-  private static final String ERROR_ENTITY = "Error";
-
-  private HttpResponseHandler handler;
-
-  @Before
-  public void setUp() throws Exception {
-    handler = new HttpResponseHandler();
-  }
-
-  @Test
-  public void testIsSuccessful() throws Exception {
-    HttpResponse response = setupMocks(NO_CONTENT, EMPTY_ENTITY);
-    HttpResult result = handler.handleResponse(response);
-    assertThat(result.isSuccessful()).isTrue();
-    assertThat(result.getMessage()).isEmpty();
-  }
-
-  @Test
-  public void testIsNotSuccessful() throws Exception {
-    HttpResponse response = setupMocks(ERROR, ERROR_ENTITY);
-    HttpResult result = handler.handleResponse(response);
-    assertThat(result.isSuccessful()).isFalse();
-    assertThat(result.getMessage()).contains(ERROR_ENTITY);
-  }
-
-  private static HttpResponse setupMocks(int httpCode, String entity)
-      throws UnsupportedEncodingException {
-    StatusLine status = mock(StatusLine.class);
-    when(status.getStatusCode()).thenReturn(httpCode);
-    HttpResponse response = mock(HttpResponse.class);
-    when(response.getStatusLine()).thenReturn(status);
-    when(response.getEntity()).thenReturn(new StringEntity(entity));
-    return response;
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpSessionTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpSessionTest.java
deleted file mode 100644
index 6381f8b..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/HttpSessionTest.java
+++ /dev/null
@@ -1,177 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.delete;
-import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
-import static com.github.tomakehurst.wiremock.client.WireMock.post;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.github.tomakehurst.wiremock.http.Fault;
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.github.tomakehurst.wiremock.stubbing.Scenario;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpClientProvider;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpSession;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-
-import java.net.SocketTimeoutException;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Answers;
-
-public class HttpSessionTest {
-
-  private static final int MAX_TRIES = 3;
-  private static final int RETRY_INTERVAL = 250;
-  private static final int TIMEOUT = 500;
-  private static final int ERROR = 500;
-  private static final int NO_CONTENT = 204;
-  private static final int NOT_FOUND = 404;
-  private static final int UNAUTHORIZED = 401;
-
-  private static final String ENDPOINT = "/plugins/multi-site/index/1";
-  private static final String BODY = "SerializedEvent";
-  private static final String ERROR_MESSAGE = "Error message";
-  private static final String REQUEST_MADE = "Request made";
-  private static final String SECOND_TRY = "Second try";
-  private static final String THIRD_TRY = "Third try";
-  private static final String RETRY_AT_DELAY = "Retry at delay";
-
-  private HttpSession httpSession;
-
-  @Rule public WireMockRule wireMockRule = new WireMockRule(0);
-
-  private Configuration configMock;
-  private String uri;
-
-  @Before
-  public void setUp() throws Exception {
-    String url = "http://localhost:" + wireMockRule.port();
-    uri = url + ENDPOINT;
-    configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
-    when(configMock.http().user()).thenReturn("user");
-    when(configMock.http().password()).thenReturn("pass");
-    when(configMock.http().maxTries()).thenReturn(MAX_TRIES);
-    when(configMock.http().connectionTimeout()).thenReturn(TIMEOUT);
-    when(configMock.http().socketTimeout()).thenReturn(TIMEOUT);
-    when(configMock.http().retryInterval()).thenReturn(RETRY_INTERVAL);
-
-    httpSession = new HttpSession(new HttpClientProvider(configMock).get());
-  }
-
-  @Test
-  public void testPostResponseOK() throws Exception {
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(NO_CONTENT)));
-
-    assertThat(httpSession.post(uri).isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testPostResponseWithContentOK() throws Exception {
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .withRequestBody(equalTo(BODY))
-            .willReturn(aResponse().withStatus(NO_CONTENT)));
-    assertThat(httpSession.post(uri, BODY).isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testDeleteResponseOK() throws Exception {
-    wireMockRule.givenThat(
-        delete(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(NO_CONTENT)));
-
-    assertThat(httpSession.delete(uri).isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testNotAuthorized() throws Exception {
-    String expected = "unauthorized";
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .willReturn(aResponse().withStatus(UNAUTHORIZED).withBody(expected)));
-
-    HttpResult result = httpSession.post(uri);
-    assertThat(result.isSuccessful()).isFalse();
-    assertThat(result.getMessage()).isEqualTo(expected);
-  }
-
-  @Test
-  public void testNotFound() throws Exception {
-    String expected = "not found";
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .willReturn(aResponse().withStatus(NOT_FOUND).withBody(expected)));
-
-    HttpResult result = httpSession.post(uri);
-    assertThat(result.isSuccessful()).isFalse();
-    assertThat(result.getMessage()).isEqualTo(expected);
-  }
-
-  @Test
-  public void testBadResponseRetryThenGiveUp() throws Exception {
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .willReturn(aResponse().withStatus(ERROR).withBody(ERROR_MESSAGE)));
-
-    HttpResult result = httpSession.post(uri);
-    assertThat(result.isSuccessful()).isFalse();
-    assertThat(result.getMessage()).isEqualTo(ERROR_MESSAGE);
-  }
-
-  @Test(expected = SocketTimeoutException.class)
-  public void testMaxRetriesAfterTimeoutThenGiveUp() throws Exception {
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .inScenario(RETRY_AT_DELAY)
-            .whenScenarioStateIs(Scenario.STARTED)
-            .willSetStateTo(REQUEST_MADE)
-            .willReturn(aResponse().withFixedDelay(TIMEOUT)));
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .inScenario(RETRY_AT_DELAY)
-            .whenScenarioStateIs(REQUEST_MADE)
-            .willSetStateTo(SECOND_TRY)
-            .willReturn(aResponse().withFixedDelay(TIMEOUT)));
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .inScenario(RETRY_AT_DELAY)
-            .whenScenarioStateIs(SECOND_TRY)
-            .willSetStateTo(THIRD_TRY)
-            .willReturn(aResponse().withFixedDelay(TIMEOUT)));
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .inScenario(RETRY_AT_DELAY)
-            .whenScenarioStateIs(THIRD_TRY)
-            .willReturn(aResponse().withFixedDelay(TIMEOUT)));
-
-    httpSession.post(uri);
-  }
-
-  @Test
-  public void testResponseWithMalformedResponse() throws Exception {
-    wireMockRule.givenThat(
-        post(urlEqualTo(ENDPOINT))
-            .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
-
-    assertThat(httpSession.post(uri).isSuccessful()).isFalse();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexAccountRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexAccountRestApiServletTest.java
deleted file mode 100644
index 070b62c..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexAccountRestApiServletTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexAccountHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.IndexAccountRestApiServlet;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IndexAccountRestApiServletTest {
-  private static final int ACCOUNT_NUMBER = 1;
-  private static final String IO_ERROR = "io-error";
-
-  @Mock private ForwardedIndexAccountHandler handlerMock;
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-
-  private Account.Id id;
-  private IndexAccountRestApiServlet servlet;
-
-  @Before
-  public void setUpMocks() {
-    servlet = new IndexAccountRestApiServlet(handlerMock);
-    id = new Account.Id(ACCOUNT_NUMBER);
-    when(requestMock.getRequestURI())
-        .thenReturn("http://gerrit.com/index/account/" + ACCOUNT_NUMBER);
-  }
-
-  @Test
-  public void accountIsIndexed() throws Exception {
-    servlet.doPost(requestMock, responseMock);
-    verify(handlerMock, times(1)).index(eq(id), eq(Operation.INDEX), any());
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void cannotDeleteAccount() throws Exception {
-    servlet.doDelete(requestMock, responseMock);
-    verify(responseMock).sendError(SC_METHOD_NOT_ALLOWED, "cannot delete account from index");
-  }
-
-  @Test
-  public void indexerThrowsIOExceptionTryingToIndexAccount() throws Exception {
-    doThrow(new IOException(IO_ERROR)).when(handlerMock).index(eq(id), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-
-  @Test
-  public void sendErrorThrowsIOException() throws Exception {
-    doThrow(new IOException(IO_ERROR)).when(handlerMock).index(eq(id), eq(Operation.INDEX), any());
-    doThrow(new IOException("someError")).when(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexChangeRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexChangeRestApiServletTest.java
deleted file mode 100644
index da001a9..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexChangeRestApiServletTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (C) 2016 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gwtorm.server.OrmException;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexChangeHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.IndexChangeRestApiServlet;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IndexChangeRestApiServletTest {
-  private static final int CHANGE_NUMBER = 1;
-  private static final String PROJECT_NAME = "test/project";
-  private static final String PROJECT_NAME_URL_ENC = "test%2Fproject";
-  private static final String CHANGE_ID = PROJECT_NAME + "~" + CHANGE_NUMBER;
-  private static final String IO_ERROR = "io-error";
-
-  @Mock private ForwardedIndexChangeHandler handlerMock;
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-
-  private IndexChangeRestApiServlet servlet;
-
-  @Before
-  public void setUpMocks() {
-    servlet = new IndexChangeRestApiServlet(handlerMock);
-    when(requestMock.getRequestURI())
-        .thenReturn("http://gerrit.com/index/change/" + PROJECT_NAME_URL_ENC + "~" + CHANGE_NUMBER);
-  }
-
-  @Test
-  public void changeIsIndexed() throws Exception {
-    servlet.doPost(requestMock, responseMock);
-    verify(handlerMock, times(1)).index(eq(CHANGE_ID), eq(Operation.INDEX), any());
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void changeIsDeletedFromIndex() throws Exception {
-    servlet.doDelete(requestMock, responseMock);
-    verify(handlerMock, times(1)).index(eq(CHANGE_ID), eq(Operation.DELETE), any());
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void indexerThrowsIOExceptionTryingToIndexChange() throws Exception {
-    doThrow(new IOException(IO_ERROR))
-        .when(handlerMock)
-        .index(eq(CHANGE_ID), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-
-  @Test
-  public void indexerThrowsOrmExceptionTryingToIndexChange() throws Exception {
-    doThrow(new OrmException("some message"))
-        .when(handlerMock)
-        .index(eq(CHANGE_ID), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_NOT_FOUND, "Error trying to find change");
-  }
-
-  @Test
-  public void sendErrorThrowsIOException() throws Exception {
-    doThrow(new IOException(IO_ERROR))
-        .when(handlerMock)
-        .index(eq(CHANGE_ID), eq(Operation.INDEX), any());
-    doThrow(new IOException("someError")).when(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexGroupRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexGroupRestApiServletTest.java
deleted file mode 100644
index c3b7737..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexGroupRestApiServletTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (C) 2017 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexGroupHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.IndexGroupRestApiServlet;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IndexGroupRestApiServletTest {
-  private static final String IO_ERROR = "io-error";
-  private static final String UUID = "we235jdf92nfj2351";
-
-  @Mock private ForwardedIndexGroupHandler handlerMock;
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-
-  private AccountGroup.UUID uuid;
-  private IndexGroupRestApiServlet servlet;
-
-  @Before
-  public void setUpMocks() {
-    servlet = new IndexGroupRestApiServlet(handlerMock);
-    uuid = new AccountGroup.UUID(UUID);
-    when(requestMock.getRequestURI()).thenReturn("http://gerrit.com/index/group/" + UUID);
-  }
-
-  @Test
-  public void groupIsIndexed() throws Exception {
-    servlet.doPost(requestMock, responseMock);
-    verify(handlerMock, times(1)).index(eq(uuid), eq(Operation.INDEX), any());
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void cannotDeleteGroup() throws Exception {
-    servlet.doDelete(requestMock, responseMock);
-    verify(responseMock).sendError(SC_METHOD_NOT_ALLOWED, "cannot delete group from index");
-  }
-
-  @Test
-  public void indexerThrowsIOExceptionTryingToIndexGroup() throws Exception {
-    doThrow(new IOException(IO_ERROR))
-        .when(handlerMock)
-        .index(eq(uuid), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-
-  @Test
-  public void sendErrorThrowsIOException() throws Exception {
-    doThrow(new IOException(IO_ERROR))
-        .when(handlerMock)
-        .index(eq(uuid), eq(Operation.INDEX), any());
-    doThrow(new IOException("someError")).when(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexProjectRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexProjectRestApiServletTest.java
deleted file mode 100644
index 704a1f8..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/IndexProjectRestApiServletTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexProjectHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IndexProjectRestApiServletTest {
-  private static final String IO_ERROR = "io-error";
-  private static final String PROJECT_NAME = "test/project";
-
-  @Mock private ForwardedIndexProjectHandler handlerMock;
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-
-  private Project.NameKey nameKey;
-  private IndexProjectRestApiServlet servlet;
-
-  @Before
-  public void setUpMocks() {
-    servlet = new IndexProjectRestApiServlet(handlerMock);
-    nameKey = new Project.NameKey(PROJECT_NAME);
-    when(requestMock.getRequestURI())
-        .thenReturn("http://gerrit.com/index/project/" + Url.encode(nameKey.get()));
-  }
-
-  @Test
-  public void projectIsIndexed() throws Exception {
-    servlet.doPost(requestMock, responseMock);
-    verify(handlerMock, times(1)).index(eq(nameKey), eq(Operation.INDEX), any());
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void cannotDeleteProject() throws Exception {
-    servlet.doDelete(requestMock, responseMock);
-    verify(responseMock).sendError(SC_METHOD_NOT_ALLOWED, "cannot delete project from index");
-  }
-
-  @Test
-  public void indexerThrowsIOExceptionTryingToIndexProject() throws Exception {
-    doThrow(new IOException(IO_ERROR))
-        .when(handlerMock)
-        .index(eq(nameKey), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-
-  @Test
-  public void sendErrorThrowsIOException() throws Exception {
-    doThrow(new IOException(IO_ERROR))
-        .when(handlerMock)
-        .index(eq(nameKey), eq(Operation.INDEX), any());
-    doThrow(new IOException("someError")).when(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_CONFLICT, IO_ERROR);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListRestApiServletIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListRestApiServletIT.java
deleted file mode 100644
index 8b27837..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListRestApiServletIT.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.acceptance.UseLocalDisk;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
-import org.junit.Test;
-
-@NoHttpd
-@TestPlugin(
-    name = "multi-site",
-    sysModule = "com.googlesource.gerrit.plugins.multisite.Module",
-    httpModule = "com.googlesource.gerrit.plugins.multisite.HttpModule")
-public class ProjectListRestApiServletIT extends LightweightPluginDaemonTest {
-  private static final Project.NameKey SOME_PROJECT = new Project.NameKey("org-a/some-project");
-
-  @Test
-  @UseLocalDisk
-  public void addProject() throws Exception {
-
-    assertThat(projectCache.all()).doesNotContain(SOME_PROJECT);
-    adminRestSession
-        .post("/plugins/multi-site/cache/project_list/" + Url.encode(SOME_PROJECT.get()))
-        .assertNoContent();
-    assertThat(projectCache.all()).contains(SOME_PROJECT);
-  }
-
-  @Test
-  @UseLocalDisk
-  public void removeProject() throws Exception {
-    addProject();
-    adminRestSession
-        .delete("/plugins/multi-site/cache/project_list/" + Url.encode(SOME_PROJECT.get()))
-        .assertNoContent();
-    assertThat(projectCache.all()).doesNotContain(SOME_PROJECT);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListRestApiServletTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListRestApiServletTest.java
deleted file mode 100644
index 8d7ca0c..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/ProjectListRestApiServletTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.extensions.restapi.Url;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedProjectListUpdateHandler;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ProjectListRestApiServletTest {
-  private static final String PROJECT_NAME = "org-a/some-project";
-
-  @Mock private ForwardedProjectListUpdateHandler handlerMock;
-  @Mock private HttpServletRequest requestMock;
-  @Mock private HttpServletResponse responseMock;
-
-  private ProjectListApiServlet servlet;
-
-  @Before
-  public void setUpMocks() {
-    servlet = new ProjectListApiServlet(handlerMock);
-    when(requestMock.getRequestURI())
-        .thenReturn(
-            "http://hostname/plugins/multi-site/cache/project_list/" + Url.encode(PROJECT_NAME));
-  }
-
-  @Test
-  public void addProject() throws Exception {
-    servlet.doPost(requestMock, responseMock);
-    verify(handlerMock, times(1)).update(new ProjectListUpdateEvent(PROJECT_NAME, false));
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-
-  @Test
-  public void deleteProject() throws Exception {
-    servlet.doDelete(requestMock, responseMock);
-    verify(handlerMock, times(1)).update(new ProjectListUpdateEvent(PROJECT_NAME, true));
-    verify(responseMock).setStatus(SC_NO_CONTENT);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestCacheEvictionForwarderTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestCacheEvictionForwarderTest.java
deleted file mode 100644
index b019c5a..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestCacheEvictionForwarderTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gson.GsonBuilder;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.io.IOException;
-import java.util.Set;
-import javax.net.ssl.SSLException;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Answers;
-
-public class RestCacheEvictionForwarderTest {
-  private static final String URL = "http://fake.com";
-  private static final String PLUGIN_NAME = "multi-site";
-  private static final String EMPTY_MSG = "";
-  private static final String ERROR = "Error";
-  private static final String PLUGINS = "plugins";
-  private static final String SUCCESS = "Success";
-  private static final boolean SUCCESSFUL = true;
-  private static final boolean FAILED = false;
-
-  // Index
-  private static final String PROJECT_NAME = "test/project";
-
-  // Event
-  private CacheEvictionForwarder forwarder;
-  private HttpSession httpSessionMock;
-
-  @SuppressWarnings("unchecked")
-  @Before
-  public void setUp() {
-    httpSessionMock = mock(HttpSession.class);
-    Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
-    when(configMock.http().maxTries()).thenReturn(3);
-    when(configMock.http().retryInterval()).thenReturn(10);
-    Provider<Set<PeerInfo>> peersMock = mock(Provider.class);
-    when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL)));
-    forwarder =
-        new RestCacheEvictionForwarder(
-            httpSessionMock, PLUGIN_NAME, configMock, peersMock); // TODO: Create provider
-  }
-
-  @Test
-  public void testEvictProjectOK() throws Exception {
-    String key = PROJECT_NAME;
-    String keyJson = new GsonBuilder().create().toJson(key);
-    when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECTS, key))).isTrue();
-  }
-
-  @Test
-  public void testEvictAccountsOK() throws Exception {
-    Account.Id key = new Account.Id(123);
-    String keyJson = new GsonBuilder().create().toJson(key);
-    when(httpSessionMock.post(buildCacheEndpoint(Constants.ACCOUNTS), keyJson))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.ACCOUNTS, key))).isTrue();
-  }
-
-  @Test
-  public void testEvictGroupsOK() throws Exception {
-    AccountGroup.Id key = new AccountGroup.Id(123);
-    String keyJson = new GsonBuilder().create().toJson(key);
-    String endpoint = buildCacheEndpoint(Constants.GROUPS);
-    when(httpSessionMock.post(endpoint, keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.GROUPS, key))).isTrue();
-  }
-
-  @Test
-  public void testEvictGroupsByIncludeOK() throws Exception {
-    AccountGroup.UUID key = new AccountGroup.UUID("90b3042d9094a37985f3f9281391dbbe9a5addad");
-    String keyJson = new GsonBuilder().create().toJson(key);
-    when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE), keyJson))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.GROUPS_BYINCLUDE, key))).isTrue();
-  }
-
-  @Test
-  public void testEvictGroupsMembersOK() throws Exception {
-    AccountGroup.UUID key = new AccountGroup.UUID("90b3042d9094a37985f3f9281391dbbe9a5addad");
-    String keyJson = new GsonBuilder().create().toJson(key);
-    when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_MEMBERS), keyJson))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.GROUPS_MEMBERS, key))).isTrue();
-  }
-
-  @Test
-  public void testEvictCacheFailed() throws Exception {
-    String key = PROJECT_NAME;
-    String keyJson = new GsonBuilder().create().toJson(key);
-    when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECTS, key))).isFalse();
-  }
-
-  @Test
-  public void testEvictCacheThrowsException() throws Exception {
-    String key = PROJECT_NAME;
-    String keyJson = new GsonBuilder().create().toJson(key);
-    doThrow(new IOException())
-        .when(httpSessionMock)
-        .post(buildCacheEndpoint(Constants.PROJECTS), keyJson);
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECTS, key))).isFalse();
-  }
-
-  @Test
-  public void testRetryOnErrorThenSuccess() throws IOException {
-    when(httpSessionMock.post(anyString(), anyString()))
-        .thenReturn(new HttpResult(false, ERROR))
-        .thenReturn(new HttpResult(false, ERROR))
-        .thenReturn(new HttpResult(true, SUCCESS));
-
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECT_LIST, new Object())))
-        .isTrue();
-  }
-
-  @Test
-  public void testRetryOnIoExceptionThenSuccess() throws IOException {
-    when(httpSessionMock.post(anyString(), anyString()))
-        .thenThrow(new IOException())
-        .thenThrow(new IOException())
-        .thenReturn(new HttpResult(true, SUCCESS));
-
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECT_LIST, new Object())))
-        .isTrue();
-  }
-
-  @Test
-  public void testNoRetryAfterNonRecoverableException() throws IOException {
-    when(httpSessionMock.post(anyString(), anyString()))
-        .thenThrow(new SSLException("Non Recoverable"))
-        .thenReturn(new HttpResult(true, SUCCESS));
-
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECT_LIST, new Object())))
-        .isFalse();
-  }
-
-  @Test
-  public void testFailureAfterMaxTries() throws IOException {
-    when(httpSessionMock.post(anyString(), anyString()))
-        .thenReturn(new HttpResult(false, ERROR))
-        .thenReturn(new HttpResult(false, ERROR))
-        .thenReturn(new HttpResult(false, ERROR));
-
-    assertThat(forwarder.evict(new CacheEvictionEvent(Constants.PROJECT_LIST, new Object())))
-        .isFalse();
-  }
-
-  private static String buildCacheEndpoint(String name) {
-    return Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "cache", name);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestIndexEventForwarderTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestIndexEventForwarderTest.java
deleted file mode 100644
index 81ce6b3..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestIndexEventForwarderTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.AccountIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.GroupIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.io.IOException;
-import java.util.Set;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Answers;
-
-public class RestIndexEventForwarderTest {
-  private static final String URL = "http://fake.com";
-  private static final String PLUGIN_NAME = "multi-site";
-  private static final String EMPTY_MSG = "";
-  private static final String PLUGINS = "plugins";
-  private static final boolean SUCCESSFUL = true;
-  private static final boolean FAILED = false;
-
-  // Index
-  private static final int CHANGE_NUMBER = 1;
-  private static final String PROJECT_NAME = "test/project";
-  private static final String PROJECT_NAME_URL_END = "test%2Fproject";
-  private static final String INDEX_CHANGE_ENDPOINT =
-      Joiner.on("/")
-          .join(
-              URL,
-              PLUGINS,
-              PLUGIN_NAME,
-              "index/change",
-              PROJECT_NAME_URL_END + "~" + CHANGE_NUMBER);
-  private static final String DELETE_CHANGE_ENDPOINT =
-      Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "index/change", "~" + CHANGE_NUMBER);
-  private static final int ACCOUNT_NUMBER = 2;
-  private static final String INDEX_ACCOUNT_ENDPOINT =
-      Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "index/account", ACCOUNT_NUMBER);
-  private static final String UUID = "we235jdf92nfj2351";
-  private static final String INDEX_GROUP_ENDPOINT =
-      Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "index/group", UUID);
-
-  private IndexEventForwarder indexForwarder;
-  private HttpSession httpSessionMock;
-
-  @SuppressWarnings("unchecked")
-  @Before
-  public void setUp() {
-    httpSessionMock = mock(HttpSession.class);
-    Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
-    when(configMock.http().maxTries()).thenReturn(3);
-    when(configMock.http().retryInterval()).thenReturn(10);
-    Provider<Set<PeerInfo>> peersMock = mock(Provider.class);
-    when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL)));
-    indexForwarder =
-        new RestIndexEventForwarder(
-            httpSessionMock, PLUGIN_NAME, configMock, peersMock); // TODO: Create provider
-  }
-
-  @Test
-  public void testIndexAccountOK() throws Exception {
-    when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any()))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(indexForwarder.indexAccount(new AccountIndexEvent(ACCOUNT_NUMBER))).isTrue();
-  }
-
-  @Test
-  public void testIndexAccountFailed() throws Exception {
-    when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any()))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(indexForwarder.indexAccount(new AccountIndexEvent(ACCOUNT_NUMBER))).isFalse();
-  }
-
-  @Test
-  public void testIndexAccountThrowsException() throws Exception {
-    doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_ACCOUNT_ENDPOINT), any());
-    assertThat(indexForwarder.indexAccount(new AccountIndexEvent(ACCOUNT_NUMBER))).isFalse();
-  }
-
-  @Test
-  public void testIndexGroupOK() throws Exception {
-    when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any()))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(indexForwarder.indexGroup(new GroupIndexEvent(UUID))).isTrue();
-  }
-
-  @Test
-  public void testIndexGroupFailed() throws Exception {
-    when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any()))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(indexForwarder.indexGroup(new GroupIndexEvent(UUID))).isFalse();
-  }
-
-  @Test
-  public void testIndexGroupThrowsException() throws Exception {
-    doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_GROUP_ENDPOINT), any());
-    assertThat(indexForwarder.indexGroup(new GroupIndexEvent(UUID))).isFalse();
-  }
-
-  @Test
-  public void testIndexChangeOK() throws Exception {
-    when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any()))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(indexForwarder.indexChange(new ChangeIndexEvent(PROJECT_NAME, CHANGE_NUMBER, false)))
-        .isTrue();
-  }
-
-  @Test
-  public void testIndexChangeFailed() throws Exception {
-    when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any()))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(indexForwarder.indexChange(new ChangeIndexEvent(PROJECT_NAME, CHANGE_NUMBER, false)))
-        .isFalse();
-  }
-
-  @Test
-  public void testIndexChangeThrowsException() throws Exception {
-    doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_CHANGE_ENDPOINT), any());
-    assertThat(indexForwarder.indexChange(new ChangeIndexEvent(PROJECT_NAME, CHANGE_NUMBER, false)))
-        .isFalse();
-  }
-
-  @Test
-  public void testChangeDeletedFromIndexOK() throws Exception {
-    when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any()))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(
-            indexForwarder.deleteChangeFromIndex(
-                new ChangeIndexEvent(PROJECT_NAME, CHANGE_NUMBER, true)))
-        .isTrue();
-  }
-
-  @Test
-  public void testChangeDeletedFromIndexFailed() throws Exception {
-    when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any()))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(
-            indexForwarder.deleteChangeFromIndex(
-                new ChangeIndexEvent(PROJECT_NAME, CHANGE_NUMBER, false)))
-        .isFalse();
-  }
-
-  @Test
-  public void testChangeDeletedFromThrowsException() throws Exception {
-    doThrow(new IOException()).when(httpSessionMock).delete(eq(DELETE_CHANGE_ENDPOINT), any());
-    assertThat(
-            indexForwarder.deleteChangeFromIndex(
-                new ChangeIndexEvent(PROJECT_NAME, CHANGE_NUMBER, false)))
-        .isFalse();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestProjectListUpdateForwarderTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestProjectListUpdateForwarderTest.java
deleted file mode 100644
index f4ea231..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestProjectListUpdateForwarderTest.java
+++ /dev/null
@@ -1,128 +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 com.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.cache.Constants;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.io.IOException;
-import java.util.Set;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Answers;
-
-public class RestProjectListUpdateForwarderTest {
-  private static final String URL = "http://fake.com";
-  private static final String PLUGIN_NAME = "multi-site";
-  private static final String EMPTY_MSG = "";
-  private static final String PLUGINS = "plugins";
-  private static final String PROJECT_TO_ADD = "projectToAdd";
-  private static final String PROJECT_TO_DELETE = "projectToDelete";
-  private static final boolean SUCCESSFUL = true;
-  private static final boolean FAILED = false;
-
-  // Event
-  private RestProjectListUpdateForwarder forwarder;
-  private HttpSession httpSessionMock;
-
-  @SuppressWarnings("unchecked")
-  @Before
-  public void setUp() {
-    httpSessionMock = mock(HttpSession.class);
-    Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
-    when(configMock.http().maxTries()).thenReturn(3);
-    when(configMock.http().retryInterval()).thenReturn(10);
-    Provider<Set<PeerInfo>> peersMock = mock(Provider.class);
-    when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL)));
-    forwarder =
-        new RestProjectListUpdateForwarder(
-            httpSessionMock, PLUGIN_NAME, configMock, peersMock); // TODO: Create provider
-  }
-
-  private static String buildCacheEndpoint(String name) {
-    return Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "cache", name);
-  }
-
-  @Test
-  public void testAddToProjectListOK() throws Exception {
-    String projectName = PROJECT_TO_ADD;
-    when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.updateProjectList(new ProjectListUpdateEvent(projectName, false)))
-        .isTrue();
-  }
-
-  @Test
-  public void testAddToProjectListFailed() throws Exception {
-    String projectName = PROJECT_TO_ADD;
-    when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.updateProjectList(new ProjectListUpdateEvent(projectName, false)))
-        .isFalse();
-  }
-
-  @Test
-  public void testAddToProjectListThrowsException() throws Exception {
-    String projectName = PROJECT_TO_ADD;
-    doThrow(new IOException())
-        .when(httpSessionMock)
-        .post(buildProjectListCacheEndpoint(projectName), null);
-    assertThat(forwarder.updateProjectList(new ProjectListUpdateEvent(projectName, false)))
-        .isFalse();
-  }
-
-  @Test
-  public void testRemoveFromProjectListOK() throws Exception {
-    String projectName = PROJECT_TO_DELETE;
-    when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any()))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.updateProjectList(new ProjectListUpdateEvent(projectName, true))).isTrue();
-  }
-
-  @Test
-  public void testRemoveToProjectListFailed() throws Exception {
-    String projectName = PROJECT_TO_DELETE;
-    when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any()))
-        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.updateProjectList(new ProjectListUpdateEvent(projectName, true)))
-        .isFalse();
-  }
-
-  @Test
-  public void testRemoveToProjectListThrowsException() throws Exception {
-    String projectName = PROJECT_TO_DELETE;
-    doThrow(new IOException())
-        .when(httpSessionMock)
-        .delete(eq(buildProjectListCacheEndpoint(projectName)), any());
-    assertThat(forwarder.updateProjectList(new ProjectListUpdateEvent(projectName, true)))
-        .isFalse();
-  }
-
-  private static String buildProjectListCacheEndpoint(String projectName) {
-    return Joiner.on("/").join(buildCacheEndpoint(Constants.PROJECT_LIST), projectName);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestStreamEventForwarderTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestStreamEventForwarderTest.java
deleted file mode 100644
index fdd422a..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/rest/RestStreamEventForwarderTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (C) 2019 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.googlesource.gerrit.plugins.multisite.forwarder.rest;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.server.events.Event;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.googlesource.gerrit.plugins.multisite.peers.PeerInfo;
-import java.io.IOException;
-import java.util.Set;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Answers;
-
-public class RestStreamEventForwarderTest {
-  private static final String URL = "http://fake.com";
-  private static final String PLUGIN_NAME = "multi-site";
-  private static final String EMPTY_MSG = "";
-  private static final String PLUGINS = "plugins";
-  private static final boolean SUCCESSFUL = true;
-  private static final boolean FAILED = false;
-
-  // Event
-  private static Event event = new Event("test-event") {};
-  private static final String EVENT_ENDPOINT =
-      Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "event", event.type);
-
-  private StreamEventForwarder forwarder;
-  private HttpSession httpSessionMock;
-
-  @SuppressWarnings("unchecked")
-  @Before
-  public void setUp() {
-    httpSessionMock = mock(HttpSession.class);
-    Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
-    when(configMock.http().maxTries()).thenReturn(3);
-    when(configMock.http().retryInterval()).thenReturn(10);
-    Provider<Set<PeerInfo>> peersMock = mock(Provider.class);
-    when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL)));
-    forwarder =
-        new RestStreamEventForwarder(
-            httpSessionMock, PLUGIN_NAME, configMock, peersMock); // TODO: Create provider
-  }
-
-  @Test
-  public void testEventSentOK() throws Exception {
-    when(httpSessionMock.post(EVENT_ENDPOINT, event))
-        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.send(event)).isTrue();
-  }
-
-  @Test
-  public void testEventSentFailed() throws Exception {
-    when(httpSessionMock.post(EVENT_ENDPOINT, event)).thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.send(event)).isFalse();
-  }
-
-  @Test
-  public void testEventSentThrowsException() throws Exception {
-    doThrow(new IOException()).when(httpSessionMock).post(EVENT_ENDPOINT, event);
-    assertThat(forwarder.send(event)).isFalse();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/AbstractIndexForwardingIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/AbstractIndexForwardingIT.java
deleted file mode 100644
index 9cf41f9..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/AbstractIndexForwardingIT.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.index;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.any;
-import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
-import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
-import static com.github.tomakehurst.wiremock.client.WireMock.post;
-import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
-import static com.github.tomakehurst.wiremock.client.WireMock.verify;
-import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
-import static com.google.common.truth.Truth.assertThat;
-
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.google.gerrit.acceptance.GlobalPluginConfig;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.acceptance.UseLocalDisk;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.HttpStatus;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-
-@Ignore
-@NoHttpd
-@TestPlugin(
-    name = "multi-site",
-    sysModule = "com.googlesource.gerrit.plugins.multisite.Module",
-    httpModule = "com.googlesource.gerrit.plugins.multisite.HttpModule")
-public abstract class AbstractIndexForwardingIT extends LightweightPluginDaemonTest {
-  private static final int PORT = 18889;
-  private static final String URL = "http://localhost:" + PORT;
-
-  @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
-
-  @Override
-  public void setUpTestPlugin() throws Exception {
-    givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
-    beforeAction();
-    super.setUpTestPlugin();
-  }
-
-  @Test
-  @UseLocalDisk
-  @GlobalPluginConfig(pluginName = "multi-site", name = "peerInfo.static.url", value = URL)
-  @GlobalPluginConfig(pluginName = "multi-site", name = "http.retryInterval", value = "100")
-  public void testIndexForwarding() throws Exception {
-    String expectedRequest = getExpectedRequest();
-    CountDownLatch expectedRequestLatch = new CountDownLatch(1);
-    wireMockRule.addMockServiceRequestListener(
-        (request, response) -> {
-          if (request.getAbsoluteUrl().contains(expectedRequest)) {
-            expectedRequestLatch.countDown();
-          }
-        });
-    givenThat(
-        post(urlEqualTo(expectedRequest))
-            .willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
-    doAction();
-    assertThat(expectedRequestLatch.await(5, TimeUnit.SECONDS)).isTrue();
-    verify(postRequestedFor(urlEqualTo(expectedRequest)));
-  }
-
-  /** Perform pre-test setup. */
-  protected abstract void beforeAction() throws Exception;
-
-  /**
-   * Get the URL on which a request is expected.
-   *
-   * @return the URL.
-   */
-  protected abstract String getExpectedRequest();
-
-  /** Perform the action that should cause an index operation to occur. */
-  protected abstract void doAction() throws Exception;
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/AccountIndexForwardingIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/AccountIndexForwardingIT.java
deleted file mode 100644
index a9c1ef5..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/AccountIndexForwardingIT.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.index;
-
-import com.google.gerrit.acceptance.TestAccount;
-
-public class AccountIndexForwardingIT extends AbstractIndexForwardingIT {
-  private TestAccount testAccount;
-
-  @Override
-  public void beforeAction() throws Exception {
-    testAccount = accountCreator.create("someUser");
-  }
-
-  @Override
-  public String getExpectedRequest() {
-    return "/plugins/multi-site/index/account/" + testAccount.id;
-  }
-
-  @Override
-  public void doAction() throws Exception {
-    gApi.accounts().id(testAccount.id.get()).setActive(false);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/ChangeIndexForwardingIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/ChangeIndexForwardingIT.java
deleted file mode 100644
index 6725797..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/ChangeIndexForwardingIT.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.index;
-
-public class ChangeIndexForwardingIT extends AbstractIndexForwardingIT {
-  private int changeId;
-
-  @Override
-  public void beforeAction() throws Exception {
-    changeId = createChange().getChange().getId().get();
-  }
-
-  @Override
-  public String getExpectedRequest() {
-    return "/plugins/multi-site/index/change/" + project.get() + "~" + changeId;
-  }
-
-  @Override
-  public void doAction() throws Exception {
-    gApi.changes().id(changeId).abandon();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/GroupIndexForwardingIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/GroupIndexForwardingIT.java
deleted file mode 100644
index a5b32f1..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/GroupIndexForwardingIT.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.index;
-
-public class GroupIndexForwardingIT extends AbstractIndexForwardingIT {
-  private String someGroupId;
-
-  @Override
-  public void beforeAction() throws Exception {
-    someGroupId = gApi.groups().create("someGroup").get().id;
-  }
-
-  @Override
-  public String getExpectedRequest() {
-    return "/plugins/multi-site/index/group/" + someGroupId;
-  }
-
-  @Override
-  public void doAction() throws Exception {
-    gApi.groups().id(someGroupId).index();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
deleted file mode 100644
index 26a91be..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
+++ /dev/null
@@ -1,288 +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 com.googlesource.gerrit.plugins.multisite.index;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.AccountIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.GroupIndexEvent;
-import com.googlesource.gerrit.plugins.multisite.index.IndexEventHandler.IndexAccountTask;
-import com.googlesource.gerrit.plugins.multisite.index.IndexEventHandler.IndexChangeTask;
-import com.googlesource.gerrit.plugins.multisite.index.IndexEventHandler.IndexGroupTask;
-import java.util.Optional;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IndexEventHandlerTest {
-  private static final String PLUGIN_NAME = "multi-site";
-  private static final String PROJECT_NAME = "test/project";
-  private static final int CHANGE_ID = 1;
-  private static final int ACCOUNT_ID = 2;
-  private static final String UUID = "3";
-  private static final String OTHER_UUID = "4";
-
-  private IndexEventHandler indexEventHandler;
-  @Mock private IndexEventForwarder forwarder;
-  @Mock private ChangeCheckerImpl.Factory changeCheckerFactoryMock;
-  @Mock private ChangeChecker changeCheckerMock;
-  private Change.Id changeId;
-  private Account.Id accountId;
-  private AccountGroup.UUID accountGroupUUID;
-
-  @Before
-  public void setUpMocks() throws Exception {
-    changeId = new Change.Id(CHANGE_ID);
-    accountId = new Account.Id(ACCOUNT_ID);
-    accountGroupUUID = new AccountGroup.UUID(UUID);
-    when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerMock);
-    when(changeCheckerMock.newIndexEvent(PROJECT_NAME, CHANGE_ID, false))
-        .thenReturn(Optional.of(new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID, false)));
-
-    indexEventHandler =
-        new IndexEventHandler(
-            MoreExecutors.directExecutor(),
-            PLUGIN_NAME,
-            asDynamicSet(forwarder),
-            changeCheckerFactoryMock);
-  }
-
-  private DynamicSet<IndexEventForwarder> asDynamicSet(IndexEventForwarder forwarder) {
-    DynamicSet<IndexEventForwarder> result = new DynamicSet<>();
-    result.add("multi-site", forwarder);
-    return result;
-  }
-
-  @Test
-  public void shouldIndexInRemoteOnChangeIndexedEvent() throws Exception {
-    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
-    ChangeIndexEvent changeIndexEvent = new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID, false);
-    verify(forwarder).indexChange(changeIndexEvent);
-  }
-
-  @Test
-  public void shouldIndexInRemoteOnAccountIndexedEvent() throws Exception {
-    indexEventHandler.onAccountIndexed(accountId.get());
-    AccountIndexEvent accountIndexEvent = new AccountIndexEvent(accountId.get());
-    verify(forwarder).indexAccount(accountIndexEvent);
-  }
-
-  @Test
-  public void shouldDeleteFromIndexInRemoteOnChangeDeletedEvent() throws Exception {
-    indexEventHandler.onChangeDeleted(changeId.get());
-    ChangeIndexEvent changeIndexEvent = new ChangeIndexEvent("", changeId.get(), true);
-    verify(forwarder).deleteChangeFromIndex(changeIndexEvent);
-    verifyZeroInteractions(
-        changeCheckerMock); // Deleted changes should not be checked against NoteDb
-  }
-
-  @Test
-  public void shouldIndexInRemoteOnGroupIndexedEvent() throws Exception {
-    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
-    GroupIndexEvent groupIndexEvent = new GroupIndexEvent(UUID);
-    verify(forwarder).indexGroup(groupIndexEvent);
-  }
-
-  @Test
-  public void shouldNotCallRemoteWhenChangeEventIsForwarded() throws Exception {
-    Context.setForwardedEvent(true);
-    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
-    indexEventHandler.onChangeDeleted(changeId.get());
-    Context.unsetForwardedEvent();
-    verifyZeroInteractions(forwarder);
-  }
-
-  @Test
-  public void shouldNotCallRemoteWhenAccountEventIsForwarded() throws Exception {
-    Context.setForwardedEvent(true);
-    indexEventHandler.onAccountIndexed(accountId.get());
-    indexEventHandler.onAccountIndexed(accountId.get());
-    Context.unsetForwardedEvent();
-    verifyZeroInteractions(forwarder);
-  }
-
-  @Test
-  public void shouldNotCallRemoteWhenGroupEventIsForwarded() throws Exception {
-    Context.setForwardedEvent(true);
-    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
-    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
-    Context.unsetForwardedEvent();
-    verifyZeroInteractions(forwarder);
-  }
-
-  @Test
-  public void duplicateChangeEventOfAQueuedEventShouldGetDiscarded() {
-    ScheduledThreadPoolExecutor poolMock = mock(ScheduledThreadPoolExecutor.class);
-    indexEventHandler =
-        new IndexEventHandler(
-            poolMock, PLUGIN_NAME, asDynamicSet(forwarder), changeCheckerFactoryMock);
-    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
-    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
-    verify(poolMock, times(1))
-        .execute(
-            indexEventHandler
-            .new IndexChangeTask(new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID, false)));
-  }
-
-  @Test
-  public void duplicateAccountEventOfAQueuedEventShouldGetDiscarded() {
-    ScheduledThreadPoolExecutor poolMock = mock(ScheduledThreadPoolExecutor.class);
-    indexEventHandler =
-        new IndexEventHandler(
-            poolMock, PLUGIN_NAME, asDynamicSet(forwarder), changeCheckerFactoryMock);
-    indexEventHandler.onAccountIndexed(accountId.get());
-    indexEventHandler.onAccountIndexed(accountId.get());
-    verify(poolMock, times(1))
-        .execute(indexEventHandler.new IndexAccountTask(new AccountIndexEvent(ACCOUNT_ID)));
-  }
-
-  @Test
-  public void duplicateGroupEventOfAQueuedEventShouldGetDiscarded() {
-    ScheduledThreadPoolExecutor poolMock = mock(ScheduledThreadPoolExecutor.class);
-    indexEventHandler =
-        new IndexEventHandler(
-            poolMock, PLUGIN_NAME, asDynamicSet(forwarder), changeCheckerFactoryMock);
-    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
-    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
-    verify(poolMock, times(1))
-        .execute(indexEventHandler.new IndexGroupTask(new GroupIndexEvent(UUID)));
-  }
-
-  @Test
-  public void testIndexChangeTaskToString() throws Exception {
-    IndexChangeTask task =
-        indexEventHandler.new IndexChangeTask(new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID, false));
-    assertThat(task.toString())
-        .isEqualTo(
-            String.format("[%s] Index change %s in target instance", PLUGIN_NAME, CHANGE_ID));
-  }
-
-  @Test
-  public void testIndexAccountTaskToString() throws Exception {
-    IndexAccountTask task =
-        indexEventHandler.new IndexAccountTask(new AccountIndexEvent(ACCOUNT_ID));
-    assertThat(task.toString())
-        .isEqualTo(
-            String.format("[%s] Index account %s in target instance", PLUGIN_NAME, ACCOUNT_ID));
-  }
-
-  @Test
-  public void testIndexGroupTaskToString() throws Exception {
-    IndexGroupTask task = indexEventHandler.new IndexGroupTask(new GroupIndexEvent(UUID));
-    assertThat(task.toString())
-        .isEqualTo(String.format("[%s] Index group %s in target instance", PLUGIN_NAME, UUID));
-  }
-
-  @Test
-  public void testIndexChangeTaskHashCodeAndEquals() {
-    IndexChangeTask task =
-        indexEventHandler.new IndexChangeTask(new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID, false));
-
-    IndexChangeTask sameTask = task;
-    assertThat(task.equals(sameTask)).isTrue();
-    assertThat(task.hashCode()).isEqualTo(sameTask.hashCode());
-
-    IndexChangeTask identicalTask =
-        indexEventHandler.new IndexChangeTask(new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID, false));
-    assertThat(task.equals(identicalTask)).isTrue();
-    assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode());
-
-    assertThat(task.equals(null)).isFalse();
-    assertThat(
-            task.equals(
-                indexEventHandler
-                .new IndexChangeTask(new ChangeIndexEvent(PROJECT_NAME, CHANGE_ID + 1, false))))
-        .isFalse();
-    assertThat(task.hashCode()).isNotEqualTo("test".hashCode());
-
-    IndexChangeTask differentChangeIdTask =
-        indexEventHandler.new IndexChangeTask(new ChangeIndexEvent(PROJECT_NAME, 123, false));
-    assertThat(task.equals(differentChangeIdTask)).isFalse();
-    assertThat(task.hashCode()).isNotEqualTo(differentChangeIdTask.hashCode());
-
-    IndexChangeTask removeTask =
-        indexEventHandler.new IndexChangeTask(new ChangeIndexEvent("", CHANGE_ID, true));
-    assertThat(task.equals(removeTask)).isFalse();
-    assertThat(task.hashCode()).isNotEqualTo(removeTask.hashCode());
-  }
-
-  @Test
-  public void testIndexAccountTaskHashCodeAndEquals() {
-    IndexAccountTask task =
-        indexEventHandler.new IndexAccountTask(new AccountIndexEvent(ACCOUNT_ID));
-
-    IndexAccountTask sameTask = task;
-    assertThat(task.equals(sameTask)).isTrue();
-    assertThat(task.hashCode()).isEqualTo(sameTask.hashCode());
-
-    IndexAccountTask identicalTask =
-        indexEventHandler.new IndexAccountTask(new AccountIndexEvent(ACCOUNT_ID));
-    assertThat(task.equals(identicalTask)).isTrue();
-    assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode());
-
-    assertThat(task.equals(null)).isFalse();
-    assertThat(
-            task.equals(
-                indexEventHandler.new IndexAccountTask(new AccountIndexEvent(ACCOUNT_ID + 1))))
-        .isFalse();
-    assertThat(task.hashCode()).isNotEqualTo("test".hashCode());
-
-    IndexAccountTask differentAccountIdTask =
-        indexEventHandler.new IndexAccountTask(new AccountIndexEvent(123));
-    assertThat(task.equals(differentAccountIdTask)).isFalse();
-    assertThat(task.hashCode()).isNotEqualTo(differentAccountIdTask.hashCode());
-  }
-
-  @Test
-  public void testIndexGroupTaskHashCodeAndEquals() {
-    IndexGroupTask task = indexEventHandler.new IndexGroupTask(new GroupIndexEvent(UUID));
-
-    IndexGroupTask sameTask = task;
-    assertThat(task.equals(sameTask)).isTrue();
-    assertThat(task.hashCode()).isEqualTo(sameTask.hashCode());
-
-    IndexGroupTask identicalTask = indexEventHandler.new IndexGroupTask(new GroupIndexEvent(UUID));
-    assertThat(task.equals(identicalTask)).isTrue();
-    assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode());
-
-    assertThat(task.equals(null)).isFalse();
-    assertThat(task.equals(indexEventHandler.new IndexGroupTask(new GroupIndexEvent(OTHER_UUID))))
-        .isFalse();
-    assertThat(task.hashCode()).isNotEqualTo("test".hashCode());
-
-    IndexGroupTask differentGroupIdTask =
-        indexEventHandler.new IndexGroupTask(new GroupIndexEvent("123"));
-    assertThat(task.equals(differentGroupIdTask)).isFalse();
-    assertThat(task.hashCode()).isNotEqualTo(differentGroupIdTask.hashCode());
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/ProjectIndexForwardingIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/ProjectIndexForwardingIT.java
deleted file mode 100644
index 5157c63..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/ProjectIndexForwardingIT.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2018 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.googlesource.gerrit.plugins.multisite.index;
-
-import com.google.gerrit.extensions.restapi.Url;
-
-public class ProjectIndexForwardingIT extends AbstractIndexForwardingIT {
-  private String someProjectName;
-
-  @Override
-  public void beforeAction() throws Exception {
-    someProjectName = gApi.projects().create("someProject").get().name;
-  }
-
-  @Override
-  public String getExpectedRequest() {
-    return "/plugins/multi-site/index/project/" + Url.encode(someProjectName);
-  }
-
-  @Override
-  public void doAction() throws Exception {
-    gApi.projects().name(someProjectName).index(false);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java
index e1601cd..4a3b466 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java
@@ -16,13 +16,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.common.base.Supplier;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.EventDeserializer;
-import com.google.gerrit.server.events.SupplierDeserializer;
 import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
 import java.util.UUID;
 import org.junit.Before;
 import org.junit.Test;
@@ -32,7 +28,7 @@
 
   @Before
   public void setUp() {
-    final Provider<Gson> gsonProvider = buildGsonProvider();
+    final Provider<Gson> gsonProvider = new GsonProvider();
     deserializer = new KafkaEventDeserializer(gsonProvider);
   }
 
@@ -67,13 +63,4 @@
   public void kafkaEventDeserializerShouldFailForInvalidObjectButValidJSON() {
     deserializer.deserialize("ignored", "{}".getBytes());
   }
-
-  private Provider<Gson> buildGsonProvider() {
-    Gson gson =
-        new GsonBuilder()
-            .registerTypeAdapter(Event.class, new EventDeserializer())
-            .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
-            .create();
-    return () -> gson;
-  }
 }