Extract the task description from EventParsingWorker

To prepare for serialization of the unfinished tasks in the
event-queue on restart. Also keep updater, udatedTime, previousTip
and force in description so that we can requeue an identical task from
the persisted description.

Solves: Jira GER-1715
Change-Id: Ibe97e3bbe6ad62274696100ff6537edb266b041b
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java
index e477baa..c5d10b8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
-import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledFuture;
 
@@ -50,10 +49,8 @@
   public void scheduleSccCreation(RevisionCreatedListener.Event event) {
     schedule(
         new EventParsingWorker(
-            SCC,
-            event.getChange().project,
-            event.getChange().branch,
-            event.getRevision().commit.commit) {
+            ParsingQueueTask.builder(SCC, event.getChange().project, event.getChange().branch)
+                .commit(event.getRevision().commit.commit)) {
 
           @Override
           public void doRun() {
@@ -76,7 +73,7 @@
    */
   public void scheduleSccCreation(String repoName, String branchRef) {
     schedule(
-        new EventParsingWorker(SCC, repoName, branchRef, null) {
+        new EventParsingWorker(ParsingQueueTask.builder(SCS, repoName, branchRef)) {
 
           @Override
           public void doRun() {
@@ -99,7 +96,7 @@
    */
   public void scheduleSccCreation(String repoName, String branchRef, String commit) {
     schedule(
-        new EventParsingWorker(SCC, repoName, branchRef, commit) {
+        new EventParsingWorker(ParsingQueueTask.builder(SCS, repoName, branchRef).commit(commit)) {
 
           @Override
           public void doRun() {
@@ -116,7 +113,11 @@
   public void scheduleScsCreation(GitReferenceUpdatedListener.Event event) {
     schedule(
         new EventParsingWorker(
-            SCS, event.getProjectName(), event.getRefName(), event.getNewObjectId()) {
+            ParsingQueueTask.builder(SCS, event.getProjectName(), event.getRefName())
+                .commit(event.getNewObjectId())
+                .updater(event.getUpdater())
+                .updateTime(TimeUtil.nowMs())
+                .previousTip(event.getOldObjectId())) {
 
           @Override
           public void doRun() {
@@ -138,7 +139,7 @@
 
   public void scheduleScsCreation(String repoName, String branchRef) {
     schedule(
-        new EventParsingWorker(SCS, repoName, branchRef, null) {
+        new EventParsingWorker(ParsingQueueTask.builder(SCS, repoName, branchRef)) {
 
           @Override
           public void doRun() {
@@ -169,7 +170,10 @@
             ? tagRefOrName.substring(RefNames.REFS_TAGS.length())
             : tagRefOrName;
     schedule(
-        new EventParsingWorker(ARTC, projectName, tagName) {
+        new EventParsingWorker(
+            ParsingQueueTask.builder(ARTC, projectName, tagName)
+                .updateTime(creationTime)
+                .force(force)) {
 
           @Override
           public void doRun() {
@@ -202,25 +206,11 @@
   }
 
   abstract class EventParsingWorker implements ProjectRunnable {
-    private final String repoName;
-    private final String refName;
-    private final String commitId;
-    private final EiffelEventType type;
-    private boolean running;
+    private final ParsingQueueTask task;
+    boolean running;
 
-    public EventParsingWorker(EiffelEventType type, String repoName, String refName) {
-      this(type, repoName, refName, null);
-    }
-
-    public EventParsingWorker(
-        EiffelEventType type, String repoName, String refName, String commitId) {
-      this.type = type;
-      this.repoName = repoName;
-      this.refName =
-          refName.startsWith(RefNames.REFS_HEADS)
-              ? refName.substring(RefNames.REFS_HEADS.length())
-              : refName;
-      this.commitId = commitId;
+    public EventParsingWorker(ParsingQueueTask.Builder taskBuilder) {
+      this.task = taskBuilder.build();
     }
 
     public abstract void doRun();
@@ -235,7 +225,7 @@
 
     @Override
     public NameKey getProjectNameKey() {
-      return Project.nameKey(repoName);
+      return Project.nameKey(task.repoName);
     }
 
     @Override
@@ -250,14 +240,7 @@
 
     @Override
     public String toString() {
-      if (commitId != null) {
-        return String.format(
-            "Parsing: %s[%s, %s, %s]%s",
-            type.name(), repoName, refName, commitId, running ? " (running)" : "");
-      }
-      return String.format(
-          "Parsing from branch: %s[%s, %s]%s",
-          type.name(), repoName, refName, running ? " (running)" : "");
+      return String.format("Parsing: %s%s", task, running ? " (running)" : "");
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTask.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTask.java
new file mode 100644
index 0000000..4513e24
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTask.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2022 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.googlesource.gerrit.plugins.eventseiffel.parsing;
+
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType;
+import java.util.Objects;
+
+/**
+ * DTO that contains all necessary data to describe and reschedule a task in the {@link
+ * EiffelEventParsingQueue}.
+ */
+public class ParsingQueueTask {
+  static Builder builder(EiffelEventType type, String repoName, String branchRefOrTag) {
+    return new Builder(type, repoName, branchRefOrTag);
+  }
+
+  public static class Builder {
+    private final EiffelEventType _type;
+    private final String _repoName;
+    private final String _branchRefOrTag;
+    private String _commitId = null;
+    private AccountInfo _updater = null;
+    private Long _updateTime = null;
+    private Boolean _force = null;
+    private String _previousTip = null;
+
+    private Builder(EiffelEventType type, String repoName, String branchRefOrTag) {
+      this._type = type;
+      this._repoName = repoName;
+      this._branchRefOrTag = branchRefOrTag;
+    }
+
+    public Builder commit(String commitId) {
+      this._commitId = commitId;
+      return this;
+    }
+
+    public Builder updater(AccountInfo updater) {
+      this._updater = updater;
+      return this;
+    }
+
+    public Builder updateTime(Long updateTime) {
+      this._updateTime = updateTime;
+      return this;
+    }
+
+    public Builder force(boolean force) {
+      this._force = force;
+      return this;
+    }
+
+    public Builder previousTip(String previousTip) {
+      this._previousTip = previousTip;
+      return this;
+    }
+
+    public ParsingQueueTask build() {
+      return new ParsingQueueTask(
+          this._type,
+          this._repoName,
+          this._branchRefOrTag,
+          this._commitId,
+          this._updater,
+          this._updateTime,
+          this._previousTip,
+          this._force);
+    }
+  }
+
+  final String repoName;
+  final String branchRefOrTag;
+  final String commitId;
+  final EiffelEventType type;
+  final AccountInfo updater;
+  final Long updateTime;
+  final Boolean force;
+  final String previousTip;
+
+  private ParsingQueueTask(
+      EiffelEventType type,
+      String repoName,
+      String branchRefOrTag,
+      String commitId,
+      AccountInfo updater,
+      Long updateTime,
+      String previousTip,
+      Boolean force) {
+    this.type = type;
+    this.repoName = repoName;
+    this.branchRefOrTag = branchRefOrTag;
+    this.commitId = commitId;
+    this.updater = updater;
+    this.updateTime = updateTime;
+    this.previousTip = previousTip;
+    this.force = force;
+  }
+
+  @Override
+  public String toString() {
+    String target =
+        branchRefOrTag.startsWith(RefNames.REFS_HEADS)
+            ? branchRefOrTag.substring(RefNames.REFS_HEADS.length())
+            : branchRefOrTag;
+    if (commitId != null) {
+      return String.format("%s[%s, %s, %s]", type.name(), repoName, target, commitId);
+    }
+    return String.format("%s[%s, %s]", type.name(), repoName, target);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof ParsingQueueTask) {
+      ParsingQueueTask that = (ParsingQueueTask) o;
+      return Objects.equals(this.type, that.type)
+          && Objects.equals(this.repoName, that.repoName)
+          && Objects.equals(this.commitId, that.commitId)
+          && Objects.equals(this.updater, that.updater)
+          && Objects.equals(this.updateTime, that.updateTime)
+          && Objects.equals(this.previousTip, that.previousTip)
+          && Objects.equals(this.force, that.force);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(type, repoName, commitId, updater, updateTime, previousTip, force);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTaskTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTaskTest.java
new file mode 100644
index 0000000..6c85763
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTaskTest.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2022 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.googlesource.gerrit.plugins.eventseiffel.parsing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gson.Gson;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType;
+import org.junit.Test;
+
+public class ParsingQueueTaskTest {
+
+  @Test
+  public void testParsingQueueTaskSerializeable() {
+    AccountInfo accountInfo = new AccountInfo(1);
+    ParsingQueueTask task =
+        ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch")
+            .commit("commit-sha1")
+            .updater(accountInfo)
+            .updateTime(0L)
+            .previousTip("previous")
+            .force(true)
+            .build();
+    String json = new Gson().toJson(task);
+    String expected =
+        "{\"repoName\":\"repo\",\"branchRefOrTag\":\"branch\",\"commitId\":\"commit-sha1\",\"type\":\"EiffelSourceChangeCreatedEvent\",\"updater\":{\"_accountId\":1},\"updateTime\":0,\"force\":true,\"previousTip\":\"previous\"}";
+    assertEquals(json, expected);
+  }
+
+  @Test
+  public void testParsingQueueTaskDeserializeable() {
+    String json =
+        "{\"repoName\":\"repo\",\"branchRefOrTag\":\"branch\",\"commitId\":\"commit-sha1\",\"type\":\"EiffelSourceChangeCreatedEvent\",\"updater\":{\"_accountId\":1},\"updateTime\":0,\"force\":true,\"previousTip\":\"previous\"}";
+    ParsingQueueTask deSerialized = new Gson().fromJson(json, ParsingQueueTask.class);
+    assertEquals(deSerialized.repoName, "repo");
+    assertEquals(deSerialized.branchRefOrTag, "branch");
+    assertEquals(deSerialized.commitId, "commit-sha1");
+    assertEquals(deSerialized.type, EiffelEventType.SCC);
+    assertEquals(deSerialized.updater._accountId, Integer.valueOf(1));
+    assertEquals(deSerialized.updateTime, Long.valueOf(0));
+    assertEquals(deSerialized.force, Boolean.TRUE);
+    assertEquals(deSerialized.previousTip, "previous");
+  }
+
+  @Test
+  public void testEqualsAndHashCode() {
+    ParsingQueueTask dis = ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch").build();
+    ParsingQueueTask dat = ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch").build();
+    assertEquals(dis, dat);
+    assertEquals(dis.hashCode(), dat.hashCode());
+    ParsingQueueTask theOther =
+        ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch").commit("commit").build();
+    assertNotEquals(dis, theOther);
+    assertNotEquals(dis.hashCode(), theOther.hashCode());
+  }
+}