Merge branch 'stable-2.15' into stable-2.16

* stable-2.15:
  ForwardedIndex{Account,Group}Handler: Remove unthrown OrmException declaration

Change-Id: I59db7b8d6a09816ed4be3b87983902b6837bdabc
diff --git a/.gitignore b/.gitignore
index 0f6afe5..11f5ae1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@
 /bazel-testlogs
 /bazel-high-availability
 /eclipse-out/
+/.apt_generated/
diff --git a/WORKSPACE b/WORKSPACE
index c5ade9c..f6bb2d7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "1650318289e9981c01e9122c62679b814c7d7a48",
+    commit = "c827ba79413585ab9dfc1bbd0d7f609eedd6aa80",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
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 edd96d7..b834b49 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -21,6 +21,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 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;
@@ -39,12 +40,10 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 public class Configuration {
-  private static final Logger log = LoggerFactory.getLogger(Configuration.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   // common parameter to peerInfo section
   static final String PEER_INFO_SECTION = "peerInfo";
@@ -153,8 +152,8 @@
     try {
       return cfg.getInt(section, name, defaultValue);
     } catch (IllegalArgumentException e) {
-      log.error("invalid value for {}; using default value {}", name, defaultValue);
-      log.debug("Failed to retrieve integer value: {}", e.getMessage(), e);
+      log.atSevere().log("invalid value for %s; using default value %d", name, defaultValue);
+      log.atFine().withCause(e).log("Failed to retrieve integer value");
       return defaultValue;
     }
   }
@@ -234,9 +233,7 @@
 
     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());
-      }
+      log.atFine().log("Strategy: %s", strategy.name());
     }
 
     public PeerInfoStrategy strategy() {
@@ -257,7 +254,7 @@
               .filter(s -> !s.isEmpty())
               .map(s -> CharMatcher.is('/').trimTrailingFrom(s))
               .collect(Collectors.toSet());
-      log.debug("Urls: {}", urls);
+      log.atFine().log("Urls: %s", urls);
     }
 
     public Set<String> urls() {
@@ -273,7 +270,7 @@
 
     private PeerInfoJGroups(Config cfg) {
       myUrl = trimTrailingSlash(cfg.getString(PEER_INFO_SECTION, JGROUPS_SUBSECTION, MY_URL_KEY));
-      log.debug("My Url: {}", myUrl);
+      log.atFine().log("My Url: %s", myUrl);
     }
 
     public String myUrl() {
@@ -302,12 +299,12 @@
     private JGroups(SitePaths site, Config cfg) {
       String[] skip = cfg.getStringList(JGROUPS_SECTION, null, SKIP_INTERFACE_KEY);
       skipInterface = skip.length == 0 ? DEFAULT_SKIP_INTERFACE_LIST : ImmutableList.copyOf(skip);
-      log.debug("Skip interface(s): {}", skipInterface);
+      log.atFine().log("Skip interface(s): %s", skipInterface);
       clusterName = getString(cfg, JGROUPS_SECTION, null, CLUSTER_NAME_KEY, DEFAULT_CLUSTER_NAME);
-      log.debug("Cluster name: {}", clusterName);
+      log.atFine().log("Cluster name: %s", clusterName);
       protocolStack = getProtocolStack(cfg, site);
-      log.debug(
-          "Protocol stack config {}",
+      log.atFine().log(
+          "Protocol stack config %s",
           protocolStack.isPresent() ? protocolStack.get() : "not configured, using default stack.");
     }
 
@@ -405,8 +402,8 @@
       try {
         return cfg.getBoolean(section, name, defaultValue);
       } catch (IllegalArgumentException e) {
-        log.error("invalid value for {}; using default value {}", name, defaultValue);
-        log.debug("Failed to retrieve boolean value: {}", e.getMessage(), e);
+        log.atSevere().log("invalid value for %s; using default value %s", name, defaultValue);
+        log.atFine().withCause(e).log("Failed to retrieve boolean value");
         return defaultValue;
       }
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
index 487f3da..de156a2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
@@ -17,8 +17,10 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexAccountHandler;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+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;
@@ -26,11 +28,9 @@
 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<Account> {
-  private static final Logger log = LoggerFactory.getLogger(AccountReindexRunnable.class);
+public class AccountReindexRunnable extends ReindexRunnable<AccountState> {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final ForwardedIndexAccountHandler accountIdx;
 
@@ -48,21 +48,23 @@
   }
 
   @Override
-  protected Iterable<Account> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<AccountState> fetchItems(ReviewDb db) throws Exception {
     return accounts.all();
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Account a, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, 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);
+        log.atInfo().log(
+            "Index %s/%s/%s/%s", 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);
+      log.atSevere().withCause(e).log("Reindex failed");
     }
     return Optional.empty();
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexScheduler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexScheduler.java
index c0d90d1..ef7ae86 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexScheduler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AutoReindexScheduler.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.autoreindex;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
@@ -24,16 +25,15 @@
 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 static final FluentLogger log = FluentLogger.forEnclosingClass();
   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<>();
 
@@ -43,18 +43,21 @@
       WorkQueue workQueue,
       ChangeReindexRunnable changeReindex,
       AccountReindexRunnable accountReindex,
-      GroupReindexRunnable groupReindex) {
+      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, "HighAvailability-AutoReindex");
   }
 
   @Override
   public void start() {
     if (cfg.pollSec() > 0) {
-      log.info("Scheduling auto-reindex after {}s and every {}s", cfg.delaySec(), cfg.pollSec());
+      log.atInfo().log(
+          "Scheduling auto-reindex after %ds and every %ds", cfg.delaySec(), cfg.pollSec());
       futureTasks.add(
           executor.scheduleAtFixedRate(
               changeReindex, cfg.delaySec(), cfg.pollSec(), TimeUnit.SECONDS));
@@ -64,11 +67,15 @@
       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());
+      log.atInfo().log("Scheduling auto-reindex after %ds", 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));
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
index 1f5b56e..67c8325 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
@@ -18,6 +18,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 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.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -34,11 +35,9 @@
 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 static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final ForwardedIndexChangeHandler changeIdx;
 
@@ -98,13 +97,13 @@
     try {
       Timestamp changeTs = c.getLastUpdatedOn();
       if (changeTs.after(sinceTs)) {
-        log.info(
-            "Index {}/{}/{} was updated after {}", c.getProject(), c.getId(), changeTs, sinceTs);
+        log.atInfo().log(
+            "Index %s/%s/%s was updated after %s", 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);
+      log.atSevere().withCause(e).log("Reindex failed");
     }
     return Optional.empty();
   }
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 640457d..71a0280 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
@@ -14,74 +14,32 @@
 
 package com.ericsson.gerrit.plugins.highavailability.autoreindex;
 
-import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler;
-import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
-import com.google.common.collect.Streams;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.Id;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.group.db.Groups;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
-import java.io.IOException;
 import java.sql.Timestamp;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
-import java.util.stream.Stream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-public class GroupReindexRunnable extends ReindexRunnable<AccountGroup> {
-  private static final Logger log = LoggerFactory.getLogger(GroupReindexRunnable.class);
+public class GroupReindexRunnable extends ReindexRunnable<GroupReference> {
 
-  private final ForwardedIndexGroupHandler indexer;
+  private final Groups groups;
 
   @Inject
-  public GroupReindexRunnable(
-      ForwardedIndexGroupHandler indexer, IndexTs indexTs, OneOffRequestContext ctx) {
+  public GroupReindexRunnable(IndexTs indexTs, OneOffRequestContext ctx, Groups groups) {
     super(AbstractIndexRestApiServlet.IndexName.GROUP, indexTs, ctx);
-    this.indexer = indexer;
+    this.groups = groups;
   }
 
   @Override
-  protected ResultSet<AccountGroup> fetchItems(ReviewDb db) throws OrmException {
-    return db.accountGroups().all();
+  protected Iterable<GroupReference> fetchItems(ReviewDb db) throws Exception {
+    return groups.getAllGroupReferences()::iterator;
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, AccountGroup g, Timestamp sinceTs) {
-    try {
-      Id groupId = g.getId();
-      Stream<Timestamp> groupIdAudTs =
-          db.accountGroupByIdAud().byGroup(g.getId()).toList().stream()
-              .map(AccountGroupByIdAud::getRemovedOn)
-              .filter(Objects::nonNull);
-      List<AccountGroupMemberAudit> groupMembersAud =
-          db.accountGroupMembersAudit().byGroup(groupId).toList();
-      Stream<Timestamp> groupMemberAudAddedTs =
-          groupMembersAud.stream().map(ga -> ga.getKey().getAddedOn()).filter(Objects::nonNull);
-      Stream<Timestamp> groupMemberAudRemovedTs =
-          groupMembersAud.stream()
-              .map(AccountGroupMemberAudit::getRemovedOn)
-              .filter(Objects::nonNull);
-      Optional<Timestamp> groupLastTs =
-          Streams.concat(groupIdAudTs, groupMemberAudAddedTs, groupMemberAudRemovedTs)
-              .max(Comparator.naturalOrder());
-
-      if (groupLastTs.isPresent() && groupLastTs.get().after(sinceTs)) {
-        log.info("Index {}/{}/{}", g.getGroupUUID(), g.getName(), groupLastTs.get());
-        indexer.index(g.getGroupUUID(), Operation.INDEX, Optional.empty());
-        return groupLastTs;
-      }
-    } catch (OrmException | IOException e) {
-      log.error("Reindex failed", e);
-    }
+  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, GroupReference g, Timestamp sinceTs) {
     return Optional.empty();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
index 952fcd1..c4c0b0b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
@@ -16,12 +16,14 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet.IndexName;
+import com.google.common.flogger.FluentLogger;
 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.extensions.events.ProjectIndexedListener;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeFinder;
+import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gwtorm.server.SchemaFactory;
@@ -35,13 +37,14 @@
 import java.time.format.DateTimeFormatter;
 import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 public class IndexTs
-    implements ChangeIndexedListener, AccountIndexedListener, GroupIndexedListener {
-  private static final Logger log = LoggerFactory.getLogger(IndexTs.class);
+    implements ChangeIndexedListener,
+        AccountIndexedListener,
+        GroupIndexedListener,
+        ProjectIndexedListener {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
 
   private final Path dataDir;
@@ -53,6 +56,7 @@
   private volatile LocalDateTime changeTs;
   private volatile LocalDateTime accountTs;
   private volatile LocalDateTime groupTs;
+  private volatile LocalDateTime projectTs;
 
   class FlusherRunner implements Runnable {
 
@@ -61,6 +65,7 @@
       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) {
@@ -70,7 +75,7 @@
         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);
+          log.atSevere().withCause(e).log("Unable to update last timestamp for index %s", index);
         }
       }
     }
@@ -90,6 +95,11 @@
   }
 
   @Override
+  public void onProjectIndexed(String project) {
+    update(IndexName.PROJECT, LocalDateTime.now());
+  }
+
+  @Override
   public void onGroupIndexed(String uuid) {
     update(IndexName.GROUP, LocalDateTime.now());
   }
@@ -109,7 +119,7 @@
               ? LocalDateTime.now()
               : changeNotes.getChange().getLastUpdatedOn().toLocalDateTime());
     } catch (Exception e) {
-      log.warn("Unable to update the latest TS for change {}", id, e);
+      log.atWarning().withCause(e).log("Unable to update the latest TS for change %d", id);
     }
   }
 
@@ -126,7 +136,7 @@
         return Optional.of(LocalDateTime.parse(tsString, formatter));
       }
     } catch (Exception e) {
-      log.warn("Unable to read last timestamp for index {}", index, e);
+      log.atWarning().withCause(e).log("Unable to read last timestamp for index %s", index);
     }
     return Optional.empty();
   }
