Merge branch 'stable-3.11' into stable-3.12

* stable-3.11:
  Set GERRIT_BRANCH to stable-3.11 for local E2E test environment

Change-Id: Id11f3258a7c31310ed56a8313f2c5f9df468f526
diff --git a/README.md b/README.md
index 1e6882c..2e13f45 100644
--- a/README.md
+++ b/README.md
@@ -32,21 +32,38 @@
 The multi-site plugin can only be built in tree mode, by cloning
 Gerrit and the multi-site plugin code, and checking them out on the desired branch.
 
-Example of cloning Gerrit and multi-site for a stable-2.16 build:
+To build the multi-site plugin in addition to the gerrit core plugins also the
+following plugins need to be present in the plugins directory:
+
+- pull-replication
+- healthcheck
+- events-broker
+- global-refdb
+
+Example of cloning Gerrit, the multi-site plugin and the plugins it depends on
+for a stable-3.11 build:
 
 ```
-git clone -b stable-2.16 https://gerrit.googlesource.com/gerrit
-git clone -b stable-2.16 https://gerrit.googlesource.com/plugins/multi-site
+git clone --recurse-submodules -b stable-3.11 https://gerrit.googlesource.com/gerrit
+git clone -b stable-3.11 https://gerrit.googlesource.com/plugins/multi-site
+git clone -b stable-3.11 https://gerrit.googlesource.com/modules/events-broker
+git clone -b stable-3.11 https://gerrit.googlesource.com/modules/global-refdb
+git clone -b stable-3.11 https://gerrit.googlesource.com/plugins/healthcheck
+git clone -b stable-3.11 https://gerrit.googlesource.com/plugins/pull-replication
 
 cd gerrit/plugins
 ln -s ../../multi-site .
+ln -s ../../events-broker .
+ln -s ../../global-refdb .
+ln -s ../../healthcheck .
+ln -s ../../pull-replication .
 ```
 
 Example of building the multi-site plugin:
 
 ```
 cd gerrit
-bazel build plugins/multi-site
+bazelisk build plugins/multi-site
 ```
 
 The multi-site.jar plugin is generated to `bazel-bin/plugins/multi-site/multi-site.jar`.
@@ -55,13 +72,13 @@
 
 ```
 cd gerrit
-bazel test plugins/multi-site:multi_site_tests
+bazelisk test plugins/multi-site/...
 ```
 
 **NOTE**: The multi-site tests include also the use of Docker containers for
 instantiating and using a Kafka/Zookeeper broker. Make sure you have a Docker
 daemon running (/var/run/docker.sock accessible) or a DOCKER_HOST pointing to
-a Docker server.
+a Docker server. In addition docker-compose needs to be installed.
 
 ## Pre-requisites
 
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 4415aea..1be09aa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -58,6 +58,8 @@
   private static final String REPLICATION_LAG_REFRESH_INTERVAL = "replicationLagRefreshInterval";
   private static final String REPLICATION_LAG_ENABLED = "replicationLagEnabled";
   private static final Duration REPLICATION_LAG_REFRESH_INTERVAL_DEFAULT = Duration.ofSeconds(60);
+  private static final String PUSH_REPLICATION_FILTER_ENABLED = "pushReplicationFilterEnabled";
+  private static final String PULL_REPLICATION_FILTER_ENABLED = "pullReplicationFilterEnabled";
   private static final String LOCAL_REF_LOCK_TIMEOUT = "localRefLockTimeout";
   private static final Duration LOCAL_REF_LOCK_TIMEOUT_DEFAULT = Duration.ofSeconds(30);
 
@@ -81,6 +83,8 @@
   private final Config multiSiteConfig;
   private final Supplier<Duration> replicationLagRefreshInterval;
   private final Supplier<Boolean> replicationLagEnabled;
+  private final Supplier<Boolean> pushReplicationFilterEnabled;
+  private final Supplier<Boolean> pullReplicationFilterEnabled;
   private final Supplier<Long> localRefLockTimeoutMsec;
 
   @Inject
@@ -121,6 +125,19 @@
                 lazyMultiSiteCfg
                     .get()
                     .getBoolean(REF_DATABASE, null, REPLICATION_LAG_ENABLED, true));
+
+    pushReplicationFilterEnabled =
+        memoize(
+            () ->
+                lazyMultiSiteCfg
+                    .get()
+                    .getBoolean(REF_DATABASE, null, PUSH_REPLICATION_FILTER_ENABLED, true));
+    pullReplicationFilterEnabled =
+        memoize(
+            () ->
+                lazyMultiSiteCfg
+                    .get()
+                    .getBoolean(REF_DATABASE, null, PULL_REPLICATION_FILTER_ENABLED, true));
     localRefLockTimeoutMsec =
         memoize(
             () ->
@@ -173,6 +190,14 @@
     return replicationLagEnabled.get();
   }
 
