Add method that retrieve UUID for SCC that a specific SCS points to

Solves: Jira GER-1545
Change-Id: Idd246fd763e5aea9535a662ebe4be7e2b9ef57b8
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 8e58fbb..347f5fa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java
@@ -16,6 +16,7 @@
 
 import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException;
 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.List;
 import java.util.Optional;
@@ -105,6 +106,22 @@
    * @throws EiffelEventIdLookupException when failing to lookup event from Eiffel.
    */
   public Optional<List<UUID>> getParentLinks(EventKey key) 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) &&
+   * this.getEventType().equals(SCS)}
+   *
+   * <p>is true, the links.target for the link where links.type is CHANGE is returned, otherwise
+   * Optional.empty().
+   *
+   * @param key SourceChangeEventKey
+   * @return Optional<UUID> event.links.target
+   * @throws EiffelEventIdLookupException when failing to lookup event from Eiffel.
+   */
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey 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 2138164..e73dbd2 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.CHANGE;
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType.PREVIOUS_VERSION;
 
 import com.google.common.collect.Lists;
@@ -198,6 +199,29 @@
   }
 
   @Override
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey 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 Arrays.stream(fromHub.links)
+              .filter(link -> link.type == CHANGE)
+              .map(link -> link.target)
+              .findAny();
+        }
+        throw new EiffelEventIdLookupException(
+            "Key: %s does not match found event: %s.", key, EventKey.fromEvent(fromHub));
+      }
+    } finally {
+      idLookupLock.unlock();
+    }
+    return idCache.getSccEventLink(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 59775e0..c542821 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
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.eventseiffel.cache;
 
 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.List;
 import java.util.Optional;
@@ -49,6 +50,19 @@
   Optional<List<UUID>> getParentLinks(EventKey key) throws EiffelEventIdLookupException;
 
   /**
+   * Returns the id that is linked with CHANGE by SCS-event that matches:
+   *
+   * <p>{@code EventKey.fromEvent(event).equals(key)}
+   *
+   * <p>Optional.Empty if no such event exists or if key is not a SCS key
+   *
+   * @param key
+   * @return Optional<UUID>
+   * @throws EiffelEventIdLookupException if value failed to load.
+   */
+  Optional<UUID> getSccEventLink(SourceChangeEventKey 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 923039b..0b14355 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
@@ -143,6 +143,19 @@
     }
   }
 
+  @Override
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey key)
+      throws EiffelEventIdLookupException {
+    try {
+      return eventStorage.getSccEventLink(key);
+    } catch (EventStorageException e) {
+      throw new EiffelEventIdLookupException(
+          e,
+          key.type(),
+          String.format("failed to lookup Eiffel SCC event link 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 002f6bc..1ae2d66 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
@@ -17,6 +17,7 @@
 import com.google.common.flogger.FluentLogger;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EiffelRepoApiConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
 import java.net.Authenticator;
 import java.net.PasswordAuthentication;
 import java.net.http.HttpClient;
@@ -65,6 +66,11 @@
   }
 
   @Override
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey key) throws EventStorageException {
+    return restClient.getSccEventLink(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 1936110..784c18a 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,7 @@
 
 package com.googlesource.gerrit.plugins.eventseiffel.eiffel.api;
 
+import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType.CHANGE;
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType.PREVIOUS_VERSION;
 
 import com.github.rholder.retry.RetryException;
@@ -96,6 +97,33 @@
     return Optional.of(qr.getParentLinks());
   }
 
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey key) throws EventStorageException {
+    String query;
+    switch (key.type()) {
+      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 qr.getSccLink();
+  }
+
   private QueryResult restQuery(String query) throws EventStorageException {
     HttpResponse<String> response;
     try {
@@ -155,6 +183,10 @@
       return getLinks(PREVIOUS_VERSION).collect(Collectors.toList());
     }
 
+    Optional<UUID> getSccLink() {
+      return getLinks(CHANGE).findAny();
+    }
+
     private Stream<UUID> getLinks(EiffelLinkType linkType) {
       return Arrays.stream(items.get(0).links)
           .filter(link -> link.type == linkType)
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 56be459..ed2bb68 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
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
@@ -44,6 +45,16 @@
   public Optional<List<UUID>> getParentLinks(EventKey key) throws EventStorageException;
 
   /**
+   * Returns the links.target for links where links.type is CHANGE for the event that matches
+   * EventKey key. Optional.empty() if there exists no such event or if key is not of the type SCS.
+   *
+   * @param key
+   * @return {@link Optional} of {@link UUID} id of the linked event.
+   * @throws EventStorageException
+   */
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey 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.
@@ -67,6 +78,11 @@
     }
 
     @Override
+    public Optional<UUID> getSccEventLink(SourceChangeEventKey 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 9957d69..f2654d3 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java
@@ -83,6 +83,19 @@
                     .collect(Collectors.toList()));
   }
 
+  @Override
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey key)
+      throws EiffelEventIdLookupException {
+    return findEvent(key)
+        .map(
+            event ->
+                Arrays.stream(event.links)
+                    .filter(link -> link.type.equals(EiffelLinkType.CHANGE))
+                    .map(link -> link.target)
+                    .findAny()
+                    .get());
+  }
+
   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 74196c8..7f70500 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventStorage.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventStorage.java
@@ -70,6 +70,11 @@
     return Optional.of(Lists.newArrayList());
   }
 
+  @Override
+  public Optional<UUID> getSccEventLink(SourceChangeEventKey key) throws EventStorageException {
+    return Optional.empty();
+  }
+
   private boolean equalsSCSWithRepoAndCommit(EventKey key, String repo, String commit) {
     if (!key.type().equals(SCS)) return false;
     SourceChangeEventKey scsKey = (SourceChangeEventKey) key;