Add notify replication failed for TRANSPORT_ERROR & REPOSITORY_MISSING

We send replication started notification for each attempt to push.
If the push fails with a reason TRANSPORT_ERROR or REPOSITORY_MISSING,
it will be retried until it succeed (endless loop in case
TRANSPORT_ERROR that occurs forever).

For each new re-try we will send another replication started
notification for that branch, but we will miss the notification
about failed attempt completed. This patch adds the missing
notification.

This fixes notification on push to replica that is down.

Change-Id: I6d5d06fbc081df2d9a32973af73f6a2136d51731
Signed-off-by: Eryk Szymanski <eryksz@gmail.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
index f7dd30a..4b22a45 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -15,6 +15,8 @@
 package com.googlesource.gerrit.plugins.replication;
 
 import static com.googlesource.gerrit.plugins.replication.PushResultProcessing.resolveNodeName;
+import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.NON_EXISTING;
+import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
@@ -55,12 +57,14 @@
 import com.google.inject.Provides;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 import com.google.inject.servlet.RequestScoped;
+import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import org.apache.commons.io.FilenameUtils;
@@ -69,6 +73,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
 import org.slf4j.Logger;
 
@@ -338,7 +343,7 @@
 
   private void addRef(PushOne e, String ref) {
     e.addRef(ref);
-    postEvent(e, ref);
+    postReplicationScheduledEvent(e, ref);
   }
 
   /**
@@ -416,7 +421,13 @@
           case TRANSPORT_ERROR:
           case REPOSITORY_MISSING:
           default:
+            RemoteRefUpdate.Status status =
+                RetryReason.REPOSITORY_MISSING.equals(reason)
+                    ? NON_EXISTING
+                    : REJECTED_OTHER_REASON;
+            postReplicationFailedEvent(pushOp, status);
             if (pushOp.setToRetry()) {
+              postReplicationScheduledEvent(pushOp);
               pool.schedule(pushOp, config.getRetryDelay(), TimeUnit.MINUTES);
             } else {
               pushOp.canceledByReplication();
@@ -595,10 +606,28 @@
     return uri.toString().contains(urlMatch);
   }
 
-  private void postEvent(PushOne pushOp, String ref) {
+  private void postReplicationScheduledEvent(PushOne pushOp) {
+    postReplicationScheduledEvent(pushOp, null);
+  }
+
+  private void postReplicationScheduledEvent(PushOne pushOp, String inputRef) {
+    Set<String> refs = inputRef == null ? pushOp.getRefs() : ImmutableSet.of(inputRef);
     Project.NameKey project = pushOp.getProjectNameKey();
     String targetNode = resolveNodeName(pushOp.getURI());
-    ReplicationScheduledEvent event = new ReplicationScheduledEvent(project.get(), ref, targetNode);
-    eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event);
+    for (String ref : refs) {
+      ReplicationScheduledEvent event =
+          new ReplicationScheduledEvent(project.get(), ref, targetNode);
+      eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event);
+    }
+  }
+
+  private void postReplicationFailedEvent(PushOne pushOp, RemoteRefUpdate.Status status) {
+    Project.NameKey project = pushOp.getProjectNameKey();
+    String targetNode = resolveNodeName(pushOp.getURI());
+    for (String ref : pushOp.getRefs()) {
+      RefReplicatedEvent event =
+          new RefReplicatedEvent(project.get(), ref, targetNode, RefPushResult.FAILED, status);
+      eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event);
+    }
   }
 }