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)
This commit is contained in:
Axel Uhl
2026-09-14 23:30:19 +02:00
parent b21a4c3a2d
commit 641a3f4258
9 changed files with 51 additions and 23 deletions
@@ -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<ReplicaSetLiveContent> replicaSetsWithLiveContent;
public LiveContentCheckResult(final long checkedAtMillis,
public LiveContentCheckResult(final TimePoint checkedAt,
final Iterable<ReplicaSetLiveContent> replicaSetsWithLiveContent) {
this.checkedAtMillis = checkedAtMillis;
this.checkedAt = checkedAt;
final List<ReplicaSetLiveContent> replicaSetsWithLiveContentCopy = new ArrayList<>();
replicaSetsWithLiveContent.forEach(replicaSetsWithLiveContentCopy::add);
this.replicaSetsWithLiveContent = Collections.unmodifiableList(replicaSetsWithLiveContentCopy);
}
public long getCheckedAtMillis() {
return checkedAtMillis;
public TimePoint getCheckedAt() {
return checkedAt;
}
public List<ReplicaSetLiveContent> getReplicaSetsWithLiveContent() {
@@ -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));
@@ -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<LiveContentCheckResult> {
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<ReplicaSetLiveContent> 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) {
@@ -596,14 +596,14 @@ public class LandscapeServiceImpl implements LandscapeService {
final Iterable<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> applicationReplicaSets,
final String bearerToken) throws Exception {
final String effectiveBearerToken = getEffectiveBearerToken(bearerToken);
final long checkedAtMillis = TimePoint.now().asMillis();
final TimePoint checkedAt = TimePoint.now();
final List<ReplicaSetLiveContent> replicaSetsWithLiveContent = new ArrayList<>();
for (final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> 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(
@@ -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<EventBase> 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<UUID> leaderboardGroupIds, boolean override,
boolean compress, boolean exportWind, boolean exportDeviceConfigs,
@@ -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) {
@@ -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();
@@ -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);
@@ -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();
}