From 0e8718afae99be5d6592e67d1296543ff82b97eb Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 13 Jul 2023 11:28:35 +0200 Subject: [PATCH] bug5870: fixing the distribution of TracTrac competitor/race status to all race logs attached to a race; this fixes the marathon set-up for iQFOils where the same tracked race is attached to more than one race column at the same time --- ...TracRaceAndCompetitorStatusReconciler.java | 21 +-- ...CompetitorStatusWithRaceLogReconciler.java | 169 ++++++++---------- 2 files changed, 88 insertions(+), 102 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java index e9fd0320d36..bef550b5fab 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java @@ -74,8 +74,8 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { @Override public Pair getRaceLogResultAndCreationTimePointForCompetitor( - TrackedRace trackedRace, Competitor competitor) { - return super.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + RaceLog raceLog, Competitor competitor) { + return super.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); } } @@ -251,9 +251,10 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { final TimePoint resultTimePoint = startOfPass.plus(Duration.ONE_SECOND); when(tractracRaceCompetitor.getStatusLastChangedTime()).thenReturn(resultTimePoint.asMillis()); when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.BFD); + final RaceLog raceLog = trackedRace.getAttachedRaceLogs().iterator().next(); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(resultTimePoint, raceLogBasedResult.getB()); assertEquals(MaxPointsReason.BFD, raceLogBasedResult.getA().getMaxPointsReason()); @@ -264,7 +265,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.DNF); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(resultTimePoint, raceLogBasedResult.getB()); // still expecting to see the result from the later time point assertEquals(MaxPointsReason.BFD, raceLogBasedResult.getA().getMaxPointsReason()); @@ -275,7 +276,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.DNC); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(newerResultTimePoint, raceLogBasedResult.getB()); // expecting to see the result from the newer time point assertEquals(MaxPointsReason.DNC, raceLogBasedResult.getA().getMaxPointsReason()); @@ -286,7 +287,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.NO_DATA); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(yetNewerResultTimePoint, raceLogBasedResult.getB()); // expecting to see the result from the yet newer time point assertEquals(MaxPointsReason.NONE, raceLogBasedResult.getA().getMaxPointsReason()); @@ -302,7 +303,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.NO_DATA); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(resultTimePoint, raceLogBasedResult.getB()); assertEquals(MaxPointsReason.NONE, raceLogBasedResult.getA().getMaxPointsReason()); @@ -314,7 +315,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getOfficialRank()).thenReturn(43); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(resultTimePoint, raceLogBasedResult.getB()); assertEquals(MaxPointsReason.NONE, raceLogBasedResult.getA().getMaxPointsReason()); @@ -326,7 +327,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getOfficialRank()).thenReturn(44); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(newerResultTimePoint, raceLogBasedResult.getB()); assertEquals(MaxPointsReason.NONE, raceLogBasedResult.getA().getMaxPointsReason()); @@ -338,7 +339,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { when(tractracRaceCompetitor.getOfficialRank()).thenReturn(0); reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); { - final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); + final Pair raceLogBasedResult = reconciler.getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); assertNotNull(raceLogBasedResult); assertEquals(yetNewerResultTimePoint, raceLogBasedResult.getB()); assertEquals(MaxPointsReason.NONE, raceLogBasedResult.getA().getMaxPointsReason()); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java index 9517c501b15..1b56362b58e 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java @@ -250,37 +250,38 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler { } } } - final RaceLog defaultRaceLog = getDefaultRaceLog(trackedRace); - final boolean resultsAreOfficial = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, defaultRaceLog).isResultsAreOfficial(); - if (raceStatus == RaceStatusType.OFFICIAL && !resultsAreOfficial) { - logger.info("Race status for race "+trackedRace.getName()+" is OFFICIAL with TracTrac and we have it as not official so far. Scheduling update."); - final Runnable setResultsAreOfficial = ()->{ - logger.info("Setting race status for race "+trackedRace.getName()+" to OFFICIAL now"); - RaceStateImpl.create(raceLogResolver, defaultRaceLog, raceLogEventAuthor).setResultsAreOfficial(raceStatusUpdateTime); - }; - if (officialCompetitorUpdateProvider != null) { - logger.info("Enqueuing the setting of race status for race "+trackedRace.getName()+ - " to OFFICIAL until all competitor updates have been handled"); - officialCompetitorUpdateProvider.runWhenNoMoreOfficialCompetitorUpdatesPending(setResultsAreOfficial); - } else { - setResultsAreOfficial.run(); + for (final RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { + final boolean resultsAreOfficial = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog).isResultsAreOfficial(); + if (raceStatus == RaceStatusType.OFFICIAL && !resultsAreOfficial) { + logger.info("Race status for race "+trackedRace.getName()+" is OFFICIAL with TracTrac and we have it as not official so far. Scheduling update."); + final Runnable setResultsAreOfficial = ()->{ + logger.info("Setting race status for race "+trackedRace.getName()+" to OFFICIAL now"); + RaceStateImpl.create(raceLogResolver, raceLog, raceLogEventAuthor).setResultsAreOfficial(raceStatusUpdateTime); + }; + if (officialCompetitorUpdateProvider != null) { + logger.info("Enqueuing the setting of race status for race "+trackedRace.getName()+ + " to OFFICIAL until all competitor updates have been handled"); + officialCompetitorUpdateProvider.runWhenNoMoreOfficialCompetitorUpdatesPending(setResultsAreOfficial); + } else { + setResultsAreOfficial.run(); + } + } + if (abortingFlagEvent != null && !isAbortedState(raceStatus) && raceStatusUpdateTime.after(abortingFlagEvent.getLogicalTimePoint())) { + logger.info("RaceLog considered race "+trackedRace.getName()+" as aborted ("+abortingFlagEvent+"), and the TracTrac race status "+raceStatus+ + " from "+raceStatusUpdateTime+" suggests it's not aborted. Starting a new pass."); + startNewPass(raceStatusUpdateTime, raceLog); + } else if (isAbortedState(raceStatus) && + (abortingFlagEvent == null || (!abortingFlagMatches(raceStatus, abortingFlagEvent.getUpperFlag()) && + raceStatusUpdateTime.after(abortingFlagEvent.getLogicalTimePoint())))) { + final Flags upperFlag = flagForRaceStatus.get(raceStatus); + logger.info("RaceLog considered race "+trackedRace.getName()+" as NOT aborted, and the TracTrac race status "+raceStatus+ + " from "+raceStatusUpdateTime+" suggests it's aborted. Adding abort flag event "+upperFlag+", starting a new pass."); + final RaceLogFlagEventImpl flagEvent = new RaceLogFlagEventImpl(raceStatusUpdateTime, raceLogEventAuthor, + raceLog.getCurrentPassId(), upperFlag, /* lower flag */ Flags.NONE, + /* is displayed */ true); + addRaceLogEventAndPreventRecursion(raceLog, flagEvent); + startNewPass(raceStatusUpdateTime, raceLog); } - } - if (abortingFlagEvent != null && !isAbortedState(raceStatus) && raceStatusUpdateTime.after(abortingFlagEvent.getLogicalTimePoint())) { - logger.info("RaceLog considered race "+trackedRace.getName()+" as aborted ("+abortingFlagEvent+"), and the TracTrac race status "+raceStatus+ - " from "+raceStatusUpdateTime+" suggests it's not aborted. Starting a new pass."); - startNewPass(raceStatusUpdateTime, defaultRaceLog); - } else if (isAbortedState(raceStatus) && - (abortingFlagEvent == null || (!abortingFlagMatches(raceStatus, abortingFlagEvent.getUpperFlag()) && - raceStatusUpdateTime.after(abortingFlagEvent.getLogicalTimePoint())))) { - final Flags upperFlag = flagForRaceStatus.get(raceStatus); - logger.info("RaceLog considered race "+trackedRace.getName()+" as NOT aborted, and the TracTrac race status "+raceStatus+ - " from "+raceStatusUpdateTime+" suggests it's aborted. Adding abort flag event "+upperFlag+", starting a new pass."); - final RaceLogFlagEventImpl flagEvent = new RaceLogFlagEventImpl(raceStatusUpdateTime, raceLogEventAuthor, - defaultRaceLog.getCurrentPassId(), upperFlag, /* lower flag */ Flags.NONE, - /* is displayed */ true); - addRaceLogEventAndPreventRecursion(defaultRaceLog, flagEvent); - startNewPass(raceStatusUpdateTime, defaultRaceLog); } } @@ -370,20 +371,6 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler { return result; } - /** - * If no race log is attached to the {@code trackedRace}, {@code null} is returned. If exactly one race log - * is attached, it is returned. If multiple race logs are attached, the one with the most entries is returned. - */ - private RaceLog getDefaultRaceLog(TrackedRace trackedRace) { - final RaceLog result; - if (Util.isEmpty(trackedRace.getAttachedRaceLogs())) { - result = null; - } else { - result = Util.stream(trackedRace.getAttachedRaceLogs()).max((rl1, rl2)->Integer.compare(rl1.size(), rl2.size())).get(); - } - return result; - } - /** * If an official finish time or rank exists on the {@link IRaceCompetitor} but not in any of the {@link TrackedRace}'s * {@link RaceLog}-based results, or the corresponding TracTrac information is newer than the race log-based result, @@ -411,65 +398,63 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler { // there is an official result for the competitor on TracTrac's side // find out if we already have this information represented in the race log(s) and if not if the TracTrac information is newer: final Competitor competitor = domainFactory.resolveCompetitor(raceCompetitor.getCompetitor()); - Pair resultFromRaceLogAndItsCreationTimePoint = getRaceLogResultAndCreationTimePointForCompetitor(trackedRace, competitor); final RaceCompetitorStatusType competitorStatus = raceCompetitor.getStatus(); final MaxPointsReason officialMaxPointsReason = getMaxPointsReason(competitorStatus); final MillisecondsTimePoint officialResultTime = new MillisecondsTimePoint(timePointForStatusEvent); - if (resultFromRaceLogAndItsCreationTimePoint == null - || ((resultFromRaceLogAndItsCreationTimePoint.getA().getOneBasedRank() != officialRank - || ((resultFromRaceLogAndItsCreationTimePoint.getA().getFinishingTime() == null) ? 0 - : resultFromRaceLogAndItsCreationTimePoint.getA().getFinishingTime().asMillis()) != officialFinishingTime - || resultFromRaceLogAndItsCreationTimePoint.getA().getMaxPointsReason() != officialMaxPointsReason) - && resultFromRaceLogAndItsCreationTimePoint.getB().before(officialResultTime))) { - logger.info("Applying official competitor result because its time point "+officialResultTime - +" is newer than the latest result for that competitor from the race log "+ - (resultFromRaceLogAndItsCreationTimePoint==null?"null":resultFromRaceLogAndItsCreationTimePoint.getB())); - // We have received an update from TracTrac, rank or finishing time or penalty varies and is newer - // than the last thing we see in the race log (including we may not have anything in the race - // log for the results of that competitor at all yet). - // --> Write the TracTrac result to the race log, where 0 for the "official rank" maps to 0 of our - // one-based rank; and 0 as official finishing time maps to null as the official finishing time: - final CompetitorResult resultForRaceLog = new CompetitorResultImpl(competitor.getId(), competitor.getName(), - competitor.getShortName(), - /* boat name: */ ((competitor.hasBoat() ? ((CompetitorWithBoat) competitor).getBoat().getName() : competitor.getShortInfo())), - /* boatSailId */ ((competitor.hasBoat() ? ((CompetitorWithBoat) competitor).getBoat().getSailID() : competitor.getShortInfo())), - officialRank, officialMaxPointsReason, /* score null means let the scoring system calculate it */ null, - officialFinishingTime == 0 ? null : new MillisecondsTimePoint(officialFinishingTime), - "Official results from TracTrac connector", MergeState.OK); - final CompetitorResults resultsForRaceLog = new CompetitorResultsImpl(); - resultsForRaceLog.add(resultForRaceLog); - final RaceLog defaultRaceLog = getDefaultRaceLog(trackedRace); - final RaceLogFinishPositioningConfirmedEventImpl raceLogEvent = new RaceLogFinishPositioningConfirmedEventImpl( - officialResultTime, officialResultTime, raceLogEventAuthor, - UUID.randomUUID(), defaultRaceLog.getCurrentPassId(), resultsForRaceLog); - // Avoid an endless recursion in case this event triggers this reconciler's race log listener; see bug 5565 - addRaceLogEventAndPreventRecursion(defaultRaceLog, raceLogEvent); - logger.info("Added the following result to the race log of " + trackedRace.getRaceIdentifier() - + " for competitor " + raceCompetitor + ": " + resultForRaceLog); - } else { - logger.info("Did not produce a new competitor result for competitor " + raceCompetitor + " in race " - + trackedRace.getRaceIdentifier() + " because existing result " - + resultFromRaceLogAndItsCreationTimePoint + " was not older than status time " - + officialResultTime + " or was an equal result"); + for (final RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { + final Pair resultFromRaceLogAndItsCreationTimePoint = getRaceLogResultAndCreationTimePointForCompetitor(raceLog, competitor); + if (resultFromRaceLogAndItsCreationTimePoint == null + || ((resultFromRaceLogAndItsCreationTimePoint.getA().getOneBasedRank() != officialRank + || ((resultFromRaceLogAndItsCreationTimePoint.getA().getFinishingTime() == null) ? 0 + : resultFromRaceLogAndItsCreationTimePoint.getA().getFinishingTime().asMillis()) != officialFinishingTime + || resultFromRaceLogAndItsCreationTimePoint.getA().getMaxPointsReason() != officialMaxPointsReason) + && resultFromRaceLogAndItsCreationTimePoint.getB().before(officialResultTime))) { + logger.info("Applying official competitor result because its time point "+officialResultTime + +" is newer than the latest result for that competitor from the race log "+ + (resultFromRaceLogAndItsCreationTimePoint==null?"null":resultFromRaceLogAndItsCreationTimePoint.getB())); + // We have received an update from TracTrac, rank or finishing time or penalty varies and is newer + // than the last thing we see in the race log (including we may not have anything in the race + // log for the results of that competitor at all yet). + // --> Write the TracTrac result to the race log, where 0 for the "official rank" maps to 0 of our + // one-based rank; and 0 as official finishing time maps to null as the official finishing time: + final CompetitorResult resultForRaceLog = new CompetitorResultImpl(competitor.getId(), competitor.getName(), + competitor.getShortName(), + /* boat name: */ ((competitor.hasBoat() ? ((CompetitorWithBoat) competitor).getBoat().getName() : competitor.getShortInfo())), + /* boatSailId */ ((competitor.hasBoat() ? ((CompetitorWithBoat) competitor).getBoat().getSailID() : competitor.getShortInfo())), + officialRank, officialMaxPointsReason, /* score null means let the scoring system calculate it */ null, + officialFinishingTime == 0 ? null : new MillisecondsTimePoint(officialFinishingTime), + "Official results from TracTrac connector", MergeState.OK); + final CompetitorResults resultsForRaceLog = new CompetitorResultsImpl(); + resultsForRaceLog.add(resultForRaceLog); + final RaceLogFinishPositioningConfirmedEventImpl raceLogEvent = new RaceLogFinishPositioningConfirmedEventImpl( + officialResultTime, officialResultTime, raceLogEventAuthor, + UUID.randomUUID(), raceLog.getCurrentPassId(), resultsForRaceLog); + // Avoid an endless recursion in case this event triggers this reconciler's race log listener; see bug 5565 + addRaceLogEventAndPreventRecursion(raceLog, raceLogEvent); + logger.info("Added the following result to the race log of " + trackedRace.getRaceIdentifier() + + " for competitor " + raceCompetitor + ": " + resultForRaceLog); + } else { + logger.info("Did not produce a new competitor result for competitor " + raceCompetitor + " in race " + + trackedRace.getRaceIdentifier() + " because existing result " + + resultFromRaceLogAndItsCreationTimePoint + " was not older than status time " + + officialResultTime + " or was an equal result"); + } } } else { logger.info("Ignoring status change for competitor " + raceCompetitor + " in race "+trackedRace.getRaceIdentifier()+" because the status time was 0"); } } - protected Pair getRaceLogResultAndCreationTimePointForCompetitor(TrackedRace trackedRace, + protected Pair getRaceLogResultAndCreationTimePointForCompetitor(RaceLog raceLog, final Competitor competitor) { Pair resultFromRaceLogAndItsCreationTimePoint = null; - for (final RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { - final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog); - final CompetitorResultsAndTheirCreationTimePoints results = raceState.getConfirmedFinishPositioningList(); - if (results.getCompetitorResults() != null) { - final Optional result = StreamSupport.stream(results.getCompetitorResults().spliterator(), /* parallel */ false) - .filter(r -> Util.equalsWithNull(competitor.getId(), r.getCompetitorId())).findAny(); - if (result.isPresent()) { - resultFromRaceLogAndItsCreationTimePoint = new Pair<>(result.get(), results.getCreationTimePointOfResultForCompetitorWithId(result.get().getCompetitorId())); - break; - } + final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog); + final CompetitorResultsAndTheirCreationTimePoints results = raceState.getConfirmedFinishPositioningList(); + if (results.getCompetitorResults() != null) { + final Optional result = StreamSupport.stream(results.getCompetitorResults().spliterator(), /* parallel */ false) + .filter(r -> Util.equalsWithNull(competitor.getId(), r.getCompetitorId())).findAny(); + if (result.isPresent()) { + resultFromRaceLogAndItsCreationTimePoint = new Pair<>(result.get(), results.getCreationTimePointOfResultForCompetitorWithId(result.get().getCompetitorId())); } } return resultFromRaceLogAndItsCreationTimePoint;