+  public boolean pushReplicationFilterEnabled() {
+    return pushReplicationFilterEnabled.get();
+  }
+
+  public boolean pullRepllicationFilterEnabled() {
+    return pullReplicationFilterEnabled.get();
+  }
+
   public long localRefLockTimeoutMsec() {
     return localRefLockTimeoutMsec.get();
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
index db06c9f..3ced8c6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
@@ -35,14 +35,10 @@
 
 public class PluginModule extends LifecycleModule {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
-  private static final String[] FILTER_MODULES_CLASS_NAMES =
-      new String[] {
-        /* Class names are defined as String for avoiding this class failing to load
-         * if either replication or pull-replication plugins are missing.
-         */
-        "com.googlesource.gerrit.plugins.multisite.validation.PullReplicationFilterModule",
-        "com.googlesource.gerrit.plugins.multisite.validation.PushReplicationFilterModule"
-      };
+  public static final String PULL_REPLICATION_FILTER_MODULE =
+      "com.googlesource.gerrit.plugins.multisite.validation.PullReplicationFilterModule";
+  public static final String PUSH_REPLICATION_FILTER_MODULE =
+      "com.googlesource.gerrit.plugins.multisite.validation.PushReplicationFilterModule";
 
   private final Configuration config;
   private final WorkQueue workQueue;
@@ -89,25 +85,33 @@
   private Iterable<AbstractModule> detectFilterModules() {
     ImmutableList.Builder<AbstractModule> filterModulesBuilder = ImmutableList.builder();
 
-    for (String filterClassName : FILTER_MODULES_CLASS_NAMES) {
-      try {
-        @SuppressWarnings("unchecked")
-        Class<AbstractModule> filterClass = (Class<AbstractModule>) Class.forName(filterClassName);
-
-        AbstractModule filterModule = parentInjector.getInstance(filterClass);
-        // Check if the filterModule would be valid for creating a child Guice Injector
-        parentInjector.createChildInjector(filterModule);
-
-        filterModulesBuilder.add(filterModule);
-      } catch (NoClassDefFoundError | ClassNotFoundException e) {
-        log.atFine().withCause(e).log(
-            "Not loading %s because of missing the associated replication plugin", filterClassName);
-      } catch (Exception e) {
-        throw new ProvisionException(
-            "Unable to instantiate replication filter " + filterClassName, e);
-      }
+    if (config.pushReplicationFilterEnabled()) {
+      bindReplicationFilterClass(PUSH_REPLICATION_FILTER_MODULE, filterModulesBuilder);
+    }
+    if (config.pullRepllicationFilterEnabled()) {
+      bindReplicationFilterClass(PULL_REPLICATION_FILTER_MODULE, filterModulesBuilder);
     }
 
     return filterModulesBuilder.build();
   }
+
+  private void bindReplicationFilterClass(
+      String filterClassName, ImmutableList.Builder<AbstractModule> filterModulesBuilder) {
+    try {
+      @SuppressWarnings("unchecked")
+      Class<AbstractModule> filterClass = (Class<AbstractModule>) Class.forName(filterClassName);
+
+      AbstractModule filterModule = parentInjector.getInstance(filterClass);
+      // Check if the filterModule would be valid for creating a child Guice Injector
+      parentInjector.createChildInjector(filterModule);
+
+      filterModulesBuilder.add(filterModule);
+    } catch (NoClassDefFoundError | ClassNotFoundException e) {
+      log.atWarning().withCause(e).log(
+          "Not loading %s because of missing the associated replication plugin", filterClassName);
+    } catch (Exception e) {
+      throw new ProvisionException(
+          "Unable to instantiate replication filter " + filterClassName, e);
+    }
+  }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 55e5041..ed72405 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -95,6 +95,16 @@
 :   Enable the use of a shared ref-database
     Defaults: true
 
+```ref-database.pushReplicationFilterClassEnabled```
+:   Enable the filtering of push replication events checking their
+    up-to-date status with the global-refdb.
+    Defaults: true
+
+```ref-database.pullReplicationFilterClassEnabled```
+:   Enable the filtering of pull replication events checking their
+    up-to-date status with the global-refdb.
+    Defaults: true
+
 ```ref-database.localRefLockTimeout```
 :   Timeout waiting for a local ref to become available to accept
     updates or for starting a replication task.