Merge branch 'stable-3.12'

* stable-3.12:
  Forward project-wide change index deletions
  Rename ForwardedEventHandler to ForwardedEventDispatcher

Change-Id: If57c8501581a4503eb308e95f9cd89e76a137edb
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcher.java
similarity index 87%
rename from src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
rename to src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcher.java
index e2e3954..aeee827 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcher.java
@@ -22,23 +22,24 @@
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.forwarder.router.StreamEventRouter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Dispatch event to the {@link EventDispatcher}. This class is meant to be used on the receiving
- * side of the {@link StreamEventForwarder} since it will prevent dispatched event to be forwarded
+ * side of the {@link StreamEventRouter} since it will prevent dispatched event to be forwarded
  * again causing an infinite forwarding loop between the 2 nodes.
  */
 @Singleton
-public class ForwardedEventHandler {
-  private static final Logger log = LoggerFactory.getLogger(ForwardedEventHandler.class);
+public class ForwardedEventDispatcher {
+  private static final Logger log = LoggerFactory.getLogger(ForwardedEventDispatcher.class);
 
   private final DynamicItem<EventDispatcher> dispatcher;
   private final OneOffRequestContext oneOffCtx;
 
   @Inject
-  public ForwardedEventHandler(
+  public ForwardedEventDispatcher(
       DynamicItem<EventDispatcher> dispatcher, OneOffRequestContext oneOffCtx) {
     this.dispatcher = dispatcher;
     this.oneOffCtx = oneOffCtx;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
index 1f3fd93..aa5929f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
@@ -19,6 +19,7 @@
 
 import com.google.common.base.Splitter;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.ManualRequestContext;
@@ -73,7 +74,12 @@
 
   @Override
   protected void doIndex(String id, Optional<ChangeIndexEvent> indexEvent) {
-    scheduleIndexing(id, indexEvent, this::indexIfConsistent);
+    if (indexEvent.isPresent()
+        && ChangeIndexEvent.isAllChangesDeletedForProject(indexEvent.get())) {
+      indexer.deleteAllForProject(Project.nameKey(indexEvent.get().projectName));
+    } else {
+      scheduleIndexing(id, indexEvent, this::indexIfConsistent);
+    }
   }
 
   private void indexIfConsistent(String id) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java
index 7fd3b6c..3acd288 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java
@@ -22,12 +22,37 @@
 
 public class ChangeIndexEvent extends IndexEvent {
   public static final String TYPE = "change-index";
+  private static final int ALL_CHANGES_FOR_PROJECT = 0;
 
   public String projectName;
   public int changeId;
   public String targetSha;
   public boolean deleted;
 
+  /**
+   * Creates a {@link ChangeIndexEvent} that represents the deletion of all changes belonging to the
+   * given project.
+   *
+   * @param projectName the name of the project
+   * @param instanceId the originating instance identifier
+   * @return an event marking all project changes as deleted
+   */
+  public static ChangeIndexEvent allChangesDeletedForProject(
+      String projectName, String instanceId) {
+    return new ChangeIndexEvent(
+        projectName, ALL_CHANGES_FOR_PROJECT, /* deleted */ true, instanceId);
+  }
+
+  /**
+   * Checks whether the given event represents the deletion of all changes for a project.
+   *
+   * @param event the event to check
+   * @return {@code true} if the event signals a project-wide deletion, {@code false} otherwise
+   */
+  public static boolean isAllChangesDeletedForProject(ChangeIndexEvent event) {
+    return event.deleted && event.changeId == ALL_CHANGES_FOR_PROJECT;
+  }
+
   public ChangeIndexEvent(String projectName, int changeId, boolean deleted, String instanceId) {
     super(TYPE, instanceId);
     this.projectName = projectName;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
index 95a3e66..c88f5cb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
@@ -17,18 +17,18 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventDispatcher;
 import com.googlesource.gerrit.plugins.replication.events.RefReplicationDoneEvent;
 import java.io.IOException;
 
 public class StreamEventRouter implements ForwardedEventRouter<Event> {
-  private final ForwardedEventHandler streamEventHandler;
+  private final ForwardedEventDispatcher forwardedEventDispatcher;
   private final IndexEventRouter indexEventRouter;
 
   @Inject
   public StreamEventRouter(
-      ForwardedEventHandler streamEventHandler, IndexEventRouter indexEventRouter) {
-    this.streamEventHandler = streamEventHandler;
+      ForwardedEventDispatcher forwardedEventDispatcher, IndexEventRouter indexEventRouter) {
+    this.forwardedEventDispatcher = forwardedEventDispatcher;
     this.indexEventRouter = indexEventRouter;
   }
 
@@ -44,6 +44,6 @@
       indexEventRouter.onRefReplicated((RefReplicationDoneEvent) sourceEvent);
     }
 
-    streamEventHandler.dispatch(sourceEvent);
+    forwardedEventDispatcher.dispatch(sourceEvent);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
index 9aee56e..83f610d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
@@ -85,6 +85,11 @@
   }
 
   @Override
+  public void onAllChangesDeletedForProject(String projectName) {
+    currCtx.onlyWithContext((ctx) -> executeAllChangesDeletedForProject(projectName));
+  }
+
+  @Override
   public void onChangeDeleted(int id) {
     executeDeleteChangeTask(id);
   }
@@ -111,6 +116,17 @@
     }
   }
 
+  private void executeAllChangesDeletedForProject(String projectName) {
+    if (!Context.isForwardedEvent()) {
+      IndexChangeTask task =
+          new IndexChangeTask(
+              ChangeIndexEvent.allChangesDeletedForProject(projectName, instanceId));
+      if (queuedTasks.add(task)) {
+        executor.execute(task);
+      }
+    }
+  }
+
   private void executeIndexChangeTask(String projectName, int id) {
     if (!Context.isForwardedEvent()) {
       ChangeChecker checker = changeChecker.create(projectName + "~" + id);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
index 11600f9..7c398f8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.inject.util.Providers;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventDispatcher;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexAccountHandler;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexChangeHandler;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexGroupHandler;
@@ -51,7 +51,7 @@
   @Mock private ForwardedIndexChangeHandler indexChangeHandler;
   @Mock private ForwardedIndexGroupHandler indexGroupHandler;
   @Mock private ForwardedIndexProjectHandler indexProjectHandler;
-  @Mock private ForwardedEventHandler forwardedEventHandler;
+  @Mock private ForwardedEventDispatcher forwardedEventDispatcher;
   private AllUsersName allUsersName = new AllUsersName("All-Users");
   PrivateInternals_DynamicMapImpl<ForwardedIndexingHandler<?, ? extends IndexEvent>> indexHandlers;
 
@@ -77,7 +77,7 @@
 
   @Test
   public void streamEventRouterShouldTriggerAccountIndexFlush() throws Exception {
-    StreamEventRouter streamEventRouter = new StreamEventRouter(forwardedEventHandler, router);
+    StreamEventRouter streamEventRouter = new StreamEventRouter(forwardedEventDispatcher, router);
 
     final AccountIndexEvent event = new AccountIndexEvent(1, INSTANCE_ID);
     router.route(event);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
index ac90436..8d02c20 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
@@ -21,7 +21,7 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.server.events.CommentAddedEvent;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventDispatcher;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.IndexEventRouter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.StreamEventRouter;
 import org.junit.Before;
@@ -34,19 +34,19 @@
 public class StreamEventRouterTest {
 
   private StreamEventRouter router;
-  @Mock private ForwardedEventHandler streamEventHandler;
+  @Mock private ForwardedEventDispatcher forwardedEventDispatcher;
   @Mock private IndexEventRouter indexEventRouter;
 
   @Before
   public void setUp() {
-    router = new StreamEventRouter(streamEventHandler, indexEventRouter);
+    router = new StreamEventRouter(forwardedEventDispatcher, indexEventRouter);
   }
 
   @Test
   public void routerShouldSendEventsToTheAppropriateHandler_StreamEvent() throws Exception {
     final CommentAddedEvent event = new CommentAddedEvent(aChange());
     router.route(event);
-    verify(streamEventHandler).dispatch(event);
+    verify(forwardedEventDispatcher).dispatch(event);
   }
 
   private Change aChange() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcherTest.java
similarity index 87%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcherTest.java
index 73ef353..bf70f62 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcherTest.java
@@ -31,24 +31,24 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
-public class ForwardedEventHandlerTest {
+public class ForwardedEventDispatcherTest {
 
   @Rule public ExpectedException exception = ExpectedException.none();
   @Mock private DynamicItem<EventDispatcher> dispatcherMockItem;
   @Mock private EventDispatcher dispatcherMock;
   @Mock OneOffRequestContext oneOffCtxMock;
-  private ForwardedEventHandler handler;
+  private ForwardedEventDispatcher forwardedEventDispatcher;
 
   @Before
   public void setUp() throws Exception {
     when(dispatcherMockItem.get()).thenReturn(dispatcherMock);
-    handler = new ForwardedEventHandler(dispatcherMockItem, oneOffCtxMock);
+    forwardedEventDispatcher = new ForwardedEventDispatcher(dispatcherMockItem, oneOffCtxMock);
   }
 
   @Test
   public void testSuccessfulDispatching() throws Exception {
     Event event = new ProjectCreatedEvent();
-    handler.dispatch(event);
+    forwardedEventDispatcher.dispatch(event);
     verify(dispatcherMock).postEvent(event);
   }
 }