Merge branch 'stable-3.2' into stable-3.3

* stable-3.2:
  Handle missing unknown during loading of change notes
  IndexEventHandler: Fix FloggerFormatString warning
  GroupReindexRunnable: Fix FloggerFormatString warning
  RestForwarder: Add missing override annotation
  AbstractIndexRestApiServlet: Fix resource leak warning
  Upgrade bazlets to latest stable-3.1 to build with 3.1.12 API

Change-Id: Icc50364daa7cb00d0775f2bae485aa51a317e117
diff --git a/.bazelignore b/.bazelignore
deleted file mode 100644
index 30f1613..0000000
--- a/.bazelignore
+++ /dev/null
@@ -1 +0,0 @@
-eclipse-out
diff --git a/.bazelrc b/.bazelrc
deleted file mode 100644
index 3ae03ff..0000000
--- a/.bazelrc
+++ /dev/null
@@ -1,2 +0,0 @@
-build --workspace_status_command="python ./tools/workspace_status.py"
-test --build_tests_only
diff --git a/.bazelversion b/.bazelversion
deleted file mode 100644
index 7c69a55..0000000
--- a/.bazelversion
+++ /dev/null
@@ -1 +0,0 @@
-3.7.0
diff --git a/.gitignore b/.gitignore
index bc98942..64f8b81 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,5 @@
 # LC_COLLATE=C sort
-/.apt_generated/
-/.classpath
-/.primary_build_tool
-/.project
 /.settings/
-/bazel-bin
-/bazel-high-availability
-/bazel-out
-/bazel-reviewers
-/bazel-testlogs
-/eclipse-out/
 /src/test/docker/gitvolume
 /src/test/docker/pgdata
 /src/test/docker/shareddir
diff --git a/BUILD b/BUILD
index 3b896ff..5975b31 100644
--- a/BUILD
+++ b/BUILD
@@ -19,7 +19,10 @@
         "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/high-availability",
     ],
     resources = glob(["src/main/resources/**/*"]),
-    deps = ["@jgroups//jar"],
+    deps = [
+      "@jgroups//jar",
+      "@global-refdb//jar",
+    ],
 )
 
 junit_tests(
@@ -42,6 +45,7 @@
     visibility = ["//visibility:public"],
     exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":high-availability__plugin",
+        "@global-refdb//jar",
         "@wiremock//jar",
     ],
 )
diff --git a/WORKSPACE b/WORKSPACE
deleted file mode 100644
index acca707..0000000
--- a/WORKSPACE
+++ /dev/null
@@ -1,19 +0,0 @@
-workspace(name = "high_availability")
-
-load("//:bazlets.bzl", "load_bazlets")
-
-load_bazlets(
-    commit = "8dc0767541f16b35d2136eccebffd9ebe2b81133",
-    #local_path = "/home/<user>/projects/bazlets",
-)
-
-load(
-    "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
-    "gerrit_api",
-)
-
-gerrit_api()
-
-load("//:external_plugin_deps.bzl", "external_plugin_deps")
-
-external_plugin_deps()
diff --git a/bazlets.bzl b/bazlets.bzl
deleted file mode 100644
index f089af4..0000000
--- a/bazlets.bzl
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
-
-NAME = "com_googlesource_gerrit_bazlets"
-
-def load_bazlets(
-        commit,
-        local_path = None):
-    if not local_path:
-        git_repository(
-            name = NAME,
-            remote = "https://gerrit.googlesource.com/bazlets",
-            commit = commit,
-        )
-    else:
-        native.local_repository(
-            name = NAME,
-            path = local_path,
-        )
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 0f8b43b..9af89f0 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -12,3 +12,9 @@
         artifact = "org.jgroups:jgroups:3.6.15.Final",
         sha1 = "755afcfc6c8a8ea1e15ef0073417c0b6e8c6d6e4",
     )
+
+    maven_jar(
+        name = "global-refdb",
+        artifact = "com.gerritforge:global-refdb:3.3.2.1:jdk8",
+        sha1 = "7a293d577665dfc6f4d36371af21c4a3f7177b23",
+    )
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 af1c4c3..f28b7c9 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -17,6 +17,7 @@
 import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
