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);