Merge branch 'stable-3.3' into stable-3.4

* stable-3.3:
  Revert "Set forward context for events having a different instanceId"
  Use Gerrit v3.2.14 on Ubuntu as CentOS is discontinued
  Serve reindexing simulating a GET request for /meta ref caching
  Auto-reload the indexTs file for auto-reindexing
  Download plugins from archive-ci.gerritforge.com
  Pin haproxy to 1.8.30-buster and fix associated issues
  Pin haproxy to 1.8.30-buster and fix associated issues
  Fix issue with change indexing during the NoteDb online migration
  Remove references to ReviewDb in README.md

Change-Id: I0c89172943e017111dffe726358b82584d794d5e
diff --git a/BUILD b/BUILD
index 5975b31..2ee0faa 100644
--- a/BUILD
+++ b/BUILD
@@ -21,7 +21,7 @@
     resources = glob(["src/main/resources/**/*"]),
     deps = [
       "@jgroups//jar",
-      "@global-refdb//jar",
+      "@global-refdb//jar:neverlink",
     ],
 )
 
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 9af89f0..74c8f13 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -15,6 +15,6 @@
 
     maven_jar(
         name = "global-refdb",
-        artifact = "com.gerritforge:global-refdb:3.3.2.1:jdk8",
-        sha1 = "7a293d577665dfc6f4d36371af21c4a3f7177b23",
+        artifact = "com.gerritforge:global-refdb:3.4.8",
+        sha1 = "a05e1684c0b02867c203e3f55efe62ced2c0fe61",
     )
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ConfigurableAllowedEventListeners.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ConfigurableAllowedEventListeners.java
new file mode 100644
index 0000000..ea5f69c
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ConfigurableAllowedEventListeners.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 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.ericsson.gerrit.plugins.highavailability;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.AllowedForwardedEventListener;
+import com.google.gerrit.server.events.EventListener;
+import com.google.inject.Inject;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** Configure the allowed listeners in high-availability.config */
+public class ConfigurableAllowedEventListeners implements AllowedForwardedEventListener {
+  private final Set<String> allowedListenerClasses;
+  private final ConcurrentHashMap<EventListener, Boolean> cachedAllowedListeners;
+
+  @Inject
+  ConfigurableAllowedEventListeners(Configuration config) {
+    allowedListenerClasses = config.event().allowedListeners();
+    cachedAllowedListeners = new ConcurrentHashMap<>();
+  }
+
+  @Override
+  public boolean isAllowed(EventListener listener) {
+    return cachedAllowedListeners.computeIfAbsent(listener, this::computeIsAllowed);
+  }
+
+  protected Boolean computeIsAllowed(EventListener listener) {
+    String listenerClassName = listener.getClass().getName();
+    boolean allowed = false;
+    while (!allowed && !listenerClassName.isEmpty()) {
+      allowed = allowedListenerClasses.contains(listenerClassName);
+      int lastDotPos = Math.max(listenerClassName.lastIndexOf('.'), 0);
+      listenerClassName = listenerClassName.substring(0, lastDotPos);
+    }
+
+    return allowed;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index f28b7c9..cdca21b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -23,6 +23,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.server.config.ConfigUtil;
@@ -467,9 +468,18 @@
 
   public static class Event extends Forwarding {
     static final String EVENT_SECTION = "event";
+    static final String ALLOWED_LISTENERS = "allowedListeners";
+
+    private final Set<String> allowedListeners;
 
     private Event(Config cfg) {
       super(cfg, EVENT_SECTION);
+
+      allowedListeners = Sets.newHashSet(cfg.getStringList(EVENT_SECTION, null, ALLOWED_LISTENERS));
+    }
+
+    public Set<String> allowedListeners() {
+      return allowedListeners;
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexModule.java
index d4871a7..d5e168d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexModule.java
@@ -18,6 +18,7 @@
 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.events.ProjectIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.AbstractModule;
 
@@ -29,5 +30,6 @@
     DynamicSet.bind(binder(), ChangeIndexedListener.class).to(IndexTs.class);
     DynamicSet.bind(binder(), AccountIndexedListener.class).to(IndexTs.class);
     DynamicSet.bind(binder(), GroupIndexedListener.class).to(IndexTs.class);
+    DynamicSet.bind(binder(), ProjectIndexedListener.class).to(IndexTs.class);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
index c21ccae..4ded55e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
@@ -22,9 +22,9 @@
 import com.google.gerrit.entities.AccountGroupByIdAudit;
 import com.google.gerrit.entities.AccountGroupMemberAudit;
 import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.entities.InternalGroup;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.db.Groups;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/AllowedForwardedEventListener.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/AllowedForwardedEventListener.java
new file mode 100644
index 0000000..d79625a
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/AllowedForwardedEventListener.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 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.ericsson.gerrit.plugins.highavailability.forwarder;
+
+import com.google.gerrit.server.events.EventListener;
+
+/** Allow to trigger an event listener unconditionally. */
+public interface AllowedForwardedEventListener {
+
+  /**
+   * Control whether an event listener should be allowed unconditionally.
+   *
+   * @param listener the event listener
+   * @return true if the listener should be allowed, false otherwise
+   */
+  boolean isAllowed(EventListener listener);
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
index 700d5e1..29d4e56 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
@@ -14,6 +14,7 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.server.config.GerritInstanceId;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventBroker;
@@ -24,10 +25,11 @@
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
-import javax.annotation.Nullable;
 
 class ForwardedAwareEventBroker extends EventBroker {
 
+  private final AllowedForwardedEventListener allowedListeners;
+
   @Inject
   ForwardedAwareEventBroker(
       PluginSetContext<UserScopedEventListener> listeners,
@@ -35,7 +37,8 @@
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
       Factory notesFactory,
-      @Nullable @GerritInstanceId String gerritInstanceId) {
+      @Nullable @GerritInstanceId String gerritInstanceId,
+      @Nullable AllowedForwardedEventListener allowedListeners) {
     super(
         listeners,
         unrestrictedListeners,
@@ -43,12 +46,21 @@
         projectCache,
         notesFactory,
         gerritInstanceId);
+
+    this.allowedListeners = allowedListeners;
   }
 
   @Override
   protected void fireEventForUnrestrictedListeners(Event event) {
-    if (!Context.isForwardedEvent()) {
-      super.fireEventForUnrestrictedListeners(event);
+    // or it was consumed by the high-availability rest endpoint and
+    // thus the context of its consumption has already been set to "forwarded".
+    unrestrictedListeners.runEach(l -> fireEventForListener(l, event));
+  }
+
+  private void fireEventForListener(EventListener l, Event event) {
+    if (!Context.isForwardedEvent()
+        || (allowedListeners != null && allowedListeners.isAllowed(l))) {
+      l.onEvent(event);
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
index 99a820e..7cb107c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
@@ -14,14 +14,19 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
+import com.ericsson.gerrit.plugins.highavailability.ConfigurableAllowedEventListeners;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
 
 public class ForwarderModule extends AbstractModule {
 
   @Override
   protected void configure() {
+    bind(AllowedForwardedEventListener.class)
+        .to(ConfigurableAllowedEventListeners.class)
+        .in(Scopes.SINGLETON);
     DynamicItem.bind(binder(), EventDispatcher.class).to(ForwardedAwareEventBroker.class);
   }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index ff987aa..d94a91e 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -175,6 +175,12 @@
     Defaults to an empty list, meaning only evictions of the core caches are
     forwarded.
 
+```event.allowedListeners```
+:   Class name or package name of the event listener that is always allowed to receive
+    all events generated locally or from a remote end.
+    Can be specified multiple times for allowing multiple listeners classes or packages.
+    Defaults to an empty list.
+
 ```event.synchronize```
 :   Whether to synchronize stream events.
     Defaults to true.
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile
index 1d0ffa0..b4f9c6e 100644
--- a/src/test/docker/gerrit/Dockerfile
+++ b/src/test/docker/gerrit/Dockerfile
@@ -1,6 +1,6 @@
-FROM gerritcodereview/gerrit:3.3.7-ubuntu20
+FROM gerritcodereview/gerrit:3.4.5-ubuntu20
 
-ENV GERRIT_BRANCH=stable-3.3
+ENV GERRIT_BRANCH=stable-3.4
 
 ENV GERRIT_CI_URL=https://archive-ci.gerritforge.com/job
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index 19cc48d..fb1b8d0 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -20,6 +20,7 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_NUM_STRIPED_LOCKS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.ALLOWED_LISTENERS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY;
@@ -68,10 +69,14 @@
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
@@ -331,6 +336,88 @@
   }
 
   @Test
+  public void testGetEventAllowedListener() throws Exception {
+    assertThat(getConfiguration().event().allowedListeners()).isEmpty();
+
+    List<String> allowedListeners = Arrays.asList("listener1", "listener2");
+    globalPluginConfig.setStringList(EVENT_SECTION, null, ALLOWED_LISTENERS, allowedListeners);
+    assertThat(getConfiguration().event().allowedListeners())
+        .containsExactlyElementsIn(allowedListeners);
+  }
+
+  @Test
+  public void testConfiguredListenerShouldBeAllowed() throws Exception {
+    EventListener listener =
+        new EventListener() {
+
+          @Override
+          public void onEvent(Event event) {}
+        };
+    assertThat(new ConfigurableAllowedEventListeners(getConfiguration()).isAllowed(listener))
+        .isFalse();
+
+    globalPluginConfig.setString(
+        EVENT_SECTION, null, ALLOWED_LISTENERS, listener.getClass().getName());
+
+    assertThat(new ConfigurableAllowedEventListeners(getConfiguration()).isAllowed(listener))
+        .isTrue();
+
+    globalPluginConfig.setString(
+        EVENT_SECTION, null, ALLOWED_LISTENERS, listener.getClass().getPackageName());
+
+    assertThat(new ConfigurableAllowedEventListeners(getConfiguration()).isAllowed(listener))
+        .isTrue();
+  }
+
+  @Test
+  public void testConfiguredListenerAllowedShouldBeCached() throws Exception {
+    AtomicInteger allowedListenerResolutionCount = new AtomicInteger();
+    EventListener listener =
+        new EventListener() {
+
+          @Override
+          public void onEvent(Event event) {}
+        };
+    assertThat(new ConfigurableAllowedEventListeners(getConfiguration()).isAllowed(listener))
+        .isFalse();
+
+    globalPluginConfig.setString(
+        EVENT_SECTION, null, ALLOWED_LISTENERS, listener.getClass().getName());
+
+    ConfigurableAllowedEventListeners allowedEventListener =
+        new ConfigurableAllowedEventListeners(getConfiguration()) {
+          @Override
+          protected Boolean computeIsAllowed(EventListener listener) {
+            allowedListenerResolutionCount.incrementAndGet();
+            return super.computeIsAllowed(listener);
+          }
+        };
+
+    for (int i = 0; i < 2; i++) {
+      assertThat(allowedEventListener.isAllowed(listener)).isTrue();
+      assertThat(allowedListenerResolutionCount.get()).isEqualTo(1);
+    }
+  }
+
+  @Test
+  public void testConfiguredPackageOfListenerShouldBeAllowed() throws Exception {
+    EventListener listener =
+        new EventListener() {
+
+          @Override
+          public void onEvent(Event event) {}
+        };
+    assertThat(new ConfigurableAllowedEventListeners(getConfiguration()).isAllowed(listener))
+        .isFalse();
+
+    globalPluginConfig.setString(
+        EVENT_SECTION, null, ALLOWED_LISTENERS, listener.getClass().getPackageName());
+
+    assertThat(new ConfigurableAllowedEventListeners(getConfiguration()).isAllowed(listener))
+        .isTrue();
+  }
+
+  @Test
   public void testGetDefaultSharedDirectory() throws Exception {
     assertEquals(
         getConfiguration().main().sharedDirectory(), sitePaths.resolve(DEFAULT_SHARED_DIRECTORY));
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java
index 7ab4a1c..afd6df4 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java
@@ -30,9 +30,9 @@
 import com.google.gerrit.entities.AccountGroupByIdAudit;
 import com.google.gerrit.entities.AccountGroupMemberAudit;
 import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.entities.InternalGroup;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.db.Groups;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import java.sql.Timestamp;
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
index ec5b1de..4c639ba 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
@@ -29,6 +29,7 @@
 public class ForwardedAwareEventBrokerTest {
 
   private EventListener listenerMock;
+  private AllowedForwardedEventListener allowListenerMock;
   private ForwardedAwareEventBroker broker;
   private Event event = new TestEvent();
 
@@ -36,10 +37,12 @@
   public void setUp() {
     PluginMetrics mockMetrics = mock(PluginMetrics.class);
     listenerMock = mock(EventListener.class);
+    allowListenerMock = mock(AllowedForwardedEventListener.class);
     DynamicSet<EventListener> set = DynamicSet.emptySet();
     set.add("high-availability", listenerMock);
     PluginSetContext<EventListener> listeners = new PluginSetContext<>(set, mockMetrics);
-    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null, null);
+    broker =
+        new ForwardedAwareEventBroker(null, listeners, null, null, null, null, allowListenerMock);
   }
 
   @Test