@@ -24,12 +25,11 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
@@ -40,7 +40,10 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
 
 @Singleton
 public class Configuration {
@@ -48,6 +51,8 @@
 
   public static final int DEFAULT_NUM_STRIPED_LOCKS = 10;
   public static final int DEFAULT_TIMEOUT_MS = 5000;
+  public static final String PLUGIN_NAME = "high-availability";
+  public static final String PLUGIN_CONFIG_FILE = PLUGIN_NAME + ".config";
 
   // common parameter to peerInfo section
   static final String PEER_INFO_SECTION = "peerInfo";
@@ -72,17 +77,20 @@
   private PeerInfoStatic peerInfoStatic;
   private PeerInfoJGroups peerInfoJGroups;
   private HealthCheck healthCheck;
+  private final SharedRefDbConfiguration sharedRefDb;
 
   public enum PeerInfoStrategy {
     JGROUPS,
     STATIC
   }
 
-  @VisibleForTesting
   @Inject
-  public Configuration(
-      PluginConfigFactory pluginConfigFactory, @PluginName String pluginName, SitePaths site) {
-    Config cfg = pluginConfigFactory.getGlobalPluginConfig(pluginName);
+  Configuration(SitePaths sitePaths) {
+    this(getConfigFile(sitePaths, PLUGIN_CONFIG_FILE), sitePaths);
+  }
+
+  @VisibleForTesting
+  public Configuration(Config cfg, SitePaths site) {
     main = new Main(site, cfg);
     autoReindex = new AutoReindex(cfg);
     peerInfo = new PeerInfo(cfg);
@@ -103,6 +111,20 @@
     index = new Index(cfg);
     websession = new Websession(cfg);
     healthCheck = new HealthCheck(cfg);
+    sharedRefDb = new SharedRefDbConfiguration(cfg, PLUGIN_NAME);
+  }
+
+  private static FileBasedConfig getConfigFile(SitePaths sitePaths, String configFileName) {
+    FileBasedConfig cfg =
+        new FileBasedConfig(sitePaths.etc_dir.resolve(configFileName).toFile(), FS.DETECTED);
+    String fileConfigFileName = cfg.getFile().getPath();
+    try {
+      log.atInfo().log("Loading configuration from %s", fileConfigFileName);
+      cfg.load();
+    } catch (IOException | ConfigInvalidException e) {
+      log.atSevere().withCause(e).log("Unable to load configuration from " + fileConfigFileName);
+    }
+    return cfg;
   }
 
   public Main main() {
@@ -153,6 +175,10 @@
     return healthCheck;
   }
 
+  public SharedRefDbConfiguration sharedRefDb() {
+    return sharedRefDb;
+  }
+
   private static int getInt(Config cfg, String section, String name, int defaultValue) {
     try {
       return cfg.getInt(section, name, defaultValue);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
index 744fac9..ddc294b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -21,7 +21,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderModule;
 import com.ericsson.gerrit.plugins.highavailability.index.IndexModule;
 import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfoModule;
-import com.google.inject.AbstractModule;
+import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
@@ -29,7 +29,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 
-class Module extends AbstractModule {
+class Module extends LifecycleModule {
   private final Configuration config;
 
   @Inject
@@ -55,6 +55,10 @@
       install(new AutoReindexModule());
     }
     install(new PeerInfoModule(config.peerInfo().strategy()));
+
+    if (config.sharedRefDb().getSharedRefDb().isEnabled()) {
+      listener().to(PluginStartup.class);
+    }
   }
 
   @Provides
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/PluginStartup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/PluginStartup.java
new file mode 100644
index 0000000..5c19eec
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/PluginStartup.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2020 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.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+/**
+ * Purpose of this class is to copy SharedRefDatabaseWrapper instance from DB injector to plugin
+ * injector. This allows to bind different global ref-db implementations.
+ */
+public class PluginStartup implements LifecycleListener {
+  private SharedRefDatabaseWrapper sharedRefDb;
+  private Injector injector;
+
+  @Inject
+  public PluginStartup(SharedRefDatabaseWrapper sharedRefDb, Injector injector) {
+    this.sharedRefDb = sharedRefDb;
+    this.injector = injector;
+  }
+
+  @Override
+  public void start() {
+    injector.injectMembers(sharedRefDb);
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
new file mode 100644
index 0000000..74db7d2
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2020 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.gerritforge.gerrit.globalrefdb.validation.BatchRefUpdateValidator;
+import com.gerritforge.gerrit.globalrefdb.validation.LockWrapper;
+import com.gerritforge.gerrit.globalrefdb.validation.Log4jSharedRefLogger;
+import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbBatchRefUpdate;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefUpdate;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRepository;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefLogger;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Scopes;
+
+public class ValidationModule extends FactoryModule {
+  final Configuration configuration;
+
+  @Inject
+  public ValidationModule(Configuration configuration) {
+    this.configuration = configuration;
+  }
+
+  @Override
+  protected void configure() {
+    factory(SharedRefDbRepository.Factory.class);
+    factory(SharedRefDbRefDatabase.Factory.class);
+    factory(SharedRefDbRefUpdate.Factory.class);
+    factory(SharedRefDbBatchRefUpdate.Factory.class);
+    factory(RefUpdateValidator.Factory.class);
+    factory(BatchRefUpdateValidator.Factory.class);
+
+    bind(SharedRefDbConfiguration.class).toInstance(configuration.sharedRefDb());
+
+    bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
+    bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+    factory(LockWrapper.Factory.class);
+
+    bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class);
+
+    if (configuration.sharedRefDb().getSharedRefDb().getEnforcementRules().isEmpty()) {
+      bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
+    } else {
+      bind(SharedRefEnforcement.class)
+          .to(CustomSharedRefEnforcementByProject.class)
+          .in(Scopes.SINGLETON);
+    }
+  }
+}
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 76c2e44..c21ccae 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
@@ -19,9 +19,9 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.common.collect.Streams;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.entities.AccountGroupByIdAudit;
 import com.google.gerrit.entities.AccountGroupMemberAudit;
+import com.google.gerrit.entities.GroupReference;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.group.InternalGroup;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java
similarity index 65%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java
index de02912..48f3bff 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java
@@ -16,56 +16,50 @@
 
 import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
 import com.google.common.base.Strings;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.events.EventGson;
 import com.google.gson.Gson;
 import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 @Singleton
-class GsonParser {
+class CacheKeyJsonParser {
   private final Gson gson;
 
   @Inject
-  public GsonParser(@EventGson Gson gson) {
+  public CacheKeyJsonParser(@EventGson Gson gson) {
     this.gson = gson;
   }
 
   public Object fromJson(String cacheName, String jsonString) {
     JsonElement json = gson.fromJson(Strings.nullToEmpty(jsonString), JsonElement.class);
-    Object key;
+    Supplier<JsonElement> id = Suppliers.memoize(() -> json.getAsJsonObject().get("id"));
+    Supplier<JsonElement> uuid = Suppliers.memoize(() -> json.getAsJsonObject().get("uuid"));
+
     // Need to add a case for 'adv_bases'
-    if (!json.isJsonObject()) {
-      return json.getAsString();
-    }
-    JsonObject asJsonObject = json.getAsJsonObject();
     switch (cacheName) {
       case Constants.ACCOUNTS:
-        key = asJsonObject.has("id") ? Account.id(asJsonObject.get("id").getAsInt()) : null;
-        break;
+        return id.get() == null ? null : Account.id(id.get().getAsInt());
       case Constants.GROUPS:
-        key = asJsonObject.has("id") ? AccountGroup.id(asJsonObject.get("id").getAsInt()) : null;
-        break;
+        return id.get() == null ? null : AccountGroup.id(id.get().getAsInt());
       case Constants.GROUPS_BYINCLUDE:
       case Constants.GROUPS_MEMBERS:
-        key =
-            asJsonObject.has("uuid")
-                ? AccountGroup.uuid(asJsonObject.get("uuid").getAsString())
-                : null;
-        break;
+        return uuid.get() == null ? null : AccountGroup.uuid(uuid.get().getAsString());
       case Constants.PROJECT_LIST:
-        key = gson.fromJson(json, Object.class);
-        break;
+        return gson.fromJson(json, Object.class);
+      case Constants.PROJECTS:
+        return Project.nameKey(json.getAsString());
       default:
         try {
-          key = gson.fromJson(json, String.class);
+          return gson.fromJson(json, String.class);
         } catch (Exception e) {
-          key = gson.fromJson(json, Object.class);
+          return gson.fromJson(json, Object.class);
         }
     }
-    return key;
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
index 60e782f..86d64d2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
@@ -34,13 +34,14 @@
   private static final long serialVersionUID = -1L;
 
   private final ForwardedCacheEvictionHandler forwardedCacheEvictionHandler;
-  private final GsonParser gson;
+  private final CacheKeyJsonParser cacheKeyParser;
 
   @Inject
   CacheRestApiServlet(
-      ForwardedCacheEvictionHandler forwardedCacheEvictionHandler, GsonParser gson) {
+      ForwardedCacheEvictionHandler forwardedCacheEvictionHandler,
+      CacheKeyJsonParser cacheKeyParser) {
     this.forwardedCacheEvictionHandler = forwardedCacheEvictionHandler;
-    this.gson = gson;
+    this.cacheKeyParser = cacheKeyParser;
   }
 
   @Override
@@ -51,7 +52,7 @@
       String cacheName = params.get(CACHENAME_INDEX);
       String json = req.getReader().readLine();
       forwardedCacheEvictionHandler.evict(
-          CacheEntry.from(cacheName, gson.fromJson(cacheName, json)));
+          CacheEntry.from(cacheName, cacheKeyParser.fromJson(cacheName, json)));
       rsp.setStatus(SC_NO_CONTENT);
     } catch (CacheNotFoundException e) {
       log.atSevere().log("Failed to process eviction request: %s", e.getMessage());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
index 945cefa..1a2ebf7 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -17,7 +17,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.HumanComment;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeFinder;
@@ -178,7 +178,7 @@
   private long getTsFromChangeAndDraftComments(ChangeNotes notes) {
     Change change = notes.getChange();
     Timestamp changeTs = change.getLastUpdatedOn();
-    for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) {
+    for (HumanComment comment : commentsUtil.draftByChange(changeNotes.get())) {
       Timestamp commentTs = comment.writtenOn;
       changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
     }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 83d3862..80117e2 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -7,10 +7,10 @@
 * sharing the git repositories using a shared file system (e.g. NFS)
 * behind a load balancer (e.g. HAProxy)
 
-Currently, the mode supported is one active instance and multiple backup
-(passive) instances but eventually the plan is to support `n` active instances.
-In the active/passive mode, the active instance is handling all traffic while
-the passives are kept updated to be always ready to take over.
+Default mode is one active instance and multiple backup (passive) instances
+but `n` active instances can be configured. In the active/passive mode, the active
+instance is handling all traffic while the passives are kept updated to be always
+ready to take over.
 
 Even if git repositories are shared by the instances, there are a few areas
 of concern in order to be able to switch traffic between instances in a
@@ -46,7 +46,7 @@
 The built-in Gerrit H2 based web session cache is replaced with a file based
 implementation that is shared amongst the instances.
 
-## Setup
+## Active/passive setup
 
 Prerequisites:
 
@@ -104,6 +104,74 @@
 For further information and supported options, refer to [config](config.md)
 documentation.
 
+## Active/active setup
+
+Prerequisites:
+
+* Git repositories must be located on a shared file system
+* A directory on a shared file system must be available for @PLUGIN@ to use
+* An implementation of global-refdb (e.g. Zookeeper) must be accessible from all the active
+instances
+
+For the instances:
+
+* Configure gerrit.basePath in gerrit.config to the shared repositories location
+* Configure gerrit.serverId in gerrit.config based on [config](config.md)'s introduction
+* Install and configure this @PLUGIN@ plugin [further](config.md) or based on example
+configuration
+* Install @PLUGIN@ plugin as a database module in $GERRIT_SITE/lib(please note that
+@PLUGIN plugin must be installed as a plugin and as a database module) and add
+`installDbModule = com.ericsson.gerrit.plugins.highavailability.ValidationModule`
+to the gerrit section in gerrit.config
+* Install [global-refdb library](https://mvnrepository.com/artifact/com.gerritforge/global-refdb) as a library module in $GERRIT_SITE/lib and add
+`installModule = com.gerritforge.gerrit.globalrefdb.validation.LibModule` to the gerrit
+section in gerrit.config
+* Install and configure [zookeeper-refdb plugin](https://gerrit-ci.gerritforge.com/view/Plugins-master/job/plugin-zookeeper-refdb-bazel-master) based on [config.md](https://gerrit.googlesource.com/plugins/zookeeper-refdb/+/refs/heads/master/src/main/resources/Documentation/config.md)
+* Configure ref-database.enabled = true in @PLUGIN@.config to enable validation with
+global-refdb.
+
+Here is an example of the minimal @PLUGIN@.config:
+
+Active instance one
+
+```
+[main]
+  sharedDirectory = /directory/accessible/from/both/instances
+
+[peerInfo "static"]
+  url = http://backupNodeHost1:8081/
+
+[http]
+  user = username
+  password = password
+
+[ref-database]
+  enabled = true
+```
+
+Active instance two
+
+```
+[main]
+  sharedDirectory = /directory/accessible/from/both/instances
+
+[peerInfo "static"]
+  url = http://primaryNodeHost:8080/
+
+[http]
+  user = username
+  password = password
+
+[ref-database]
+  enabled = true
+```
+
+Minimal zookeeper-refdb.config for both active instances:
+
+```
+[ref-database "zookeeper"]
+  connectString = zookeeperhost:2181
+```
 ### Last index update timestamp storage
 
 The plugin keeps track of the timestamp when it lastly updated an index.
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index 1d86dd0..615c1ac 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -1,53 +1,7 @@
-# Build
+Build
+=====
 
-This plugin can be built with Bazel, and two build modes are supported:
-
-* Standalone
-* In Gerrit tree
-
-Standalone build mode is recommended, as this mode doesn't require local Gerrit
-tree to exist. Moreover, there are some limitations and additional manual steps
-required when building in Gerrit tree mode (see corresponding sections).
-
-## Build standalone
-
-To build the plugin, issue the following command:
-
-```
-  bazel build @PLUGIN@
-```
-
-The output is created in
-
-```
-  bazel-bin/@PLUGIN@.jar
-```
-
-To package the plugin sources run:
-
-```
-  bazel build lib@PLUGIN@__plugin-src.jar
-```
-
-The output is created in:
-
-```
-  bazel-bin/lib@PLUGIN@__plugin-src.jar
-```
-
-To execute the tests run:
-
-```
-  bazel test //...
-```
-
-This project can be imported into the Eclipse IDE:
-
-```
-  ./tools/eclipse/project.sh
-```
-
-## Build in Gerrit tree
+This plugin can be built with Bazel in the Gerrit tree.
 
 Clone or link this plugin to the plugins directory of Gerrit's
 source tree. Put the external dependency Bazel build file into
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 3b1fee2..2f7d41a 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -239,6 +239,32 @@
 ```healthcheck.enable```
 :   Whether to enable the health check endpoint. Defaults to 'true'.
 
+```ref-database.enabled```
+:   Enable the use of a global ref-database. Defaults to 'false'.
+
+```ref-database.enforcementRules.<policy>```
+:   Level of consistency enforcement across sites on a project:refs basis.
+    Supports two values for enforcing the policy on multiple projects or refs.
+    If the project or ref is omitted, apply the policy to all projects or all refs.
+
+    The <policy> can be one of the following values:
+
+    1. REQUIRED - Throw an exception if a git ref-update is processed against
+    a local ref not yet in sync with the global ref-database.
+    The user transaction is cancelled. LOCK_FAILURE is reported upstream.
+
+    2. IGNORED - Do not validate against the global ref-database.
+
+    *Example:*
+    ```
+    [ref-database "enforcementRules"]
+       IGNORED = AProject:/refs/heads/feature
+    ```
+
+    Ignore the alignment with the global ref-db for AProject on refs/heads/feature.
+
+    Defaults to no rule. All projects are REQUIRED to be consistent on all refs.
+
 File 'gerrit.config'
 --------------------
 
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 3444625..19cc48d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -63,7 +63,6 @@
 import static com.google.common.truth.Truth8.assertThat;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration.PeerInfoStrategy;
 import com.google.common.collect.ImmutableList;
@@ -105,12 +104,11 @@
   @Before
   public void setUp() throws IOException {
     globalPluginConfig = new Config();
-    when(pluginConfigFactoryMock.getGlobalPluginConfig(PLUGIN_NAME)).thenReturn(globalPluginConfig);
     sitePaths = new SitePaths(SITE_PATH);
   }
 
   private Configuration getConfiguration() {
-    return new Configuration(pluginConfigFactoryMock, PLUGIN_NAME, sitePaths);
+    return new Configuration(globalPluginConfig, sitePaths);
   }
 
   @Test
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 ce11242..7ab4a1c 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
@@ -23,13 +23,13 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Account.Id;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.AccountGroup.UUID;
 import com.google.gerrit.entities.AccountGroupByIdAudit;
 import com.google.gerrit.entities.AccountGroupMemberAudit;
+import com.google.gerrit.entities.GroupReference;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.group.InternalGroup;
@@ -54,7 +54,7 @@
   @Mock private Groups groups;
   @Mock private GitRepositoryManager repoManager;
   @Mock private AllUsersName allUsers;
-  @Mock private GroupReference groupReference;
+  private GroupReference groupReference;
 
   private GroupReindexRunnable groupReindexRunnable;
   private static UUID uuid;
@@ -64,7 +64,7 @@
     groupReindexRunnable =
         new GroupReindexRunnable(indexer, indexTs, ctx, groups, repoManager, allUsers);
     uuid = UUID.parse("123");
-    when(groupReference.getUUID()).thenReturn(uuid);
+    groupReference = GroupReference.create(uuid, "123");
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java
index 7077319..e6633d8 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java
@@ -15,7 +15,6 @@
 package com.ericsson.gerrit.plugins.highavailability.cache;
 
 import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
@@ -46,10 +45,8 @@
 
   @Before
   public void setUp() throws IOException {
-    when(pluginConfigFactoryMock.getGlobalPluginConfig(PLUGIN_NAME)).thenReturn(new Config());
     defaultCacheMatcher =
-        new CachePatternMatcher(
-            new Configuration(pluginConfigFactoryMock, PLUGIN_NAME, new SitePaths(SITE_PATH)));
+        new CachePatternMatcher(new Configuration(new Config(), new SitePaths(SITE_PATH)));
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParserTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java
similarity index 81%
rename from src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParserTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java
index 5b76598..3df4e67 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParserTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java
@@ -19,14 +19,15 @@
 import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.events.EventGsonProvider;
 import com.google.gson.Gson;
 import org.junit.Test;
 
-public class GsonParserTest {
+public class CacheKeyJsonParserTest {
   private static final Object EMPTY_JSON = "{}";
   private final Gson gson = new EventGsonProvider().get();
-  private final GsonParser objectUnderTest = new GsonParser(gson);
+  private final CacheKeyJsonParser objectUnderTest = new CacheKeyJsonParser(gson);
 
   @Test
   public void accountIDParse() {
@@ -51,10 +52,17 @@
   }
 
   @Test
+  public void projectNameKeyParse() {
+    Project.NameKey name = Project.nameKey("foo");
+    String json = gson.toJson(name);
+    assertThat(name).isEqualTo(objectUnderTest.fromJson(Constants.PROJECTS, json));
+  }
+
+  @Test
   public void stringParse() {
     String key = "key";
     String json = gson.toJson(key);
-    assertThat(key).isEqualTo(objectUnderTest.fromJson(Constants.PROJECTS, json));
+    assertThat(key).isEqualTo(objectUnderTest.fromJson("string-keyed-cache", json));
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
index 44dd7f0..e4ffd59 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
@@ -46,7 +46,8 @@
   @Before
   public void setUp() {
     servlet =
-        new CacheRestApiServlet(forwardedCacheEvictionHandlerMock, new GsonParser(new Gson()));
+        new CacheRestApiServlet(
+            forwardedCacheEvictionHandlerMock, new CacheKeyJsonParser(new Gson()));
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
index 5843908..83f86d0 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
@@ -25,15 +25,19 @@
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
 import static com.google.common.truth.Truth.assertThat;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
 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.config.GlobalPluginConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import org.apache.http.HttpStatus;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -50,17 +54,23 @@
 
   @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
 
+  @Inject SitePaths sitePaths;
+
   @Override
   public void setUpTestPlugin() throws Exception {
     givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
+    FileBasedConfig fileBasedConfig =
+        new FileBasedConfig(
+            sitePaths.etc_dir.resolve(Configuration.PLUGIN_CONFIG_FILE).toFile(), FS.DETECTED);
+    fileBasedConfig.setString("peerInfo", "static", "url", URL);
+    fileBasedConfig.setInt("http", null, "retryInterval", 100);
+    fileBasedConfig.save();
     beforeAction();
     super.setUpTestPlugin();
   }
 
   @Test
   @UseLocalDisk
-  @GlobalPluginConfig(pluginName = "high-availability", name = "peerInfo.static.url", value = URL)
-  @GlobalPluginConfig(pluginName = "high-availability", name = "http.retryInterval", value = "100")
   public void testIndexForwarding() throws Exception {
     String expectedRequest = getExpectedRequest();
     CountDownLatch expectedRequestLatch = new CountDownLatch(1);
diff --git a/tools/BUILD b/tools/BUILD
deleted file mode 100644
index c5ed0b7..0000000
--- a/tools/BUILD
+++ /dev/null
@@ -1 +0,0 @@
-# Empty file required by Bazel
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD
deleted file mode 100644
index c5ed0b7..0000000
--- a/tools/bzl/BUILD
+++ /dev/null
@@ -1 +0,0 @@
-# Empty file required by Bazel
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl
deleted file mode 100644
index c921d01..0000000
--- a/tools/bzl/classpath.bzl
+++ /dev/null
@@ -1,6 +0,0 @@
-load(
-    "@com_googlesource_gerrit_bazlets//tools:classpath.bzl",
-    _classpath_collector = "classpath_collector",
-)
-
-classpath_collector = _classpath_collector
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
deleted file mode 100644
index 97307bd..0000000
--- a/tools/bzl/junit.bzl
+++ /dev/null
@@ -1,6 +0,0 @@
-load(
-    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
-    _junit_tests = "junit_tests",
-)
-
-junit_tests = _junit_tests
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
deleted file mode 100644
index 35ea8ce..0000000
--- a/tools/bzl/maven_jar.bzl
+++ /dev/null
@@ -1,3 +0,0 @@
-load("@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl", _maven_jar = "maven_jar")
-
-maven_jar = _maven_jar
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
deleted file mode 100644
index 4d2dbdd..0000000
--- a/tools/bzl/plugin.bzl
+++ /dev/null
@@ -1,10 +0,0 @@
-load(
-    "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
-    _gerrit_plugin = "gerrit_plugin",
-    _plugin_deps = "PLUGIN_DEPS",
-    _plugin_test_deps = "PLUGIN_TEST_DEPS",
-)
-
-gerrit_plugin = _gerrit_plugin
-PLUGIN_DEPS = _plugin_deps
-PLUGIN_TEST_DEPS = _plugin_test_deps
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
deleted file mode 100644
index 8a8bd40..0000000
--- a/tools/eclipse/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("//tools/bzl:classpath.bzl", "classpath_collector")
-
-classpath_collector(
-    name = "main_classpath_collect",
-    testonly = 1,
-    deps = [
-        "//:high-availability__plugin_test_deps",
-    ],
-)
diff --git a/tools/eclipse/project.sh b/tools/eclipse/project.sh
deleted file mode 100755
index b6dd909..0000000
--- a/tools/eclipse/project.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-# 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.
-
-path=$(bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project \
-    --output location | sed s/BUILD:.*//)
-${path}project.py -n high-availability -r . "$@"
-
diff --git a/tools/sonar/sonar.sh b/tools/sonar/sonar.sh
deleted file mode 100755
index 39df185..0000000
--- a/tools/sonar/sonar.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-# 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.
-`bazel query @com_googlesource_gerrit_bazlets//tools/sonar:sonar --output location | sed s/BUILD:.*//`sonar.py
diff --git a/tools/workspace_status.py b/tools/workspace_status.py
deleted file mode 100644
index 887e037..0000000
--- a/tools/workspace_status.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python
-
-# This script will be run by bazel when the build process starts to
-# generate key-value information that represents the status of the
-# workspace. The output should be like
-#
-# KEY1 VALUE1
-# KEY2 VALUE2
-#
-# If the script exits with non-zero code, it's considered as a failure
-# and the output will be discarded.
-
-from __future__ import print_function
-import subprocess
-import sys
-
-CMD = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty']
-
-
-def revision():
-    try:
-        return subprocess.check_output(CMD).strip().decode("utf-8")
-    except OSError as err:
-        print('could not invoke git: %s' % err, file=sys.stderr)
-        sys.exit(1)
-    except subprocess.CalledProcessError as err:
-        print('error using git: %s' % err, file=sys.stderr)
-        sys.exit(1)
-
-
-print("STABLE_BUILD_HIGH-AVAILABILITY_LABEL %s" % revision())