Use millisecond accuracy for IndexEvent.eventCreatedOn

Also use the more modern `Instant` API.

Change-Id: I449aefca0fb47fc3d90dd4c47a9d823689a2681c
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java
index 71cf044..129af6b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java
@@ -14,12 +14,13 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
+import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 
 public class IndexEvent {
-  public long eventCreatedOn = System.currentTimeMillis() / 1000;
+  public Instant eventCreatedOn = Instant.now();
   public String targetSha;
   public String metaSha;
 
@@ -31,8 +32,7 @@
         + ((metaSha != null) ? "/meta:" + metaSha : "");
   }
 
-  public static String format(long eventTs) {
-    return LocalDateTime.ofEpochSecond(eventTs, 0, ZoneOffset.UTC)
-        .format(DateTimeFormatter.ISO_DATE_TIME);
+  public static String format(Instant eventTs) {
+    return LocalDateTime.ofInstant(eventTs, ZoneOffset.UTC).format(DateTimeFormatter.ISO_DATE_TIME);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/InstantTypeAdapter.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/InstantTypeAdapter.java
new file mode 100644
index 0000000..df8780c
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/InstantTypeAdapter.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2025 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.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+import java.time.Instant;
+
+public class InstantTypeAdapter implements JsonDeserializer<Instant>, JsonSerializer<Instant> {
+  @Override
+  public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+      throws JsonParseException {
+    if (json == null || !json.isJsonPrimitive() || !json.getAsJsonPrimitive().isNumber()) {
+      throw new JsonParseException("Invalid Instant value: " + json);
+    }
+    return Instant.ofEpochMilli(json.getAsLong());
+  }
+
+  @Override
+  public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
+    return context.serialize(src.toEpochMilli(), Long.class);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
index 58e7aeb..dee4a6d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
@@ -24,6 +24,7 @@
 import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import dev.failsafe.FailsafeExecutor;
+import java.time.Instant;
 import org.jgroups.blocks.MessageDispatcher;
 import org.jgroups.blocks.RequestHandler;
 
@@ -50,6 +51,7 @@
     return eventGson
         .newBuilder()
         .registerTypeAdapter(Command.class, new CommandDeserializer())
+        .registerTypeAdapter(Instant.class, new InstantTypeAdapter())
         .create();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
index 6d801f6..330afbb 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -14,6 +14,7 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
+import com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups.InstantTypeAdapter;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
 import com.google.common.net.MediaType;
 import com.google.gerrit.server.events.EventGson;
@@ -22,6 +23,7 @@
 import java.io.IOException;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
 import org.apache.http.client.methods.HttpPost;
@@ -35,7 +37,9 @@
   @Inject
   HttpSession(CloseableHttpClient httpClient, @EventGson Gson gson) {
     this.httpClient = httpClient;
-    this.gson = gson;
+
+    this.gson =
+        gson.newBuilder().registerTypeAdapter(Instant.class, new InstantTypeAdapter()).create();
   }
 
   HttpResult post(String uri) throws IOException {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
index 0ed6160..f702d87 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
@@ -17,6 +17,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Optional;
 
 /** Encapsulates the logic of verifying the up-to-date status of a change. */
@@ -51,8 +52,8 @@
    *
    * <p>Compute the up-to-date Change time-stamp when it is invoked for the very first time.
    *
-   * @return the Change timestamp epoch in seconds
+   * @return the Change timestamp instant
    * @throws IOException if an I/O error occurred while reading the local Change
    */
-  Optional<Long> getComputedChangeTs() throws IOException;
+  Optional<Instant> getComputedChangeTs() throws IOException;
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
index a392935..6156fb5 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -26,6 +26,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Objects;
 import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
@@ -38,7 +39,7 @@
   private final OneOffRequestContext oneOffReqCtx;
   private final String changeId;
   private final ChangeFinder changeFinder;
-  private Optional<Long> computedChangeTs = Optional.empty();
+  private Optional<Instant> computedChangeTs = Optional.empty();
   private Optional<ChangeNotes> changeNotes = Optional.empty();
 
   public interface Factory {
@@ -59,12 +60,12 @@
 
   @Override
   public Optional<IndexEvent> newIndexEvent() throws IOException {
-    Optional<Long> changeTs = getComputedChangeTs();
+    Optional<Instant> changeTs = getComputedChangeTs();
     if (!changeTs.isPresent()) {
       return Optional.empty();
     }
 
-    long ts = changeTs.get();
+    Instant ts = changeTs.get();
 
     IndexEvent event = new IndexEvent();
     event.eventCreatedOn = ts;
@@ -99,12 +100,9 @@
       if (indexEventOption.isPresent()) {
         try (Repository repo = gitRepoMgr.openRepository(changeNotes.get().getProjectName())) {
           IndexEvent indexEvent = indexEventOption.get();
-          return (computedChangeTs.get() > indexEvent.eventCreatedOn)
-              || (computedChangeTs.get() == indexEvent.eventCreatedOn)
-                  && (Objects.isNull(indexEvent.targetSha)
-                      || repositoryHas(repo, indexEvent.targetSha))
-                  && (Objects.isNull(indexEvent.targetSha)
-                      || repositoryHas(repo, indexEvent.metaSha));
+          return computedChangeTs.get().compareTo(indexEvent.eventCreatedOn) >= 0
+              && (Objects.isNull(indexEvent.targetSha) || repositoryHas(repo, indexEvent.targetSha))
+              && (Objects.isNull(indexEvent.targetSha) || repositoryHas(repo, indexEvent.metaSha));
         }
       }
       return true;
@@ -116,7 +114,7 @@
   }
 
   @Override
-  public Optional<Long> getComputedChangeTs() {
+  public Optional<Instant> getComputedChangeTs() {
     if (!computedChangeTs.isPresent()) {
       computedChangeTs = computeLastChangeTs();
     }
@@ -166,7 +164,7 @@
     }
   }
 
-  private Optional<Long> computeLastChangeTs() {
+  private Optional<Instant> computeLastChangeTs() {
     return getChangeNotes().map(this::getTsFromChange);
   }
 
@@ -180,8 +178,8 @@
     return ref.getTarget().getObjectId().getName();
   }
 
-  private long getTsFromChange(ChangeNotes notes) {
+  private Instant getTsFromChange(ChangeNotes notes) {
     Change change = notes.getChange();
-    return change.getLastUpdatedOn().toEpochMilli() / 1000;
+    return change.getLastUpdatedOn();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java
index dfea1df..460d56b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import java.io.IOException;
-import java.sql.Timestamp;
 import java.time.Instant;
 import java.util.Optional;
 import org.junit.Before;
@@ -45,7 +44,7 @@
   private final Instant testLastUpdatedOn = Instant.now();
   private final String changeId = "1";
   Optional<IndexEvent> event = Optional.empty();
-  private Optional<Long> computedChangeTs = Optional.empty();
+  private Optional<Instant> computedChangeTs = Optional.empty();
   private ChangeCheckerImpl changeChecker;
 
   @Before
@@ -61,8 +60,7 @@
 
   @Test
   public void testGetComputedChangeTs() {
-    long testTime = Timestamp.from(testLastUpdatedOn).getTime();
-    computedChangeTs = Optional.of(testTime / 1000);
+    computedChangeTs = Optional.of(testLastUpdatedOn);
     when(changeChecker.getChangeNotes()).thenReturn(Optional.of(testChangeNotes));
     when(testChangeNotes.getChange()).thenReturn(testChange);
     when(testChange.getLastUpdatedOn()).thenReturn(testLastUpdatedOn);