@@ -142,6 +152,9 @@
       case GROUP:
         groupTs = dateTime;
         break;
+      case PROJECT:
+        projectTs = dateTime;
+        break;
       default:
         throw new IllegalArgumentException("Unsupported index " + index);
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
new file mode 100644
index 0000000..582227d
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
@@ -0,0 +1,46 @@
+// 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.ericsson.gerrit.plugins.highavailability.autoreindex;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.inject.Inject;
+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(ReviewDb db) {
+    return projectCache.all();
+  }
+
+  @Override
+  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Project.NameKey g, Timestamp sinceTs) {
+    return Optional.empty();
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
index df3a0fd..7a5669e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
@@ -16,6 +16,7 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.common.base.Stopwatch;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
@@ -24,11 +25,9 @@
 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 static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final AbstractIndexRestApiServlet.IndexName itemName;
   private final OneOffRequestContext ctx;
@@ -49,7 +48,7 @@
     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);
+      log.atFine().log("Scanning for all the %ss after %s", itemNameString, newLastIndexTs);
       try (ManualRequestContext mctx = ctx.open();
           ReviewDb db = mctx.getReviewDbProvider().get()) {
         int count = 0;
@@ -63,27 +62,27 @@
               newLastIndexTs = maxTimestamp(newLastIndexTs, itemTs.get());
             }
           } catch (Exception e) {
-            log.error("Unable to reindex {} {}", itemNameString, c, e);
+            log.atSevere().withCause(e).log("Unable to reindex %s %s", itemNameString, c);
             errors++;
           }
         }
         long elapsedNanos = stopwatch.stop().elapsed(TimeUnit.NANOSECONDS);
         if (count > 0) {
-          log.info(
-              "{} {}s reindexed in {} msec ({}/sec), {} failed",
+          log.atInfo().log(
+              "%d %ss reindexed in %d msec (%d/sec), %d failed",
               count,
               itemNameString,
               elapsedNanos / 1000000L,
               (count * 1000L) / (elapsedNanos / 1000000L),
               errors);
         } else if (errors > 0) {
-          log.info("{} {}s failed to reindex", errors, itemNameString);
+          log.atInfo().log("%d %ss failed to reindex", errors, itemNameString);
         } else {
-          log.debug("Scanning finished");
+          log.atFine().log("Scanning finished");
         }
         indexTs.update(itemName, newLastIndexTs.toLocalDateTime());
       } catch (Exception e) {
-        log.error("Unable to scan " + itemNameString + "s", e);
+        log.atSevere().withCause(e).log("Unable to scan %ss", itemNameString);
       }
     }
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
index f42d115..025115c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
@@ -16,9 +16,9 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
-import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.inject.Inject;
 import java.util.concurrent.Executor;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java
