Add method that retrieve links for specific EiffelEvent key

Solves: Jira GER-1545
Change-Id: Ia2cc9f065773a49bed01158334bd16f96a037d73
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java
index 8d6cab9..8e58fbb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java
@@ -91,6 +91,20 @@
    */
   public Optional<UUID> getScsForCommit(String repo, String commit, List<String> branches)
       throws EiffelEventIdLookupException;
+
+  /**
+   * If an event exists, already published or awaiting publish in this EiffelEventHub, such that:
+   *
+   * <p>{@code EventKey.fromEvent(this).equals(EventKey.fromEvent(existing)}
+   *
+   * <p>is true, links.target for all links of type PREVIOUS_VERSION of this event is returned,
+   * otherwise Optional.empty()
+   *
+   * @param key EventKey
+   * @return Optional<List<UUID>> event.links[].target
+   * @throws EiffelEventIdLookupException when failing to lookup event from Eiffel.
+   */
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EiffelEventIdLookupException;
   /** Start publishing events to Eiffel. */
   public void startPublishing();
   /** Stop publishing events to Eiffel. */
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java
index 841aa4f..2138164 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.eventseiffel;
 
 import static com.google.common.base.Preconditions.checkState;
+import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType.PREVIOUS_VERSION;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -26,6 +27,7 @@
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEvent;
+import java.util.Arrays;
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
@@ -173,6 +175,29 @@
   }
 
   @Override
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EiffelEventIdLookupException {
+    final ReentrantLock idLookupLock = this.idLookupLock;
+    idLookupLock.lock();
+    try {
+      EiffelEvent fromHub = eventsInQueue.getOrDefault(key, null);
+      if (fromHub != null) {
+        if (key.matches(fromHub)) {
+          return Optional.of(
+              Arrays.stream(fromHub.links)
+                  .filter(link -> link.type == PREVIOUS_VERSION)
+                  .map(link -> link.target)
+                  .collect(Collectors.toList()));
+        }
+        throw new EiffelEventIdLookupException(
+            "Key: %s does not match found event: %s.", key, EventKey.fromEvent(fromHub));
+      }
+    } finally {
+      idLookupLock.unlock();
+    }
+    return idCache.getParentLinks(key);
+  }
+
+  @Override
   public boolean isOpen() {
     return open;
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCache.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCache.java
index 1299a7e..59775e0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCache.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCache.java
@@ -36,6 +36,19 @@
   Optional<UUID> getEventId(EventKey key) throws EiffelEventIdLookupException;
 
   /**
+   * Returns the ids that is linked with PREVIOUS_VERSION by event that matches:
+   *
+   * <p>{@code EventKey.fromEvent(event).equals(key)}
+   *
+   * <p>Optional.Empty if no such event exists.
+   *
+   * @param key
+   * @return Optional<List<UUID>>
+   * @throws EiffelEventIdLookupException if value failed to load.
+   */
+  Optional<List<UUID>> getParentLinks(EventKey key) throws EiffelEventIdLookupException;
+
+  /**
    * Returns the id of an event that matches the repo and the commit:
    *
    * <p>Optional.Empty if no such event exists.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java
index fa7d622..923039b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java
@@ -131,6 +131,18 @@
     return !ids.isEmpty() ? Optional.of(ids.get(0)) : Optional.empty();
   }
 
+  @Override
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EiffelEventIdLookupException {
+    try {
+      return eventStorage.getParentLinks(key);
+    } catch (EventStorageException e) {
+      throw new EiffelEventIdLookupException(
+          e,
+          key.type(),
+          String.format("failed to lookup Eiffel event links for %s from GraphQl", key));
+    }
+  }
+
   /* (non-Javadoc)
    * @see com.googlesource.gerrit.plugins.eventseiffel.EiffelEventIdCache#putId(com.googlesource.gerrit.plugins.eventseiffel.eiffel.EiffelEvent)
    */
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelClient.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelClient.java
index 5bf5c75..002f6bc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelClient.java
@@ -60,6 +60,11 @@
   }
 
   @Override
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EventStorageException {
+    return restClient.getParentLinks(key);
+  }
+
+  @Override
   public List<UUID> getScsIds(String repo, String commit) throws EventStorageException {
     return graphQlClient.getScsIds(repo, commit);
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGoRestClient.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGoRestClient.java
index 1cece64..1936110 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGoRestClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGoRestClient.java
@@ -14,6 +14,8 @@
 
 package com.googlesource.gerrit.plugins.eventseiffel.eiffel.api;
 
+import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType.PREVIOUS_VERSION;
+
 import com.github.rholder.retry.RetryException;
 import com.github.rholder.retry.Retryer;
 import com.github.rholder.retry.RetryerBuilder;
@@ -23,16 +25,24 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gson.Gson;
 import com.google.gson.JsonSyntaxException;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkInfo;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType;
 import java.io.IOException;
 import java.net.URI;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class EiffelGoRestClient {
   public static FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -49,6 +59,43 @@
     this.goRestUrl = goRestUrl;
   }
 
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EventStorageException {
+    String query;
+    switch (key.type()) {
+      case SCC:
+        SourceChangeEventKey sccKey = (SourceChangeEventKey) key;
+        query =
+            String.format(
+                LINKS_ID_URL,
+                sccKey.type().getType(),
+                sccKey.repo(),
+                sccKey.branch(),
+                sccKey.commit());
+        break;
+      case SCS:
+        SourceChangeEventKey scsKey = (SourceChangeEventKey) key;
+        query =
+            String.format(
+                LINKS_ID_URL,
+                scsKey.type().getType(),
+                scsKey.repo(),
+                scsKey.branch(),
+                scsKey.commit());
+        break;
+      default:
+        return Optional.empty();
+    }
+    QueryResult qr = restQuery(query);
+    if (qr == null || qr.isEmpty()) {
+      return Optional.empty();
+    }
+
+    if (qr.nbrOfFoundEvents() > 1) {
+      logger.atWarning().log("More than one event found (using first) for query:\"%s\"", query);
+    }
+    return Optional.of(qr.getParentLinks());
+  }
+
   private QueryResult restQuery(String query) throws EventStorageException {
     HttpResponse<String> response;
     try {
@@ -95,5 +142,23 @@
     class Data {
       EiffelLinkInfo[] links;
     }
+
+    int nbrOfFoundEvents() {
+      return items != null ? items.size() : 0;
+    }
+
+    boolean isEmpty() {
+      return items == null || items.isEmpty();
+    }
+
+    List<UUID> getParentLinks() {
+      return getLinks(PREVIOUS_VERSION).collect(Collectors.toList());
+    }
+
+    private Stream<UUID> getLinks(EiffelLinkType linkType) {
+      return Arrays.stream(items.get(0).links)
+          .filter(link -> link.type == linkType)
+          .map(link -> link.target);
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java
index 468d030..2b0a79a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java
@@ -180,18 +180,22 @@
     Data data;
     List<Error> errors;
 
-    List<UUID> getIds() {
+    List<Data.Field.Edge> getEdges() {
       if (data != null) {
         if (data.sourceChangeCreated != null) {
-          return toIds(data.sourceChangeCreated.edges);
+          return data.sourceChangeCreated.edges;
         }
         if (data.sourceChangeSubmitted != null) {
-          return toIds(data.sourceChangeSubmitted.edges);
+          return data.sourceChangeSubmitted.edges;
         }
       }
       return Lists.newArrayList();
     }
 
+    List<UUID> getIds() {
+      return toIds(getEdges());
+    }
+
     List<String> getErrorMessages() {
       return errors.stream().map(e -> e.message).collect(Collectors.toList());
     }
@@ -208,18 +212,20 @@
 
     class Field {
       List<Edge> edges;
+      Meta meta;
 
       class Edge {
         Node node;
 
         class Node {
+          List<Data> links;
           Meta meta;
-
-          class Meta {
-            UUID id;
-          }
         }
       }
+
+      class Meta {
+        UUID id;
+      }
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EventStorage.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EventStorage.java
index 399cc5b..56be459 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EventStorage.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EventStorage.java
@@ -34,6 +34,16 @@
   public Optional<UUID> getEventId(EventKey key) throws EventStorageException;
 
   /**
+   * Returns the links.target for links where links.type is PREVIOUS_VERSION for the event that
+   * matches EventKey key. Optional.empty() if there exists no such event.
+   *
+   * @param key
+   * @return {@link Optional} of {@link List} of {@link UUID} ids of the linked events.
+   * @throws EventStorageException
+   */
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EventStorageException;
+
+  /**
    * Returns the meta.ids of the SCS events that match the repo and the commitid.
    *
    * @param repo the repo of the SCS events.
@@ -52,6 +62,11 @@
     }
 
     @Override
+    public Optional<List<UUID>> getParentLinks(EventKey key) throws EventStorageException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public List<UUID> getScsIds(String repo, String commit) throws EventStorageException {
       throw new NotImplementedException();
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java
index ddbb055..9957d69 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java
@@ -22,6 +22,8 @@
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEvent;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -70,6 +72,17 @@
     return idCache.getScsForCommit(repo, commit, branches);
   }
 
+  @Override
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EiffelEventIdLookupException {
+    return findEvent(key)
+        .map(
+            event ->
+                Arrays.stream(event.links)
+                    .filter(link -> link.type.equals(EiffelLinkType.PREVIOUS_VERSION))
+                    .map(link -> link.target)
+                    .collect(Collectors.toList()));
+  }
+
   private Optional<EiffelEvent> findEvent(EventKey key) {
     for (EiffelEvent event : EVENTS) {
       if (key.equals(EventKey.fromEvent(event))) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventStorage.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventStorage.java
index b257679..74196c8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventStorage.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventStorage.java
@@ -16,6 +16,7 @@
 
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCS;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
@@ -64,6 +65,11 @@
         .collect(Collectors.toList());
   }
 
+  @Override
+  public Optional<List<UUID>> getParentLinks(EventKey key) throws EventStorageException {
+    return Optional.of(Lists.newArrayList());
+  }
+
   private boolean equalsSCSWithRepoAndCommit(EventKey key, String repo, String commit) {
     if (!key.type().equals(SCS)) return false;
     SourceChangeEventKey scsKey = (SourceChangeEventKey) key;