From 641a3f42587c8559b1313ec91b89592a5074a487 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 13 Sep 2026 18:51:35 +0200 Subject: [PATCH] bug6288: use TimePoint instead of long millis in LiveContentCheckResult and SailingServer.getLiveContent Change SailingServer.getLiveContent(long) to accept a TimePoint and refactor LiveContentCheckResult to hold a TimePoint (checkedAt) rather than a long (checkedAtMillis), matching how TimePoint is used elsewhere in the SailingService[Write] API. Also adds Javadoc to getLiveContent. Serialization: - GWT RPC now writes/reads the TimePoint as an object (TimePoint is GWT-serializable and already used across the RPC boundary). - JSON keeps the long millisecond value (or null) on the wire under the unchanged checkedAtMillis key; the deserializer wraps it back into a TimePoint via TimePoint.of(...). Regenerated serialVersionUID for the changed field layout of LiveContentCheckResult. Assisted-By: Claude (claude-4.8-opus) --- .../common/LiveContentCheckResult.java | 14 +++++++------ .../test/TestLiveContentValueTypes.java | 11 ++++++---- ...tentCheckResult_CustomFieldSerializer.java | 7 ++++--- .../landscape/impl/LandscapeServiceImpl.java | 6 +++--- .../gateway/interfaces/SailingServer.java | 21 ++++++++++++++++++- ...iveContentCheckResultJsonDeserializer.java | 5 +++-- .../LiveContentCheckResultJsonSerializer.java | 3 ++- .../gateway/impl/SailingServerImpl.java | 5 +++-- .../jaxrs/api/LiveContentResource.java | 2 +- 9 files changed, 51 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sailing.landscape.common/src/com/sap/sailing/landscape/common/LiveContentCheckResult.java b/java/com.sap.sailing.landscape.common/src/com/sap/sailing/landscape/common/LiveContentCheckResult.java index 67061830aa0..5df4a30b5e3 100644 --- a/java/com.sap.sailing.landscape.common/src/com/sap/sailing/landscape/common/LiveContentCheckResult.java +++ b/java/com.sap.sailing.landscape.common/src/com/sap/sailing/landscape/common/LiveContentCheckResult.java @@ -5,25 +5,27 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import com.sap.sse.common.TimePoint; + /** * Describes live tracked races found in one or more application replica sets at one instant in time. An empty * {@link #getReplicaSetsWithLiveContent() list} means that no live content was found. */ public final class LiveContentCheckResult implements Serializable { - private static final long serialVersionUID = 4259503344851123760L; - private final long checkedAtMillis; + private static final long serialVersionUID = 3100475095484004645L; + private final TimePoint checkedAt; private final List replicaSetsWithLiveContent; - public LiveContentCheckResult(final long checkedAtMillis, + public LiveContentCheckResult(final TimePoint checkedAt, final Iterable replicaSetsWithLiveContent) { - this.checkedAtMillis = checkedAtMillis; + this.checkedAt = checkedAt; final List replicaSetsWithLiveContentCopy = new ArrayList<>(); replicaSetsWithLiveContent.forEach(replicaSetsWithLiveContentCopy::add); this.replicaSetsWithLiveContent = Collections.unmodifiableList(replicaSetsWithLiveContentCopy); } - public long getCheckedAtMillis() { - return checkedAtMillis; + public TimePoint getCheckedAt() { + return checkedAt; } public List getReplicaSetsWithLiveContent() { diff --git a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestLiveContentValueTypes.java b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestLiveContentValueTypes.java index 738b081ae99..c88eac02c01 100644 --- a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestLiveContentValueTypes.java +++ b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestLiveContentValueTypes.java @@ -15,13 +15,16 @@ import com.sap.sailing.landscape.common.LiveContentAwareOperationResult; import com.sap.sailing.landscape.common.LiveContentCheckResult; import com.sap.sailing.landscape.common.RaceLiveContent; import com.sap.sailing.landscape.common.ReplicaSetLiveContent; +import com.sap.sse.common.TimePoint; public class TestLiveContentValueTypes { + private static final TimePoint CHECKED_AT = TimePoint.of(42L); + @Test public void testEmptyCheckResult() { - final LiveContentCheckResult result = new LiveContentCheckResult(42L, Collections.emptyList()); + final LiveContentCheckResult result = new LiveContentCheckResult(CHECKED_AT, Collections.emptyList()); assertFalse(result.hasLiveContent()); - assertEquals(42L, result.getCheckedAtMillis()); + assertEquals(CHECKED_AT, result.getCheckedAt()); } @Test @@ -31,7 +34,7 @@ public class TestLiveContentValueTypes { Collections.singleton(race)); final ReplicaSetLiveContent replicaSet = new ReplicaSetLiveContent("replica-set", Collections.singleton(event)); - final LiveContentCheckResult result = new LiveContentCheckResult(42L, Collections.singleton(replicaSet)); + final LiveContentCheckResult result = new LiveContentCheckResult(CHECKED_AT, Collections.singleton(replicaSet)); assertTrue(result.hasLiveContent()); assertEquals("race", result.getReplicaSetsWithLiveContent().get(0).getEventsWithLiveContent().get(0) .getRacesWithLiveContent().get(0).getRaceName()); @@ -40,7 +43,7 @@ public class TestLiveContentValueTypes { @Test public void testOperationResultInvariants() { - final LiveContentCheckResult conflict = new LiveContentCheckResult(42L, Collections.emptyList()); + final LiveContentCheckResult conflict = new LiveContentCheckResult(CHECKED_AT, Collections.emptyList()); assertTrue(LiveContentAwareOperationResult.success("ok").isSuccessful()); assertFalse(LiveContentAwareOperationResult.liveContentConflict(conflict).isSuccessful()); assertThrows(IllegalArgumentException.class, () -> LiveContentAwareOperationResult.liveContentConflict(null)); diff --git a/java/com.sap.sailing.landscape.ui/src/com/google/gwt/user/client/rpc/core/com/sap/sailing/landscape/common/LiveContentCheckResult_CustomFieldSerializer.java b/java/com.sap.sailing.landscape.ui/src/com/google/gwt/user/client/rpc/core/com/sap/sailing/landscape/common/LiveContentCheckResult_CustomFieldSerializer.java index 8384f81ea9c..458c4736662 100644 --- a/java/com.sap.sailing.landscape.ui/src/com/google/gwt/user/client/rpc/core/com/sap/sailing/landscape/common/LiveContentCheckResult_CustomFieldSerializer.java +++ b/java/com.sap.sailing.landscape.ui/src/com/google/gwt/user/client/rpc/core/com/sap/sailing/landscape/common/LiveContentCheckResult_CustomFieldSerializer.java @@ -9,11 +9,12 @@ import com.google.gwt.user.client.rpc.SerializationStreamReader; import com.google.gwt.user.client.rpc.SerializationStreamWriter; import com.sap.sailing.landscape.common.LiveContentCheckResult; import com.sap.sailing.landscape.common.ReplicaSetLiveContent; +import com.sap.sse.common.TimePoint; public final class LiveContentCheckResult_CustomFieldSerializer extends CustomFieldSerializer { public static void serialize(final SerializationStreamWriter writer, final LiveContentCheckResult instance) throws SerializationException { - writer.writeLong(instance.getCheckedAtMillis()); + writer.writeObject(instance.getCheckedAt()); writer.writeInt(instance.getReplicaSetsWithLiveContent().size()); for (final ReplicaSetLiveContent replicaSet : instance.getReplicaSetsWithLiveContent()) { writer.writeObject(replicaSet); @@ -22,13 +23,13 @@ public final class LiveContentCheckResult_CustomFieldSerializer extends CustomFi public static LiveContentCheckResult instantiate(final SerializationStreamReader reader) throws SerializationException { - final long checkedAtMillis = reader.readLong(); + final TimePoint checkedAt = (TimePoint) reader.readObject(); final int replicaSetCount = reader.readInt(); final List replicaSets = new ArrayList<>(replicaSetCount); for (int i = 0; i < replicaSetCount; i++) { replicaSets.add((ReplicaSetLiveContent) reader.readObject()); } - return new LiveContentCheckResult(checkedAtMillis, replicaSets); + return new LiveContentCheckResult(checkedAt, replicaSets); } public static void deserialize(final SerializationStreamReader reader, final LiveContentCheckResult instance) { diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/impl/LandscapeServiceImpl.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/impl/LandscapeServiceImpl.java index 97ce2a6b37f..651a99481a5 100644 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/impl/LandscapeServiceImpl.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/impl/LandscapeServiceImpl.java @@ -596,14 +596,14 @@ public class LandscapeServiceImpl implements LandscapeService { final Iterable>> applicationReplicaSets, final String bearerToken) throws Exception { final String effectiveBearerToken = getEffectiveBearerToken(bearerToken); - final long checkedAtMillis = TimePoint.now().asMillis(); + final TimePoint checkedAt = TimePoint.now(); final List replicaSetsWithLiveContent = new ArrayList<>(); for (final AwsApplicationReplicaSet> replicaSet : applicationReplicaSets) { final SailingServer server = sailingServerFactoryTracker.getService().getSailingServer(new URL("https", replicaSet.getHostname(), "/"), effectiveBearerToken); - final LiveContentCheckResult replicaSetResult = server.getLiveContent(checkedAtMillis); + final LiveContentCheckResult replicaSetResult = server.getLiveContent(checkedAt); replicaSetsWithLiveContent.addAll(replicaSetResult.getReplicaSetsWithLiveContent()); } - return new LiveContentCheckResult(checkedAtMillis, replicaSetsWithLiveContent); + return new LiveContentCheckResult(checkedAt, replicaSetsWithLiveContent); } private void checkForLiveContentUnlessForced( diff --git a/java/com.sap.sailing.server.gateway.interfaces/src/com/sap/sailing/server/gateway/interfaces/SailingServer.java b/java/com.sap.sailing.server.gateway.interfaces/src/com/sap/sailing/server/gateway/interfaces/SailingServer.java index 73413579f36..9800518a55c 100755 --- a/java/com.sap.sailing.server.gateway.interfaces/src/com/sap/sailing/server/gateway/interfaces/SailingServer.java +++ b/java/com.sap.sailing.server.gateway.interfaces/src/com/sap/sailing/server/gateway/interfaces/SailingServer.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.landscape.common.LiveContentCheckResult; +import com.sap.sse.common.TimePoint; import com.sap.sse.security.util.SecuredServer; import com.sap.sse.shared.json.JsonDeserializationException; @@ -56,7 +57,25 @@ public interface SailingServer extends SecuredServer { */ Iterable getEvents() throws Exception; - LiveContentCheckResult getLiveContent(long checkedAtMillis) throws Exception; + /** + * Asks the remote server which of its {@link EventBase events} were tracking live content (races being tracked) + * at a given instant in time, reusing the {@code /v1/livecontent} endpoint. This is a Java facade over that REST + * call and is executed with this object's authentication information (see the {@link SailingServer class + * documentation}); the remote endpoint requires {@code MANAGE} permission on the {@code AWS} landscape object. + * + * @param checkedAt + * the instant for which live content shall be determined. A race counts as live content if it was being + * tracked at that instant. Pass {@link TimePoint#now()} to find out what is live right now. + * @return a {@link LiveContentCheckResult} describing, per replica set, the events and races that were tracked + * live at {@code checkedAt}. The result is never {@code null}; an empty + * {@link LiveContentCheckResult#getReplicaSetsWithLiveContent() list} (equivalently, + * {@link LiveContentCheckResult#hasLiveContent()} returning {@code false}) means no live content was found. + * @throws Exception + * if the remote request cannot be sent or its response cannot be parsed or deserialized; concrete + * implementations may throw more specific exceptions such as {@link IOException}, + * {@link ClientProtocolException}, {@link ParseException} or {@link JsonDeserializationException}. + */ + LiveContentCheckResult getLiveContent(TimePoint checkedAt) throws Exception; MasterDataImportResult importMasterData(SailingServer from, Iterable leaderboardGroupIds, boolean override, boolean compress, boolean exportWind, boolean exportDeviceConfigs, diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LiveContentCheckResultJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LiveContentCheckResultJsonDeserializer.java index ca16c050424..b53c521ab82 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LiveContentCheckResultJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LiveContentCheckResultJsonDeserializer.java @@ -11,6 +11,7 @@ import com.sap.sailing.landscape.common.LiveContentCheckResult; import com.sap.sailing.landscape.common.RaceLiveContent; import com.sap.sailing.landscape.common.ReplicaSetLiveContent; import com.sap.sailing.server.gateway.serialization.impl.LiveContentCheckResultJsonSerializer; +import com.sap.sse.common.TimePoint; import com.sap.sse.shared.json.JsonDeserializationException; import com.sap.sse.shared.json.JsonDeserializer; @@ -45,8 +46,8 @@ public final class LiveContentCheckResultJsonDeserializer implements JsonDeseria replicaSets.add(new ReplicaSetLiveContent( (String) serializedReplicaSet.get(LiveContentCheckResultJsonSerializer.REPLICA_SET_NAME), events)); } - return new LiveContentCheckResult( - ((Number) object.get(LiveContentCheckResultJsonSerializer.CHECKED_AT_MILLIS)).longValue(), replicaSets); + final Long checkedAtMillis = asLong(object.get(LiveContentCheckResultJsonSerializer.CHECKED_AT_MILLIS)); + return new LiveContentCheckResult(checkedAtMillis == null ? null : TimePoint.of(checkedAtMillis), replicaSets); } private Long asLong(final Object value) { diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LiveContentCheckResultJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LiveContentCheckResultJsonSerializer.java index 3fed7fc0ac8..0497ebce586 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LiveContentCheckResultJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LiveContentCheckResultJsonSerializer.java @@ -27,7 +27,8 @@ public final class LiveContentCheckResultJsonSerializer implements JsonSerialize @Override public JSONObject serialize(final LiveContentCheckResult liveContentCheckResult) { final JSONObject result = new JSONObject(); - result.put(CHECKED_AT_MILLIS, liveContentCheckResult.getCheckedAtMillis()); + result.put(CHECKED_AT_MILLIS, liveContentCheckResult.getCheckedAt() == null ? null + : liveContentCheckResult.getCheckedAt().asMillis()); final JSONArray replicaSets = new JSONArray(); for (final ReplicaSetLiveContent replicaSet : liveContentCheckResult.getReplicaSetsWithLiveContent()) { final JSONObject serializedReplicaSet = new JSONObject(); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/SailingServerImpl.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/SailingServerImpl.java index 68274feada2..1c01b7bd6ce 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/SailingServerImpl.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/SailingServerImpl.java @@ -48,6 +48,7 @@ import com.sap.sailing.server.gateway.jaxrs.api.EventsResource; import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource; import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; import com.sap.sailing.server.gateway.jaxrs.api.LiveContentResource; +import com.sap.sse.common.TimePoint; import com.sap.sailing.server.gateway.jaxrs.api.MasterDataImportResource; import com.sap.sailing.server.gateway.jaxrs.api.RemoteServerReferenceResource; import com.sap.sailing.server.gateway.serialization.LeaderboardGroupConstants; @@ -130,10 +131,10 @@ public class SailingServerImpl extends SecuredServerImpl implements SailingServe } @Override - public LiveContentCheckResult getLiveContent(final long checkedAtMillis) throws ClientProtocolException, IOException, + public LiveContentCheckResult getLiveContent(final TimePoint checkedAt) throws ClientProtocolException, IOException, ParseException, JsonDeserializationException { final URL liveContentUrl = new URL(getBaseUrl(), GATEWAY_URL_PREFIX + LiveContentResource.V1_LIVE_CONTENT + - "?" + LiveContentResource.CHECKED_AT_MILLIS_QUERY_PARAM + "=" + checkedAtMillis); + "?" + LiveContentResource.CHECKED_AT_MILLIS_QUERY_PARAM + "=" + checkedAt.asMillis()); final HttpGet getLiveContent = new HttpGet(liveContentUrl.toString()); final JSONObject jsonResponse = (JSONObject) getJsonParsedResponse(getLiveContent).getA(); return new LiveContentCheckResultJsonDeserializer().deserialize(jsonResponse); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LiveContentResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LiveContentResource.java index f4d01a94582..3e90deba02c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LiveContentResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LiveContentResource.java @@ -61,7 +61,7 @@ public final class LiveContentResource extends AbstractSailingServerResource { if (!eventsWithLiveContent.isEmpty()) { replicaSetsWithLiveContent.add(new ReplicaSetLiveContent(ServerInfo.getName(), eventsWithLiveContent)); } - final LiveContentCheckResult result = new LiveContentCheckResult(checkedAt.asMillis(), + final LiveContentCheckResult result = new LiveContentCheckResult(checkedAt, replicaSetsWithLiveContent); return Response.ok(streamingOutput(new LiveContentCheckResultJsonSerializer().serialize(result))).build(); }