index a848c20..28a9e8e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java
@@ -14,9 +14,9 @@
 
 package com.ericsson.gerrit.plugins.highavailability.event;
 
-import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.events.EventListener;
 import java.util.concurrent.Executor;
 
 public class EventModule extends LifecycleModule {
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 b3696fb..3d9fd60 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,14 +14,14 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.google.gerrit.common.EventBroker;
-import com.google.gerrit.common.EventListener;
-import com.google.gerrit.common.UserScopedEventListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventBroker;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.events.UserScopedEventListener;
 import com.google.gerrit.server.notedb.ChangeNotes.Factory;
 import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -30,8 +30,8 @@
 
   @Inject
   ForwardedAwareEventBroker(
-      DynamicSet<UserScopedEventListener> listeners,
-      DynamicSet<EventListener> unrestrictedListeners,
+      PluginSetContext<UserScopedEventListener> listeners,
+      PluginSetContext<EventListener> unrestrictedListeners,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
       Factory notesFactory,
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedCacheEvictionHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedCacheEvictionHandler.java
index c4c2219..27531b0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedCacheEvictionHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedCacheEvictionHandler.java
@@ -16,11 +16,10 @@
 
 import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
 import com.google.common.cache.Cache;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Evict cache entries. This class is meant to be used on the receiving side of the {@link
@@ -29,7 +28,7 @@
  */
 @Singleton
 public class ForwardedCacheEvictionHandler {
-  private static final Logger log = LoggerFactory.getLogger(ForwardedCacheEvictionHandler.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final DynamicMap<Cache<?, ?>> cacheMap;
 
@@ -55,10 +54,10 @@
       if (Constants.PROJECT_LIST.equals(entry.getCacheName())) {
         // One key is holding the list of projects
         cache.invalidateAll();
-        log.debug("Invalidated cache {}", entry.getCacheName());
+        log.atFine().log("Invalidated cache %s", entry.getCacheName());
       } else {
         cache.invalidate(entry.getKey());
-        log.debug("Invalidated cache {}[{}]", entry.getCacheName(), entry.getKey());
+        log.atFine().log("Invalidated cache %s[%s]", entry.getCacheName(), entry.getKey());
       }
     } finally {
       Context.unsetForwardedEvent();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
index 4e01abf..51db006 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
@@ -14,14 +14,13 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.google.gerrit.common.EventDispatcher;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Dispatch event to the {@link EventDispatcher}. This class is meant to be used on the receiving
@@ -30,7 +29,7 @@
  */
 @Singleton
 public class ForwardedEventHandler {
-  private static final Logger log = LoggerFactory.getLogger(ForwardedEventHandler.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final EventDispatcher dispatcher;
 
@@ -48,7 +47,7 @@
   public void dispatch(Event event) throws OrmException, PermissionBackendException {
     try {
       Context.setForwardedEvent(true);
-      log.debug("dispatching event {}", event.getType());
+      log.atFine().log("dispatching event %s", event.getType());
       dispatcher.postEvent(event);
     } finally {
       Context.unsetForwardedEvent();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
index 649ffc8..ba60830 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
@@ -41,7 +41,7 @@
   @Override
   protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) throws IOException {
     indexer.index(id);
-    log.debug("Account {} successfully indexed", id);
+    log.atFine().log("Account %s successfully indexed", id);
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
index d34959c..f68268a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -89,29 +89,27 @@
 
         if (checker.isChangeUpToDate(indexEvent)) {
           if (retryCount > 0) {
-            log.warn("Change {} has been eventually indexed after {} attempt(s)", id, retryCount);
+            log.atWarning().log(
+                "Change %s has been eventually indexed after %d attempt(s)", id, retryCount);
           } else {
-            log.debug("Change {} successfully indexed", id);
+            log.atFine().log("Change %s successfully indexed", id);
           }
         } else {
-          log.warn(
-              "Change {} seems too old compared to the event timestamp (event-Ts={} >> change-Ts={})",
-              id,
-              indexEvent,
-              checker);
+          log.atWarning().log(
+              "Change %s seems too old compared to the event timestamp (event-Ts=%s >> change-Ts=%s)",
+              id, indexEvent, checker);
           rescheduleIndex(id, indexEvent, retryCount + 1);
         }
       } else {
         indexer.delete(parseChangeId(id));
-        log.warn(
-            "Change {} could not be found in the local Git repository (eventTs={}), deleted from index",
-            id,
-            indexEvent);
+        log.atWarning().log(
+            "Change %s could not be found in the local Git repository (eventTs=%s), deleted from index",
+            id, indexEvent);
       }
     } catch (Exception e) {
       if (isCausedByNoSuchChangeException(e)) {
         indexer.delete(parseChangeId(id));
-        log.warn("Error trying to index Change {}. Deleted from index", id, e);
+        log.atWarning().withCause(e).log("Error trying to index Change %s. Deleted from index", id);
         return;
       }
 
@@ -128,25 +126,22 @@
 
   private void rescheduleIndex(String id, Optional<IndexEvent> indexEvent, int retryCount) {
     if (retryCount > maxTries) {
-      log.error(
-          "Change {} could not be indexed after {} retries. Change index could be stale.",
-          id,
-          retryCount);
+      log.atSevere().log(
+          "Change %s could not be indexed after %d retries. Change index could be stale.",
+          id, retryCount);
       return;
     }
 
-    log.warn(
-        "Retrying for the #{} time to index Change {} after {} msecs",
-        retryCount,
-        id,
-        retryInterval);
+    log.atWarning().log(
+        "Retrying for the #%d time to index Change %s after %d msecs",
+        retryCount, id, retryInterval);
     indexExecutor.schedule(
         () -> {
           try (ManualRequestContext ctx = oneOffCtx.open()) {
             Context.setForwardedEvent(true);
             doIndex(id, indexEvent, retryCount);
           } catch (Exception e) {
-            log.warn("Change {} could not be indexed", id, e);
+            log.atWarning().withCause(e).log("Change %s could not be indexed", id);
           }
         },
         retryInterval,
@@ -156,7 +151,7 @@
   @Override
   protected void doDelete(String id, Optional<IndexEvent> indexEvent) throws IOException {
     indexer.delete(parseChangeId(id));
-    log.debug("Change {} successfully deleted from index", id);
+    log.atFine().log("Change %s successfully deleted from index", id);
   }
 
   private static Change.Id parseChangeId(String id) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
index 7c6d723..fb4d2f2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
@@ -42,7 +42,7 @@
   protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent)
       throws IOException {
     indexer.index(uuid);
-    log.debug("Group {} successfully indexed", uuid);
+    log.atFine().log("Group %s successfully indexed", uuid);
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
new file mode 100644
index 0000000..5ae4cc0
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
@@ -0,0 +1,52 @@
+// 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.ericsson.gerrit.plugins.highavailability.forwarder;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.google.gerrit.index.project.ProjectIndexer;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * Index a project using {@link ProjectIndexer}. This class is meant to be used on the receiving
+ * side of the {@link Forwarder} since it will prevent indexed project to be forwarded again causing
+ * an infinite forwarding loop between the 2 nodes. It will also make sure no concurrent indexing is
+ * done for the same project name.
+ */
+@Singleton
+public class ForwardedIndexProjectHandler extends ForwardedIndexingHandler<Project.NameKey> {
+  private final ProjectIndexer indexer;
+
+  @Inject
+  ForwardedIndexProjectHandler(ProjectIndexer indexer, Configuration config) {
+    super(config.index());
+    this.indexer = indexer;
+  }
+
+  @Override
+  protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent)
+      throws IOException {
+    indexer.index(projectName);
+    log.atFine().log("Project %s successfully indexed", projectName);
+  }
+
+  @Override
+  protected void doDelete(Project.NameKey projectName, Optional<IndexEvent> indexEvent) {
+    throw new UnsupportedOperationException("Delete from project index not supported");
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
index bfedf3f..78e103d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
@@ -15,13 +15,12 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.google.common.flogger.FluentLogger;
 import com.google.common.util.concurrent.Striped;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.locks.Lock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Base class to handle forwarded indexing. This class is meant to be extended by classes used on
@@ -30,7 +29,7 @@
  * indexing is done for the same id.
  */
 public abstract class ForwardedIndexingHandler<T> {
-  protected final Logger log = LoggerFactory.getLogger(getClass());
+  protected static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   public enum Operation {
     INDEX,
@@ -64,7 +63,7 @@
    */
   public void index(T id, Operation operation, Optional<IndexEvent> indexEvent)
       throws IOException, OrmException {
-    log.debug("{} {} {}", operation, id, indexEvent);
+    log.atFine().log("%s %s %s", operation, id, indexEvent);
     try {
       Context.setForwardedEvent(true);
       Lock idLock = idLocks.get(id);
@@ -78,7 +77,7 @@
             doDelete(id, indexEvent);
             break;
           default:
-            log.error("unexpected operation: {}", operation);
+            log.atSevere().log("unexpected operation: %s", operation);
             break;
         }
       } finally {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedProjectListUpdateHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedProjectListUpdateHandler.java
index 93e1186..84f4675 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedProjectListUpdateHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedProjectListUpdateHandler.java
@@ -14,12 +14,12 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.io.IOException;
 
 /**
  * Update project list cache. This class is meant to be used on the receiving side of the {@link
@@ -28,8 +28,7 @@
  */
 @Singleton
 public class ForwardedProjectListUpdateHandler {
-  private static final Logger log =
-      LoggerFactory.getLogger(ForwardedProjectListUpdateHandler.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final ProjectCache projectCache;
 
@@ -43,17 +42,18 @@
    *
    * @param projectName the name of the project to add or remove.
    * @param remove true to remove, false to add project.
+   * @throws IOException
    */
-  public void update(String projectName, boolean remove) {
+  public void update(String projectName, boolean remove) throws IOException {
     Project.NameKey projectKey = new Project.NameKey(projectName);
     try {
       Context.setForwardedEvent(true);
       if (remove) {
         projectCache.remove(projectKey);
-        log.debug("Removed {} from project list", projectName);
+        log.atFine().log("Removed %s from project list", projectName);
       } else {
         projectCache.onCreateProject(projectKey);
-        log.debug("Added {} to project list", projectName);
+        log.atFine().log("Added %s to project list", projectName);
       }
     } finally {
       Context.unsetForwardedEvent();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
index b156563..49cdc8b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -57,6 +57,15 @@
   boolean indexGroup(String uuid, IndexEvent indexEvent);
 
   /**
+   * Forward a project indexing event to the other master.
+   *
+   * @param projectName the project to index.
+   * @param indexEvent the details of the index event.
+   * @return true if successful, otherwise false.
+   */
+  boolean indexProject(String projectName, IndexEvent indexEvent);
+
+  /**
    * Forward a stream event to the other master.
    *
    * @param event the event to forward.
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 15fdcfd..99a820e 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,8 +14,8 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.google.gerrit.common.EventDispatcher;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.events.EventDispatcher;
 import com.google.inject.AbstractModule;
 
 public class ForwarderModule extends AbstractModule {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
index 8c429de..410aa23 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -43,7 +43,8 @@
   public enum IndexName {
     CHANGE,
     ACCOUNT,
-    GROUP;
+    GROUP,
+    PROJECT;
 
     @Override
     public String toString() {
@@ -91,11 +92,11 @@
       rsp.setStatus(SC_NO_CONTENT);
     } catch (IOException e) {
       sendError(rsp, SC_CONFLICT, e.getMessage());
-      log.error("Unable to update {} index", indexName, e);
+      log.atSevere().withCause(e).log("Unable to update %s index", indexName);
     } catch (OrmException e) {
       String msg = String.format("Error trying to find %s", indexName);
       sendError(rsp, SC_NOT_FOUND, msg);
-      log.debug(msg, e);
+      log.atFine().withCause(e).log(msg);
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java
index 054f640..a76d8ea 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java
@@ -16,15 +16,14 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.flogger.FluentLogger;
 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 final FluentLogger log = FluentLogger.forEnclosingClass();
 
   protected static void setHeaders(HttpServletResponse rsp) {
     rsp.setContentType("text/plain");
@@ -35,7 +34,7 @@
     try {
       rsp.sendError(statusCode, message);
     } catch (IOException e) {
-      log.error("Failed to send error messsage: {}", e.getMessage(), e);
+      log.atSevere().withCause(e).log("Failed to send error messsage");
     }
   }
 }
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 033aaa7..ffc05a5 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
@@ -51,10 +51,10 @@
           CacheEntry.from(cacheName, GsonParser.fromJson(cacheName, json)));
       rsp.setStatus(SC_NO_CONTENT);
     } catch (CacheNotFoundException e) {
-      log.error("Failed to process eviction request: {}", e.getMessage());
+      log.atSevere().log("Failed to process eviction request: %s", e.getMessage());
       sendError(rsp, SC_BAD_REQUEST, e.getMessage());
     } catch (IOException e) {
-      log.error("Failed to process eviction request: {}", e.getMessage(), e);
+      log.atSevere().withCause(e).log("Failed to process eviction request");
       sendError(rsp, SC_BAD_REQUEST, e.getMessage());
     }
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
index 2636df4..9c5c5d9 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -59,10 +59,10 @@
       forwardedEventHandler.dispatch(getEventFromRequest(req));
       rsp.setStatus(SC_NO_CONTENT);
     } catch (OrmException e) {
-      log.debug("Error trying to find a change ", e);
+      log.atFine().withCause(e).log("Error trying to find a change");
       sendError(rsp, SC_NOT_FOUND, "Change not found\n");
     } catch (IOException | PermissionBackendException e) {
-      log.error("Unable to re-trigger event", e);
+      log.atSevere().withCause(e).log("Unable to re-trigger event");
       sendError(rsp, SC_BAD_REQUEST, e.getMessage());
     }
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
index 26943c2..0510aed 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.security.KeyManagementException;
@@ -37,12 +38,10 @@
 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 FluentLogger log = FluentLogger.forEnclosingClass();
   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;
@@ -101,7 +100,7 @@
       context.init(null, trustAllCerts, null);
       return context;
     } catch (KeyManagementException | NoSuchAlgorithmException e) {
-      log.warn("Error building SSLContext object", e);
+      log.atWarning().withCause(e).log("Error building SSLContext object");
       return null;
     }
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
index cfcd519..9a1e635 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
@@ -17,13 +17,12 @@
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
+import com.google.common.flogger.FluentLogger;
 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;
 
 class HttpResponseHandler implements ResponseHandler<HttpResult> {
 
@@ -45,7 +44,7 @@
     }
   }
 
-  private static final Logger log = LoggerFactory.getLogger(HttpResponseHandler.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   @Override
   public HttpResult handleResponse(HttpResponse response) {
@@ -63,7 +62,7 @@
       try {
         asString = EntityUtils.toString(entity);
       } catch (IOException e) {
-        log.error("Error parsing entity", e);
+        log.atSevere().withCause(e).log("Error parsing entity");
       }
     }
     return asString;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java
new file mode 100644
index 0000000..cc87442
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java
@@ -0,0 +1,36 @@
+// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexProjectHandler;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+class IndexProjectRestApiServlet extends AbstractIndexRestApiServlet<Project.NameKey> {
+  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));
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java
index e781a10..7a31ea0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java
@@ -14,12 +14,14 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -48,7 +50,12 @@
     setHeaders(rsp);
     String requestURI = req.getRequestURI();
     String projectName = requestURI.substring(requestURI.lastIndexOf('/') + 1);
-    forwardedProjectListUpdateHandler.update(Url.decode(projectName), delete);
-    rsp.setStatus(SC_NO_CONTENT);
+    try {
+      forwardedProjectListUpdateHandler.update(Url.decode(projectName), delete);
+      rsp.setStatus(SC_NO_CONTENT);
+    } catch (IOException e) {
+      log.atSevere().withCause(e).log("Unable to update project list");
+      sendError(rsp, SC_BAD_REQUEST, e.getMessage());
+    }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
index dc59117..044fce0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -21,6 +21,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
 import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo;
 import com.google.common.base.Joiner;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.server.events.Event;
@@ -34,8 +35,6 @@
 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 RestForwarder implements Forwarder {
   enum RequestMethod {
@@ -43,7 +42,7 @@
     DELETE
   }
 
-  private static final Logger log = LoggerFactory.getLogger(RestForwarder.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final HttpSession httpSession;
   private final String pluginRelativePath;
@@ -98,6 +97,12 @@
   }
 
   @Override
+  public boolean indexProject(String projectName, IndexEvent event) {
+    return execute(
+        RequestMethod.POST, "index project", "index/project", Url.encode(projectName), event);
+  }
+
+  @Override
   public boolean send(final Event event) {
     return execute(RequestMethod.POST, "send event", "event", event.type, event);
   }
@@ -182,41 +187,36 @@
     }
 
     boolean execute() {
-      log.debug("Executing {} {} towards {}", action, key, destination);
+      log.atFine().log("Executing %s %s towards %s", action, key, destination);
       for (; ; ) {
         try {
           execCnt++;
           tryOnce();
-          log.debug("{} {} towards {} OK", action, key, destination);
+          log.atFine().log("%s %s towards %s 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);
+          log.atFine().withCause(e).log(
+              "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries);
           if (!e.isRecoverable()) {
-            log.error(
-                "{} {} towards {} failed with unrecoverable error; giving up",
-                action,
-                key,
-                destination,
-                e);
+            log.atSevere().withCause(e).log(
+                "%s %s towards %s failed with unrecoverable error; giving up",
+                action, key, destination);
             return false;
           }
           if (execCnt >= maxTries) {
-            log.error(
-                "Failed to {} {} on {} after {} tries; giving up",
-                action,
-                key,
-                destination,
-                maxTries);
+            log.atSevere().log(
+                "Failed to %s %s on %s after %d tries; giving up",
+                action, key, destination, maxTries);
             return false;
           }
 
-          log.debug("Retrying to {} {} on {}", action, key, destination);
+          log.atFine().log("Retrying to %s %s on %s", action, key, destination);
           try {
             Thread.sleep(cfg.http().retryInterval());
           } catch (InterruptedException ie) {
-            log.error("{} {} towards {} was interrupted; giving up", action, key, destination, ie);
+            log.atSevere().withCause(ie).log(
+                "%s %s towards %s was interrupted; giving up", action, key, destination);
             Thread.currentThread().interrupt();
             return false;
           }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
index 184157d..589bbef 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
@@ -22,6 +22,7 @@
     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/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java
index 6d98d83..0f8125e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java
@@ -20,6 +20,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
 
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.annotations.PluginData;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.permissions.PermissionBackend;
@@ -34,12 +35,10 @@
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 public class HealthServlet extends HttpServlet {
-  private static final Logger log = LoggerFactory.getLogger(HealthServlet.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private static final long serialVersionUID = -1L;
 
   private final Provider<CurrentUser> currentUserProvider;
@@ -66,7 +65,7 @@
       setHealthy();
       rsp.setStatus(SC_NO_CONTENT);
     } catch (IOException e) {
-      log.error("Failed to set healthy", e);
+      log.atSevere().withCause(e).log("Failed to set healthy");
       sendError(rsp, SC_INTERNAL_SERVER_ERROR);
     }
   }
@@ -81,7 +80,7 @@
       setUnhealthy();
       rsp.setStatus(SC_NO_CONTENT);
     } catch (IOException e) {
-      log.error("Failed to set unhealthy", e);
+      log.atSevere().withCause(e).log("Failed to set unhealthy");
       sendError(rsp, SC_INTERNAL_SERVER_ERROR);
     }
   }
@@ -100,7 +99,7 @@
       rsp.sendError(statusCode);
     } catch (IOException e) {
       rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
-      log.error("Failed to send error response", e);
+      log.atSevere().withCause(e).log("Failed to send error response");
     }
   }
 
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 9dca6dd..4cdf431 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
@@ -15,11 +15,12 @@
 package com.ericsson.gerrit.plugins.highavailability.index;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeFinder;
 import com.google.gerrit.server.CommentsUtil;
+import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.ManualRequestContext;
@@ -33,11 +34,9 @@
 import java.util.Optional;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class ChangeCheckerImpl implements ChangeChecker {
-  private static final Logger log = LoggerFactory.getLogger(ChangeCheckerImpl.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private final GitRepositoryManager gitRepoMgr;
   private final CommentsUtil commentsUtil;
   private final ChangeDb changeDb;
@@ -91,9 +90,9 @@
   public boolean isChangeUpToDate(Optional<IndexEvent> indexEvent)
       throws IOException, OrmException {
     getComputedChangeTs();
-    log.debug("Checking change {} against index event {}", this, indexEvent);
+    log.atFine().log("Checking change %s against index event %s", this, indexEvent);
     if (!computedChangeTs.isPresent()) {
-      log.warn("Unable to compute last updated ts for change {}", changeId);
+      log.atWarning().log("Unable to compute last updated ts for change %s", changeId);
       return false;
     }
 
@@ -128,7 +127,7 @@
           + "/"
           + getBranchTargetSha();
     } catch (IOException | OrmException e) {
-      log.error("Unable to render change {}", changeId, e);
+      log.atSevere().withCause(e).log("Unable to render change %s", changeId);
       return "change-id=" + changeId;
     }
   }
@@ -138,12 +137,13 @@
       String refName = changeNotes.get().getChange().getDest().get();
       Ref ref = repo.exactRef(refName);
       if (ref == null) {
-        log.warn("Unable to find target ref {} for change {}", refName, changeId);
+        log.atWarning().log("Unable to find target ref %s for change %s", refName, changeId);
         return null;
       }
       return ref.getTarget().getObjectId().getName();
     } catch (IOException e) {
-      log.warn("Unable to resolve target branch SHA for change {}", changeId, e);
+      log.atWarning().withCause(e).log(
+          "Unable to resolve target branch SHA for change %s", changeId);
       return null;
     }
   }
@@ -163,7 +163,7 @@
         changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
       }
     } catch (OrmException e) {
-      log.warn("Unable to access draft comments for change {}", change, e);
+      log.atWarning().withCause(e).log("Unable to access draft comments for change %s", change);
     }
     return changeTs.getTime() / 1000;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
index 192ee8c..25ec6e8 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
@@ -17,12 +17,6 @@
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Id;
-import com.google.gerrit.reviewdb.server.AccountGroupAccess;
-import com.google.gerrit.reviewdb.server.AccountGroupByIdAccess;
-import com.google.gerrit.reviewdb.server.AccountGroupByIdAudAccess;
-import com.google.gerrit.reviewdb.server.AccountGroupMemberAccess;
-import com.google.gerrit.reviewdb.server.AccountGroupMemberAuditAccess;
-import com.google.gerrit.reviewdb.server.AccountGroupNameAccess;
 import com.google.gerrit.reviewdb.server.ChangeAccess;
 import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
 import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
@@ -30,7 +24,6 @@
 import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.reviewdb.server.SchemaVersionAccess;
-import com.google.gerrit.reviewdb.server.SystemConfigAccess;
 import com.google.gwtorm.server.Access;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
@@ -168,31 +161,6 @@
   }
 
   @Override
-  public SystemConfigAccess systemConfig() {
-    throw new Disabled();
-  }
-
-  @Override
-  public AccountGroupAccess accountGroups() {
-    throw new Disabled();
-  }
-
-  @Override
-  public AccountGroupNameAccess accountGroupNames() {
-    throw new Disabled();
-  }
-
-  @Override
-  public AccountGroupMemberAccess accountGroupMembers() {
-    throw new Disabled();
-  }
-
-  @Override
-  public AccountGroupMemberAuditAccess accountGroupMembersAudit() {
-    throw new Disabled();
-  }
-
-  @Override
   public ChangeAccess changes() {
     return new DisabledChangeAccess();
   }
@@ -218,16 +186,6 @@
   }
 
   @Override
-  public AccountGroupByIdAccess accountGroupById() {
-    throw new Disabled();
-  }
-
-  @Override
-  public AccountGroupByIdAudAccess accountGroupByIdAud() {
-    throw new Disabled();
-  }
-
-  @Override
   public int nextAccountId() {
     throw new Disabled();
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
index 3922f54..273013d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -18,21 +18,24 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.google.common.base.Objects;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.annotations.PluginName;
 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.ProjectIndexedListener;
 import com.google.inject.Inject;
 import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 class IndexEventHandler
-    implements ChangeIndexedListener, AccountIndexedListener, GroupIndexedListener {
-  private static final Logger log = LoggerFactory.getLogger(IndexEventHandler.class);
+    implements ChangeIndexedListener,
+        AccountIndexedListener,
+        GroupIndexedListener,
+        ProjectIndexedListener {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private final Executor executor;
   private final Forwarder forwarder;
   private final String pluginName;
@@ -77,7 +80,7 @@
                   }
                 });
       } catch (Exception e) {
-        log.warn("Unable to create task to reindex change {}", changeId, e);
+        log.atWarning().withCause(e).log("Unable to create task to reindex change {}", changeId);
       }
     }
   }
@@ -93,6 +96,16 @@
   }
 
   @Override
+  public void onProjectIndexed(String projectName) {
+    if (!Context.isForwardedEvent()) {
+      IndexProjectTask task = new IndexProjectTask(projectName);
+      if (queuedTasks.add(task)) {
+        executor.execute(task);
+      }
+    }
+  }
+
+  @Override
   public void onGroupIndexed(String groupUUID) {
     if (!Context.isForwardedEvent()) {
       IndexGroupTask task = new IndexGroupTask(groupUUID);
@@ -253,4 +266,36 @@
       return String.format("[%s] Index group %s in target instance", pluginName, groupUUID);
     }
   }
+
+  class IndexProjectTask extends IndexTask {
+    private final String projectName;
+
+    IndexProjectTask(String projectName) {
+      this.projectName = projectName;
+    }
+
+    @Override
+    public void execute() {
+      forwarder.indexProject(projectName, indexEvent);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(IndexProjectTask.class, projectName);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof IndexProjectTask)) {
+        return false;
+      }
+      IndexProjectTask other = (IndexProjectTask) obj;
+      return projectName.equals(other.projectName);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("[%s] Index project %s in target instance", pluginName, projectName);
+    }
+  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
index ebf8fdf..c17731f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
@@ -17,6 +17,7 @@
 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.ProjectIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
@@ -35,6 +36,7 @@
     DynamicSet.bind(binder(), ChangeIndexedListener.class).to(IndexEventHandler.class);
     DynamicSet.bind(binder(), AccountIndexedListener.class).to(IndexEventHandler.class);
     DynamicSet.bind(binder(), GroupIndexedListener.class).to(IndexEventHandler.class);
+    DynamicSet.bind(binder(), ProjectIndexedListener.class).to(IndexEventHandler.class);
 
     install(
         new FactoryModuleBuilder()
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
index 596f86e..ce47390 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
@@ -17,6 +17,7 @@
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -30,8 +31,6 @@
 import org.jgroups.Message;
 import org.jgroups.ReceiverAdapter;
 import org.jgroups.View;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Provider which uses JGroups to find the peer gerrit instances. On startup every gerrit instance
@@ -46,7 +45,7 @@
 @Singleton
 public class JGroupsPeerInfoProvider extends ReceiverAdapter
     implements Provider<Set<PeerInfo>>, LifecycleListener {
-  private static final Logger log = LoggerFactory.getLogger(JGroupsPeerInfoProvider.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private static final String JGROUPS_LOG_FACTORY_PROPERTY = "jgroups.logging.log_factory_class";
 
   static {
@@ -80,25 +79,25 @@
       peerAddress = msg.getSrc();
       String url = (String) msg.getObject();
       peerInfo = Optional.of(new PeerInfo(url));
-      log.info("receive(): Set new peerInfo: {}", url);
+      log.atInfo().log("receive(): Set new peerInfo: %s", url);
     }
   }
 
   @Override
   public void viewAccepted(View view) {
-    log.info("viewAccepted(view: {}) called", view);
+    log.atInfo().log("viewAccepted(view: %s) called", view);
     synchronized (this) {
       if (view.getMembers().size() > 2) {
-        log.warn(
-            "{} members joined the jgroups cluster {} ({}). "
-                + " Only two members are supported. Members: {}",
+        log.atWarning().log(
+            "%d members joined the jgroups cluster %s (%s). "
+                + " Only two members are supported. Members: %s",
             view.getMembers().size(),
             jgroupsConfig.clusterName(),
             channel.getName(),
             view.getMembers());
       }
       if (peerAddress != null && !view.getMembers().contains(peerAddress)) {
-        log.info("viewAccepted(): removed peerInfo");
+        log.atInfo().log("viewAccepted(): removed peerInfo");
         peerAddress = null;
         peerInfo = Optional.empty();
       }
@@ -108,11 +107,9 @@
         channel.send(new Message(null, myUrl));
       } catch (Exception e) {
         // channel communication caused an error. Can't do much about it.
-        log.error(
-            "Sending a message over channel {} to cluster {} failed",
-            channel.getName(),
-            jgroupsConfig.clusterName(),
-            e);
+        log.atSevere().withCause(e).log(
+            "Sending a message over channel %s to cluster %s failed",
+            channel.getName(), jgroupsConfig.clusterName());
       }
     }
   }
@@ -122,28 +119,25 @@
       channel = getChannel();
       Optional<InetAddress> address = finder.findAddress();
       if (address.isPresent()) {
-        log.debug("Protocol stack: " + channel.getProtocolStack());
+        log.atFine().log("Protocol stack: %s", channel.getProtocolStack());
         channel.getProtocolStack().getTransport().setBindAddress(address.get());
-        log.debug("Channel bound to {}", address.get());
+        log.atFine().log("Channel bound to %s", address.get());
       } else {
-        log.warn("Channel not bound: address not present");
+        log.atWarning().log("Channel not bound: address not present");
       }
       channel.setReceiver(this);
       channel.setDiscardOwnMessages(true);
       channel.connect(jgroupsConfig.clusterName());
-      log.info(
-          "Channel {} successfully joined jgroups cluster {}",
-          channel.getName(),
-          jgroupsConfig.clusterName());
+      log.atInfo().log(
+          "Channel %s successfully joined jgroups cluster %s",
+          channel.getName(), jgroupsConfig.clusterName());
     } catch (Exception e) {
       if (channel != null) {
-        log.error(
-            "joining cluster {} (channel {}) failed",
-            jgroupsConfig.clusterName(),
-            channel.getName(),
-            e);
+        log.atSevere().withCause(e).log(
+            "joining cluster %s (channel %s) failed",
+            jgroupsConfig.clusterName(), channel.getName());
       } else {
-        log.error("joining cluster {} failed", jgroupsConfig.clusterName(), e);
+        log.atSevere().withCause(e).log("joining cluster %s failed", jgroupsConfig.clusterName());
       }
     }
   }
@@ -156,10 +150,9 @@
       }
       return new JChannel();
     } catch (Exception e) {
-      log.error(
-          "Unable to create a channel with protocol stack: {}",
-          protocolStack.isPresent() ? protocolStack : "default",
-          e);
+      log.atSevere().withCause(e).log(
+          "Unable to create a channel with protocol stack: %s",
+          protocolStack.isPresent() ? protocolStack : "default");
       throw e;
     }
   }
@@ -177,10 +170,9 @@
   @Override
   public void stop() {
     if (channel != null) {
-      log.info(
-          "closing jgroups channel {} (cluster {})",
-          channel.getName(),
-          jgroupsConfig.clusterName());
+      log.atInfo().log(
+          "closing jgroups channel %s (cluster %s)",
+          channel.getName(), jgroupsConfig.clusterName());
       channel.close();
     }
     peerInfo = Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java
index adf7d2e..a257eb3 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java
@@ -16,6 +16,7 @@
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.base.CharMatcher;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -26,12 +27,10 @@
 import java.net.UnknownHostException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.transport.URIish;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 class MyUrlProvider implements Provider<String> {
-  private static final Logger log = LoggerFactory.getLogger(MyUrlProvider.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private static final String HTTPD_SECTION = "httpd";
   private static final String LISTEN_URL_KEY = "listenUrl";
@@ -44,7 +43,7 @@
   MyUrlProvider(@GerritServerConfig Config srvConfig, Configuration pluginConfiguration) {
     String url = pluginConfiguration.peerInfoJGroups().myUrl();
     if (url == null) {
-      log.info("myUrl not configured; attempting to determine from {}", LISTEN_URL);
+      log.atInfo().log("myUrl not configured; attempting to determine from %s", LISTEN_URL);
       try {
         url = CharMatcher.is('/').trimTrailingFrom(getMyUrlFromListenUrl(srvConfig));
       } catch (MyUrlProviderException e) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebSessionCacheCleaner.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebSessionCacheCleaner.java
index b9dc245..afc6568 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebSessionCacheCleaner.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebSessionCacheCleaner.java
@@ -18,6 +18,7 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
@@ -25,8 +26,6 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.concurrent.ScheduledFuture;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 class FileBasedWebSessionCacheCleaner implements LifecycleListener {
@@ -37,7 +36,7 @@
   private ScheduledFuture<?> scheduledCleanupTask;
 
   static class CleanupTask implements Runnable {
-    private static final Logger log = LoggerFactory.getLogger(CleanupTask.class);
+    private static final FluentLogger log = FluentLogger.forEnclosingClass();
     private final FileBasedWebsessionCache fileBasedWebSessionCache;
     private final String pluginName;
 
@@ -49,9 +48,9 @@
 
     @Override
     public void run() {
-      log.info("Cleaning up expired file based websessions...");
+      log.atInfo().log("Cleaning up expired file based websessions...");
       fileBasedWebSessionCache.cleanUp();
-      log.info("Cleaning up expired file based websessions...Done");
+      log.atInfo().log("Cleaning up expired file based websessions...Done");
     }
 
     @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebsessionCache.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebsessionCache.java
index b4c99b5..674de10 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebsessionCache.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/websession/file/FileBasedWebsessionCache.java
@@ -18,6 +18,7 @@
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheStats;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.httpd.WebSessionManager;
 import com.google.gerrit.httpd.WebSessionManager.Val;
@@ -44,8 +45,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 public class FileBasedWebsessionCache implements Cache<String, WebSessionManager.Val> {
@@ -74,7 +73,7 @@
     }
   }
 
-  private static final Logger log = LoggerFactory.getLogger(FileBasedWebsessionCache.class);
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final Path websessionsDir;
 
@@ -179,7 +178,7 @@
             StandardCopyOption.ATOMIC_MOVE);
       }
     } catch (IOException e) {
-      log.warn("Cannot put into cache {}", websessionsDir, e);
+      log.atWarning().withCause(e).log("Cannot put into cache %s", websessionsDir);
     }
   }
 
@@ -197,7 +196,7 @@
 
   @Override
   public CacheStats stats() {
-    log.warn("stats() unimplemented");
+    log.atWarning().log("stats() unimplemented");
     return null;
   }
 
@@ -207,15 +206,14 @@
           ObjectInputStream objStream = new ObjectInputStream(fileStream)) {
         return (Val) objStream.readObject();
       } catch (ClassNotFoundException e) {
-        log.warn(
-            "Entry {} in cache {} has an incompatible class and can't be"
+        log.atWarning().log(
+            "Entry %s in cache %s has an incompatible class and can't be"
                 + " deserialized. Invalidating entry.",
-            path,
-            websessionsDir);
-        log.debug(e.getMessage(), e);
+            path, websessionsDir);
+        log.atFine().withCause(e).log(e.getMessage());
         invalidate(path.getFileName().toString());
       } catch (IOException e) {
-        log.warn("Cannot read cache {}", websessionsDir, e);
+        log.atWarning().withCause(e).log("Cannot read cache %s", websessionsDir);
       }
     }
     return null;
@@ -225,7 +223,7 @@
     try {
       Files.deleteIfExists(path);
     } catch (IOException e) {
-      log.error("Error trying to delete {} from {}", path, websessionsDir, e);
+      log.atSevere().withCause(e).log("Error trying to delete %s from %s", path, websessionsDir);
     }
   }
 
@@ -236,7 +234,7 @@
         files.add(path);
       }
     } catch (IOException e) {
-      log.error("Cannot list files in cache {}", websessionsDir, e);
+      log.atSevere().withCause(e).log("Cannot list files in cache %s", websessionsDir);
     }
     return files;
   }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
index 01af1a3..a3c8412 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
@@ -50,9 +50,9 @@
   @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
 
   @Override
-  public void setUp() throws Exception {
+  public void setUpTestPlugin() throws Exception {
     givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
-    super.setUp();
+    super.setUpTestPlugin();
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java
index 9d08f15..22ef89e 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java
@@ -49,9 +49,9 @@
   @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
 
   @Override
-  public void setUp() throws Exception {
+  public void setUpTestPlugin() throws Exception {
     givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
-    super.setUp();
+    super.setUpTestPlugin();
   }
 
   @Test
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 02b4a47..ec5b1de 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
@@ -18,9 +18,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
-import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.plugincontext.PluginContext.PluginMetrics;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -32,9 +34,11 @@
 
   @Before
   public void setUp() {
+    PluginMetrics mockMetrics = mock(PluginMetrics.class);
     listenerMock = mock(EventListener.class);
-    DynamicSet<EventListener> listeners = DynamicSet.emptySet();
-    listeners.add(listenerMock);
+    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);
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
index ecb976e..9d0b73c 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
@@ -19,8 +19,8 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
-import com.google.gerrit.common.EventDispatcher;
 import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
 import com.google.gwtorm.server.OrmException;
 import org.junit.Before;
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
index 8bfe47d..6bfc58b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -28,12 +28,12 @@
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
new file mode 100644
index 0000000..ea64d71
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
@@ -0,0 +1,111 @@
+// 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.ericsson.gerrit.plugins.highavailability.forwarder;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
+import com.google.gerrit.index.project.ProjectIndexer;
+import com.google.gerrit.reviewdb.client.Project;
+import java.io.IOException;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ForwardedIndexProjectHandlerTest {
+
+  @Rule public ExpectedException exception = ExpectedException.none();
+  @Mock private ProjectIndexer indexerMock;
+  @Mock private Configuration configMock;
+  @Mock private Configuration.Index indexMock;
+  private ForwardedIndexProjectHandler handler;
+  private Project.NameKey nameKey;
+
+  @Before
+  public void setUp() {
+    when(configMock.index()).thenReturn(indexMock);
+    when(indexMock.numStripedLocks()).thenReturn(10);
+    handler = new ForwardedIndexProjectHandler(indexerMock, configMock);
+    nameKey = new Project.NameKey("project/name");
+  }
+
+  @Test
+  public void testSuccessfulIndexing() throws Exception {
+    handler.index(nameKey, Operation.INDEX, Optional.empty());
+    verify(indexerMock).index(nameKey);
+  }
+
+  @Test
+  public void deleteIsNotSupported() throws Exception {
+    exception.expect(UnsupportedOperationException.class);
+    exception.expectMessage("Delete from project index not supported");
+    handler.index(nameKey, Operation.DELETE, Optional.empty());
+  }
+
+  @Test
+  public void shouldSetAndUnsetForwardedContext() throws Exception {
+    // this doAnswer is to allow to assert that context is set to forwarded
+    // while cache eviction is called.
+    doAnswer(
+            (Answer<Void>)
+                invocation -> {
+                  assertThat(Context.isForwardedEvent()).isTrue();
+                  return null;
+                })
+        .when(indexerMock)
+        .index(nameKey);
+
+    assertThat(Context.isForwardedEvent()).isFalse();
+    handler.index(nameKey, Operation.INDEX, Optional.empty());
+    assertThat(Context.isForwardedEvent()).isFalse();
+
+    verify(indexerMock).index(nameKey);
+  }
+
+  @Test
+  public void shouldSetAndUnsetForwardedContextEvenIfExceptionIsThrown() throws Exception {
+    doAnswer(
+            (Answer<Void>)
+                invocation -> {
+                  assertThat(Context.isForwardedEvent()).isTrue();
+                  throw new IOException("someMessage");
+                })
+        .when(indexerMock)
+        .index(nameKey);
+
+    assertThat(Context.isForwardedEvent()).isFalse();
+    try {
+      handler.index(nameKey, Operation.INDEX, Optional.empty());
+      fail("should have thrown an IOException");
+    } catch (IOException e) {
+      assertThat(e.getMessage()).isEqualTo("someMessage");
+    }
+    assertThat(Context.isForwardedEvent()).isFalse();
+
+    verify(indexerMock).index(nameKey);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java
new file mode 100644
index 0000000..83420b9
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java
@@ -0,0 +1,91 @@
+// 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.ericsson.gerrit.plugins.highavailability.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.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexProjectHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.Project;
+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/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
index 2eee205..af4637e 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
@@ -51,10 +51,10 @@
   @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
 
   @Override
-  public void setUp() throws Exception {
+  public void setUpTestPlugin() throws Exception {
     givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT)));
     beforeAction();
-    super.setUp();
+    super.setUpTestPlugin();
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ProjectIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ProjectIndexForwardingIT.java
new file mode 100644
index 0000000..951e4f6
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ProjectIndexForwardingIT.java
@@ -0,0 +1,36 @@
+// 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.ericsson.gerrit.plugins.highavailability.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/high-availability/index/project/" + Url.encode(someProjectName);
+  }
+
+  @Override
+  public void doAction() throws Exception {
+    gApi.projects().name(someProjectName).index(false);
+  }
+}