From ca9550e93d8175cb1f0c03076362a1a68389052f Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 21 Apr 2016 17:26:53 +0200 Subject: [PATCH] implemented new attribute fleetsCanRunInParallel on Series domain object including storage, test, replication and admin UI --- .../dto/SeriesCreationParametersDTO.java | 123 +- .../impl/DomainObjectFactoryImpl.java | 8 +- .../domain/persistence/impl/FieldNames.java | 1 + .../impl/MongoObjectFactoryImpl.java | 1 + .../impl/CreateAndTrackWithRaceLogTest.java | 2 +- .../sap/sailing/domain/base/SeriesBase.java | 2 +- .../racegroup/impl/SeriesWithRowsImpl.java | 11 +- ...mpetitorProviderCacheInvalidationTest.java | 2 +- .../test/LeaderboardCourseChangeTest.java | 2 +- .../LeaderboardScoringAndRankingTest.java | 16 +- ...ecutionOrderProvdiderAttachDetachTest.java | 586 +-- ...ttaLogEventNotificationForwardingTest.java | 3 +- .../AbstractMockedRaceMarkPassingTest.java | 2 +- .../domain/base/RaceColumnListener.java | 4 +- .../com/sap/sailing/domain/base/Series.java | 2 + .../RaceColumnListenerWithDefaultAction.java | 219 +- .../sailing/domain/base/impl/RegattaImpl.java | 7 +- .../sailing/domain/base/impl/SeriesImpl.java | 30 +- .../impl/AbstractSimpleLeaderboardImpl.java | 3891 +++++++++-------- .../DelayedLeaderboardCorrectionsImpl.java | 4 + .../meta/MetaLeaderboardColumn.java | 5 + .../util/impl/RaceColumnListeners.java | 6 + .../ui/test/PingMarkViaRegattaLogTest.java | 106 +- .../adminconsole/CreateRegattaCallback.java | 2 +- .../adminconsole/RegattaDetailsComposite.java | 5 +- .../ui/adminconsole/RegattaListComposite.java | 2 +- .../gwt/ui/adminconsole/SeriesDescriptor.java | 144 +- .../gwt/ui/adminconsole/SeriesEditDialog.java | 9 +- .../sailing/gwt/ui/client/SailingService.java | 2 +- .../gwt/ui/client/SailingServiceAsync.java | 2 +- .../sailing/gwt/ui/client/StringMessages.java | 1 + .../gwt/ui/client/StringMessages.properties | 1 + .../ui/client/StringMessages_de.properties | 1 + .../ui/client/StringMessages_ru.properties | 1 + .../ui/client/StringMessages_zh.properties | 1 + .../ui/client/StringMessages_zh_CN.properties | 1 + .../gwt/ui/server/SailingServiceImpl.java | 10 +- .../sap/sailing/gwt/ui/shared/SeriesDTO.java | 22 +- ...tStoringAndRetrievingRaceLogInRegatta.java | 162 +- ...estStoringAndLoadingEventsAndRegattas.java | 28 +- .../impl/SeriesWithRowsDeserializer.java | 73 +- .../jaxrs/LeaderboardGroupResourceTest.java | 184 +- ...LeaderboardsResourceCheckinAndOutTest.java | 2 +- .../LeaderboardsResourcePingMarkTest.java | 210 +- .../test/jaxrs/LeaderboardsResourceTest.java | 532 +-- .../test/jaxrs/RegattasResourceTest.java | 156 +- .../gateway/impl/rc/RaceGroupFactory.java | 4 +- .../test/AbstractLogReplicationTest.java | 232 +- .../test/RegattaReplicationTest.java | 16 +- .../impl/MediaMasterDataExportTest.java | 380 +- .../server/test/MasterDataImportTest.java | 26 +- .../server/test/SearchServiceTest.java | 644 +-- .../server/impl/RaceLogReplicator.java | 4 + .../server/impl/RaceLogScoringReplicator.java | 4 + .../server/impl/RacingEventServiceImpl.java | 2 +- .../AddSpecificRegatta.java | 2 +- .../UpdateSeries.java | 183 +- .../structureimport/StructureImporter.java | 2 +- .../app/data/OfflineDataManager.java | 4 +- 59 files changed, 4109 insertions(+), 3978 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java index 94ba4e935b4..fbc9ac70b83 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java @@ -1,58 +1,65 @@ -package com.sap.sailing.domain.common.dto; - -import java.io.Serializable; -import java.util.List; - -public class SeriesCreationParametersDTO implements Serializable { - private static final long serialVersionUID = -3205172230707607515L; - - private List fleets; - - private boolean medal; - - private boolean startsWithZero; - - private boolean firstColumnIsNonDiscardableCarryForward; - - private int[] discardingThresholds; - - private boolean hasSplitFleetContiguousScoring; - - SeriesCreationParametersDTO() {} - - public SeriesCreationParametersDTO(List fleets, boolean medal, boolean startsWithZero, boolean firstColumnIsNonDiscardableCarryForward, - int[] discardingThresholds, boolean hasSplitFleetContiguousScoring) { - super(); - this.fleets = fleets; - this.medal = medal; - this.startsWithZero = startsWithZero; - this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; - this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; - this.discardingThresholds = discardingThresholds; - } - - public List getFleets() { - return fleets; - } - - public boolean isMedal() { - return medal; - } - - public boolean isStartsWithZero() { - return startsWithZero; - } - - public boolean hasSplitFleetContiguousScoring() { - return hasSplitFleetContiguousScoring; - } - - public int[] getDiscardingThresholds() { - return discardingThresholds; - } - - public boolean isFirstColumnIsNonDiscardableCarryForward() { - return firstColumnIsNonDiscardableCarryForward; - } - -} +package com.sap.sailing.domain.common.dto; + +import java.io.Serializable; +import java.util.List; + +public class SeriesCreationParametersDTO implements Serializable { + private static final long serialVersionUID = -3205172230707607515L; + + private List fleets; + + private boolean isMedal; + + private boolean isFleetsCanRunInParallel; + + private boolean isStartsWithZeroScore; + + private boolean firstColumnIsNonDiscardableCarryForward; + + private int[] discardingThresholds; + + private boolean hasSplitFleetContiguousScoring; + + SeriesCreationParametersDTO() {} + + public SeriesCreationParametersDTO(List fleets, boolean isMedal, boolean isFleetsCanRunInParallel, boolean isStartsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward, + int[] discardingThresholds, boolean hasSplitFleetContiguousScoring) { + super(); + this.fleets = fleets; + this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + this.isStartsWithZeroScore = isStartsWithZeroScore; + this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; + this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; + this.discardingThresholds = discardingThresholds; + } + + public List getFleets() { + return fleets; + } + + public boolean isMedal() { + return isMedal; + } + + public boolean isStartsWithZero() { + return isStartsWithZeroScore; + } + + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + public boolean hasSplitFleetContiguousScoring() { + return hasSplitFleetContiguousScoring; + } + + public int[] getDiscardingThresholds() { + return discardingThresholds; + } + + public boolean isFirstColumnIsNonDiscardableCarryForward() { + return firstColumnIsNonDiscardableCarryForward; + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index 9eb8f687b6b..bf7f91277a4 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -1188,6 +1188,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private Series loadSeries(DBObject dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) { String name = (String) dbSeries.get(FieldNames.SERIES_NAME.name()); boolean isMedal = (Boolean) dbSeries.get(FieldNames.SERIES_IS_MEDAL.name()); + // isFleetCanRunInParallel is a new field -> set to true when it does not exist in older db versions + boolean isFleetsCanRunInParallel = true; + Object isFleetCanRunInParallelObject = dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name()); + if(isFleetCanRunInParallelObject != null) { + isFleetsCanRunInParallel = (Boolean) dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name()); + } Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name()); Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name()); Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name()); @@ -1195,7 +1201,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { List fleets = loadFleets(dbFleets); BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name()); Iterable raceColumnNames = loadRaceColumnNames(dbRaceColumns); - Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry); + Series series = new SeriesImpl(name, isMedal, isFleetsCanRunInParallel, fleets, raceColumnNames, trackedRegattaRegistry); if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) { ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS); series.setResultDiscardingRule(resultDiscardingRule); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 1763fe75957..e7c2699cf1e 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -36,6 +36,7 @@ public enum FieldNames { REGATTA_USE_START_TIME_INFERENCE, SERIES_NAME, SERIES_IS_MEDAL, + SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL, SERIES_STARTS_WITH_ZERO_SCORE, SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING, SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD, diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 8377d398eff..37e3c3573d5 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -674,6 +674,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBObject dbSeries = new BasicDBObject(); dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName()); dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal()); + dbSeries.put(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name(), s.isFleetsCanRunInParallel()); dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring()); dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore()); dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward()); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index e70ac72f818..b5f231b00f3 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -76,7 +76,7 @@ public class CreateAndTrackWithRaceLogTest { gpsFixStore = service.getGPSFixStore(); service.getMongoObjectFactory().getDatabase().dropDatabase(); author = service.getServerAuthor(); - Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), Collections.emptySet(), + Series series = new SeriesImpl("series", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(fleet), Collections.emptySet(), service); regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /* startDate */null, /* endDate */null, UUID.randomUUID(), Collections. singletonList(series), false, diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java index 726eebefb64..6dbb0f7f1f8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java @@ -33,5 +33,5 @@ public interface SeriesBase extends Renamable { * made except that within a fleet the races are usually run in the order as defined by the {@link #getRaceColumns() * race columns}. */ - boolean isFleetsRunInParallel(); + boolean isFleetsCanRunInParallel(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java index fa5aa6c7126..9aaeacdee1b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java @@ -13,11 +13,13 @@ public class SeriesWithRowsImpl implements SeriesWithRows { private String name; private Iterable raceRows; private boolean isMedal; - - public SeriesWithRowsImpl(String name, boolean isMedal, Iterable raceRows) { + private boolean isFleetsCanRunInParallel; + + public SeriesWithRowsImpl(String name, boolean isMedal, boolean isFleetsCanRunInParallel, Iterable raceRows) { this.name = name; this.raceRows = raceRows; this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; } public String getName() { @@ -46,8 +48,7 @@ public class SeriesWithRowsImpl implements SeriesWithRows { } @Override - public boolean isFleetsRunInParallel() { - // TODO This is a default implementation so far, allowing for parallel fleet races to not restrict generality. This shall become a configurable series property. See bug 3532 - return true; + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java index b9a5c66198f..b47d2282365 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java @@ -78,7 +78,7 @@ public class CompetitorProviderCacheInvalidationTest extends AbstractLeaderboard null, /* endDate */null, /* trackedRegattaRegistry */null, new LowPoint(), UUID.randomUUID(), courseArea); regattaLeaderboard = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(new int[0])); - regatta.addSeries(new SeriesImpl("Test Series", /* isMedal */false, Arrays.asList(new FleetImpl("Yellow"), + regatta.addSeries(new SeriesImpl("Test Series", /* isMedal */false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new FleetImpl("Yellow"), new FleetImpl("Blue")), Arrays.asList("R1", "R2", "R3"), /* trackedRegattaRegistry */null)); for (int l = 0; l < NUMBER_OF_COMP_LISTS; l++) { compLists[l] = new ArrayList(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java index b43c350c0e5..94f362b2691 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java @@ -175,7 +175,7 @@ public class LeaderboardCourseChangeTest { Set fleets = new HashSet<>(); fleets.add(fleet); - Series series = new SeriesImpl("TestSeries", false, fleets, new HashSet(), trackedRegattaRegistry); + Series series = new SeriesImpl("TestSeries", false, true, fleets, new HashSet(), trackedRegattaRegistry); return series; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index ace5830c7fe..62ee7421832 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1709,7 +1709,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = 1; i <= numberOfQualifyingRaces; i++) { qualifyingRaceColumnNames.add("Q" + i); } - Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets, + Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, qualifyingRaceColumnNames, /* trackedRegattaRegistry */null); series.add(qualifyingSeries); } @@ -1725,7 +1725,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = 1; i <= numberOfFinalRaces; i++) { finalRaceColumnNames.add("F" + i); } - Series finalSeries = new SeriesImpl("Final", /* isMedal */false, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(finalSeries); } @@ -1735,7 +1735,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { medalFleets.add(new FleetImpl("Medal")); List medalRaceColumnNames = new ArrayList(); medalRaceColumnNames.add("M"); - Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(medalSeries); } @@ -1774,7 +1774,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } else { roundName = "Round "+roundNumber; } - Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, fleetsInRound, + Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, /* isFleetsCanRunInParallel */ true, fleetsInRound, raceColumnNameForRound, /* trackedRegattaRegistry */null); if (isFinalRound) { // last "Final" round; here, the fleets are contiguously scored @@ -1808,7 +1808,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = 1; i <= numberOfBeforeRaces; i++) { beforeRaceColumnNames.add("R" + i); } - Series qualifyingSeries = new SeriesImpl("Before", /* isMedal */false, beforeFleets, + Series qualifyingSeries = new SeriesImpl("Before", /* isMedal */false, /* isFleetsCanRunInParallel */ true, beforeFleets, beforeRaceColumnNames, /* trackedRegattaRegistry */null); series.add(qualifyingSeries); } @@ -1820,7 +1820,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } List qualificationRaceColumnNames = new ArrayList(); qualificationRaceColumnNames.add("Q"); - Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, qualificationFleets, qualificationRaceColumnNames, /* trackedRegattaRegistry */ null); + Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualificationFleets, qualificationRaceColumnNames, /* trackedRegattaRegistry */ null); // discard the one and only qualification race; it doesn't score qualificationSeries.setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1 })); series.add(qualificationSeries); @@ -1835,7 +1835,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } List finalRaceColumnNames = new ArrayList(); finalRaceColumnNames.add("F"); - Series finalSeries = new SeriesImpl("Final", /* isMedal */false, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); finalSeries.setSplitFleetContiguousScoring(true); series.add(finalSeries); } @@ -1847,7 +1847,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = numberOfBeforeRaces+1; i <= numberOfBeforeRaces+numberOfFinalRaces; i++) { finalRaceColumnNames.add("R" + i); } - Series finalSeries = new SeriesImpl("After", /* isMedal */false, afterFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("After", /* isMedal */false, /* isFleetsCanRunInParallel */ true, afterFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(finalSeries); } final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("Extreme40", /* typicallyStartsUpwind */ false); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java index 13784b461aa..fbb03acad0b 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java @@ -1,293 +1,293 @@ -package com.sap.sailing.domain.test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import org.junit.Test; - -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnInSeries; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.base.impl.TrackedRaces; -import com.sap.sailing.domain.common.TimingConstants; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.WindImpl; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; -import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -/** - * Covers the linking and unlinking between {@link RaceColumn}s and {@link TrackedRaces}s - * and tests whether {@link RaceExecutionOrderProvider}s get correctly attached and detached. Also tests - * late attaching and detaching of {@link RaceExecutionOrderProvider}s in case the {@link RaceExecutionOrderProvider} - * was null when {@link RaceColumn} was linked to {@link TrackedRace}. - * - * @author Alexander Ries (D062114) - * @author Axel Uhl (D043530) - * - */ -public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest { - private FlexibleLeaderboard flexibleLeaderboard; - private RaceColumnInSeries raceColumnInSeries; - private Fleet fleet; - private TrackedRaceImpl trackedRace; - private Regatta regatta; - private Series series; - - private final String REGATTA = "TestRegatta"; - private final String RACE = "TestRace"; - private final String FLEET = "TestFleet"; - private final String BOATCLASS = "TestClass"; - private final String SERIES = "TestSeries"; - private final String FLEXIBLELEADERBOARD = "TestFlexibleLeaderboard"; - private final String RACECOLUMN_SERIES = "TestSeriesRaceColumn"; - private final String RACECOLUMN_FLEXIBLELEADERBOARD = "TestFlexibleLeaderboardRaceColumn"; - - @Test - public void testRaceExecutionOrderProviderAttachDetachWithRaceColumn() { - trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), - MillisecondsTimePoint.now()); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); - assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); - flexibleLeaderboard.removeRaceColumn(RACECOLUMN_FLEXIBLELEADERBOARD); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - } - - @Test - public void testWindInRegularIntervalWithPreviousRaceStillTracking() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_MINUTE.times(5)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - Wind wind = new WindImpl(new DegreePosition(12, 13), startOfSecondRace.plus(Duration.ONE_MINUTE), new KnotSpeedWithBearingImpl( - /* speedInKnots */18, new DegreeBearingImpl(185))); - assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking still open and takes the fix - assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval - } - - @Test - public void testWindInExtendedLeadIntervalWithPreviousRaceStillTracking() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - // the wind fix is after the grace period of the first race's end, so won't be accepted by it, but within the extended - // time range before the second race, so shall be accepted by it. - Wind wind = new WindImpl(new DegreePosition(12, 13), - endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking closed one second after it started and doesn't accept the fix - assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval - } - - @Test - public void testWindInExtendedLeadIntervalWithNoPreviousRace() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); - // the wind fix is within the extended time range before the second race, and there is no previous race, so shall be accepted by it - Wind wind = new WindImpl(new DegreePosition(12, 13), - endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); - } - - @Test - public void testWindInExtendedLeadIntervalWithPreviousRaceLongAgo() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.times(2)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - // the wind fix is outside the extended time range before the second race, and the previous race doesn't accept it; shall not be accepted - Wind wind = new WindImpl(new DegreePosition(12, 13), - endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period - assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); - } - - @Test - public void testWindInExtendedLeadIntervalButStillRecordedByPreviousRace() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - // the wind fix is inside the extended time range before the second race, but the previous race still accepts it; shall not be accepted - Wind wind = new WindImpl(new DegreePosition(12, 13), - startOfFirstRace.plus(Duration.ONE_SECOND), // fix is recorded by previous race - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period - assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); - } - - @Test - public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumnInSeries() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */true); - raceColumnInSeries.setTrackedRace(fleet, trackedRace); - assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); - raceColumnInSeries.releaseTrackedRace(fleet); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - } - - @Test - public void testRaceExecutionOrderProviderAttachDetachWhenSeriesRegattaIsSetAndRemovedAfterTrackedRaceHaveBeenSetToRaceColumns() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); - raceColumnInSeries.setTrackedRace(fleet, trackedRace); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - series.setRegatta(regatta); - assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); - series.setRegatta(null); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - } - - @Test - public void testThatTrackedRaceReceivesRaceLogWhenSeriesIsLinkedToRegatta() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); - raceColumnInSeries.setTrackedRace(fleet, trackedRace); - assertNull(raceColumnInSeries.getRaceLog(fleet)); - regatta.addSeries(series); - final RaceLog raceLog = raceColumnInSeries.getRaceLog(fleet); - assertNotNull(raceLog); - assertNotNull(trackedRace.getRaceLog(raceLog.getId())); - } - - private void createTestSetupWithRegattaAndSeries(boolean linkSeriesToRegatta) { - trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), - MillisecondsTimePoint.now()); - fleet = new FleetImpl(FLEET); - Set fleets = new HashSet<>(); - fleets.add(fleet); - series = new SeriesImpl(SERIES, false, fleets, new HashSet(), null); - Set seriesSet = new HashSet<>(); - if (linkSeriesToRegatta) { - seriesSet.add(series); - } - BoatClass boatClass = new BoatClassImpl(BOATCLASS, true); - raceColumnInSeries = series.addRaceColumn(RACECOLUMN_SERIES, null); - ScoringScheme scoringScheme = new LowPoint(); - regatta = new RegattaImpl(RegattaImpl.getDefaultName(REGATTA, boatClass.getName()), boatClass, - /* startDate */null, /* endDate */null, seriesSet, false, scoringScheme, UUID.randomUUID(), null, - OneDesignRankingMetric::new); - } - - /** - * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" - * definition's transitive closure may become cyclic, and a race may be considered its own - * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} - * implementation which traverses a race's predecessors recursively. - */ - @Test - public void testCyclicPreviousRacesSequence() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); - final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r3 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r4 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); - final TimePoint startOfSecondRace = endOfFirstRace.plus(Duration.ONE_MINUTE); - final TimePoint endOfSecondRace = startOfSecondRace.plus(Duration.ONE_MINUTE); - final TimePoint startOfThirdRace = endOfSecondRace.plus(Duration.ONE_MINUTE); - final TimePoint endOfThirdRace = startOfThirdRace.plus(Duration.ONE_MINUTE); - final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); - final TrackedRace tr2 = createTrackedRace("SecondRace", startOfSecondRace, endOfSecondRace); - final TrackedRace tr3 = createTrackedRace("ThirdRace", startOfThirdRace, endOfThirdRace); - r1.setTrackedRace(fleet, tr3); - r2.setTrackedRace(fleet, tr1); - r3.setTrackedRace(fleet, tr2); - r4.setTrackedRace(fleet, tr3); // produce a cycle because now transitively tr1 has itself as a predecessor - assertTrue(tr3.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), - startOfThirdRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); - } - - /** - * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" - * definition's transitive closure may become cyclic, and a race may be considered its own - * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} - * implementation which traverses a race's predecessors recursively. - */ - @Test - public void testCyclicPreviousRacesSequenceUsingSingleRace() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); - final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); - final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); - r1.setTrackedRace(fleet, tr1); - r2.setTrackedRace(fleet, tr1); - assertTrue(tr1.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), - startOfFirstRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); - } - - private DynamicTrackedRace createTrackedRace(final String name, final TimePoint startOfRace, final TimePoint endOfRace) { - DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections. emptyList(), startOfRace); - trackedRace.setStartOfTrackingReceived(startOfRace); - trackedRace.setEndOfTrackingReceived(endOfRace); - return trackedRace; - } -} +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.TrackedRaces; +import com.sap.sailing.domain.common.TimingConstants; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.WindImpl; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * Covers the linking and unlinking between {@link RaceColumn}s and {@link TrackedRaces}s + * and tests whether {@link RaceExecutionOrderProvider}s get correctly attached and detached. Also tests + * late attaching and detaching of {@link RaceExecutionOrderProvider}s in case the {@link RaceExecutionOrderProvider} + * was null when {@link RaceColumn} was linked to {@link TrackedRace}. + * + * @author Alexander Ries (D062114) + * @author Axel Uhl (D043530) + * + */ +public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest { + private FlexibleLeaderboard flexibleLeaderboard; + private RaceColumnInSeries raceColumnInSeries; + private Fleet fleet; + private TrackedRaceImpl trackedRace; + private Regatta regatta; + private Series series; + + private final String REGATTA = "TestRegatta"; + private final String RACE = "TestRace"; + private final String FLEET = "TestFleet"; + private final String BOATCLASS = "TestClass"; + private final String SERIES = "TestSeries"; + private final String FLEXIBLELEADERBOARD = "TestFlexibleLeaderboard"; + private final String RACECOLUMN_SERIES = "TestSeriesRaceColumn"; + private final String RACECOLUMN_FLEXIBLELEADERBOARD = "TestFlexibleLeaderboardRaceColumn"; + + @Test + public void testRaceExecutionOrderProviderAttachDetachWithRaceColumn() { + trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), + MillisecondsTimePoint.now()); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); + assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); + flexibleLeaderboard.removeRaceColumn(RACECOLUMN_FLEXIBLELEADERBOARD); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + } + + @Test + public void testWindInRegularIntervalWithPreviousRaceStillTracking() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_MINUTE.times(5)); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + Wind wind = new WindImpl(new DegreePosition(12, 13), startOfSecondRace.plus(Duration.ONE_MINUTE), new KnotSpeedWithBearingImpl( + /* speedInKnots */18, new DegreeBearingImpl(185))); + assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking still open and takes the fix + assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval + } + + @Test + public void testWindInExtendedLeadIntervalWithPreviousRaceStillTracking() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + // the wind fix is after the grace period of the first race's end, so won't be accepted by it, but within the extended + // time range before the second race, so shall be accepted by it. + Wind wind = new WindImpl(new DegreePosition(12, 13), + endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking closed one second after it started and doesn't accept the fix + assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval + } + + @Test + public void testWindInExtendedLeadIntervalWithNoPreviousRace() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); + // the wind fix is within the extended time range before the second race, and there is no previous race, so shall be accepted by it + Wind wind = new WindImpl(new DegreePosition(12, 13), + endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); + } + + @Test + public void testWindInExtendedLeadIntervalWithPreviousRaceLongAgo() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.times(2)); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + // the wind fix is outside the extended time range before the second race, and the previous race doesn't accept it; shall not be accepted + Wind wind = new WindImpl(new DegreePosition(12, 13), + endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period + assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); + } + + @Test + public void testWindInExtendedLeadIntervalButStillRecordedByPreviousRace() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + // the wind fix is inside the extended time range before the second race, but the previous race still accepts it; shall not be accepted + Wind wind = new WindImpl(new DegreePosition(12, 13), + startOfFirstRace.plus(Duration.ONE_SECOND), // fix is recorded by previous race + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period + assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); + } + + @Test + public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumnInSeries() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */true); + raceColumnInSeries.setTrackedRace(fleet, trackedRace); + assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); + raceColumnInSeries.releaseTrackedRace(fleet); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + } + + @Test + public void testRaceExecutionOrderProviderAttachDetachWhenSeriesRegattaIsSetAndRemovedAfterTrackedRaceHaveBeenSetToRaceColumns() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); + raceColumnInSeries.setTrackedRace(fleet, trackedRace); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + series.setRegatta(regatta); + assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); + series.setRegatta(null); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + } + + @Test + public void testThatTrackedRaceReceivesRaceLogWhenSeriesIsLinkedToRegatta() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); + raceColumnInSeries.setTrackedRace(fleet, trackedRace); + assertNull(raceColumnInSeries.getRaceLog(fleet)); + regatta.addSeries(series); + final RaceLog raceLog = raceColumnInSeries.getRaceLog(fleet); + assertNotNull(raceLog); + assertNotNull(trackedRace.getRaceLog(raceLog.getId())); + } + + private void createTestSetupWithRegattaAndSeries(boolean linkSeriesToRegatta) { + trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), + MillisecondsTimePoint.now()); + fleet = new FleetImpl(FLEET); + Set fleets = new HashSet<>(); + fleets.add(fleet); + series = new SeriesImpl(SERIES, false, /* isFleetsCanRunInParallel */ true, fleets, new HashSet(), null); + Set seriesSet = new HashSet<>(); + if (linkSeriesToRegatta) { + seriesSet.add(series); + } + BoatClass boatClass = new BoatClassImpl(BOATCLASS, true); + raceColumnInSeries = series.addRaceColumn(RACECOLUMN_SERIES, null); + ScoringScheme scoringScheme = new LowPoint(); + regatta = new RegattaImpl(RegattaImpl.getDefaultName(REGATTA, boatClass.getName()), boatClass, + /* startDate */null, /* endDate */null, seriesSet, false, scoringScheme, UUID.randomUUID(), null, + OneDesignRankingMetric::new); + } + + /** + * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" + * definition's transitive closure may become cyclic, and a race may be considered its own + * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} + * implementation which traverses a race's predecessors recursively. + */ + @Test + public void testCyclicPreviousRacesSequence() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); + final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r3 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r4 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); + final TimePoint startOfSecondRace = endOfFirstRace.plus(Duration.ONE_MINUTE); + final TimePoint endOfSecondRace = startOfSecondRace.plus(Duration.ONE_MINUTE); + final TimePoint startOfThirdRace = endOfSecondRace.plus(Duration.ONE_MINUTE); + final TimePoint endOfThirdRace = startOfThirdRace.plus(Duration.ONE_MINUTE); + final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); + final TrackedRace tr2 = createTrackedRace("SecondRace", startOfSecondRace, endOfSecondRace); + final TrackedRace tr3 = createTrackedRace("ThirdRace", startOfThirdRace, endOfThirdRace); + r1.setTrackedRace(fleet, tr3); + r2.setTrackedRace(fleet, tr1); + r3.setTrackedRace(fleet, tr2); + r4.setTrackedRace(fleet, tr3); // produce a cycle because now transitively tr1 has itself as a predecessor + assertTrue(tr3.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), + startOfThirdRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); + } + + /** + * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" + * definition's transitive closure may become cyclic, and a race may be considered its own + * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} + * implementation which traverses a race's predecessors recursively. + */ + @Test + public void testCyclicPreviousRacesSequenceUsingSingleRace() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); + final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); + final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); + r1.setTrackedRace(fleet, tr1); + r2.setTrackedRace(fleet, tr1); + assertTrue(tr1.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), + startOfFirstRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); + } + + private DynamicTrackedRace createTrackedRace(final String name, final TimePoint startOfRace, final TimePoint endOfRace) { + DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections. emptyList(), startOfRace); + trackedRace.setStartOfTrackingReceived(startOfRace); + trackedRace.setEndOfTrackingReceived(endOfRace); + return trackedRace; + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java index 31d96c7cb22..6555282c269 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java @@ -178,7 +178,8 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa } private Regatta createRegatta() { - Series series = new SeriesImpl("Test Series", /* isMedal */ false, /* fleets */ Collections.singleton(new FleetImpl("Default")), + Series series = new SeriesImpl("Test Series", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, + /* fleets */ Collections.singleton(new FleetImpl("Default")), Collections.singleton("R1"), /* trackedRegattaRegistry */ null); Regatta regatta = new RegattaImpl("test", null, null, null, Collections.singleton(series), false, new LowPoint(), "test", null, OneDesignRankingMetric::new); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java index 1f6023f282d..fedbd2f437a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java @@ -68,7 +68,7 @@ public class AbstractMockedRaceMarkPassingTest { Waypoint w5 = new WaypointImpl(cp, PassingInstruction.Line); waypoints = Arrays.asList(w1, w2, w3, w4, w5); Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, - /*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")), + /*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, /* isFleetsCanRunInParallel */ true, Arrays.asList(new FleetImpl("fleet")), new ArrayList(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5)), OneDesignRankingMetric::new); Course course = new CourseImpl("course", waypoints); RaceDefinition raceDef = new RaceDefinitionImpl("Performance Race", course, boatClass, Arrays.asList(ron, tom, ben)); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java index d096c29fcb4..ac4bea25d40 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java @@ -14,7 +14,9 @@ public interface RaceColumnListener extends Serializable { void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace); void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace); - + + void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel); + void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore); void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java index 5f61acb1989..02bf40878bc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java @@ -41,6 +41,8 @@ public interface Series extends SeriesBase { void setIsMedal(boolean isMedal); + void setIsFleetsCanRunInParallel(boolean isFleetsCanRunInParallel); + Fleet getFleetByName(String fleetName); /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java index 6c154e3c3cd..2454b024231 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java @@ -1,107 +1,112 @@ -package com.sap.sailing.domain.base.impl; - -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.tracking.TrackedRace; - -/** - * Delegates all listener operations to a default action which is implemented here to do nothing. This way, subclasses - * can easily provide a default action to be executed for all callback methods except for maybe a few which then need to - * be explicitly overridden. If not most methods need to perform the same action then instead of implementing a default - * action subclasses should rather override the {@link RaceColumnListener} operations individually. - * - * @author Alexander Ries (D062114) - * - */ -public interface RaceColumnListenerWithDefaultAction extends RaceColumnListener { - /** - * This implementation of the default action to which all other operations delegate does nothing. Subclasses can - * choose to override this method to provide this as the default behavior for all other methods and then still - * override individual {@link RaceColumnListener} methods, or they can leave the default action empty and just - * override {@link RaceColumnListener} methods. - */ - void defaultAction(); - - @Override - default void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - defaultAction(); - } - - @Override - default void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - defaultAction(); - } - - @Override - default void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { - defaultAction(); - } - - @Override - default void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { - defaultAction(); - } - - @Override - default void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, - boolean firstColumnIsNonDiscardableCarryForward) { - defaultAction(); - } - - @Override - default void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { - defaultAction(); - } - - @Override - default void raceColumnAddedToContainer(RaceColumn raceColumn) { - defaultAction(); - } - - @Override - default void raceColumnRemovedFromContainer(RaceColumn raceColumn) { - defaultAction(); - } - - @Override - default void raceColumnMoved(RaceColumn raceColumn, int newIndex) { - defaultAction(); - } - - @Override - default void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { - defaultAction(); - } - - @Override - default void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { - defaultAction(); - } - - @Override - default void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, - ResultDiscardingRule newDiscardingRule) { - defaultAction(); - } - - @Override - default void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { - defaultAction(); - } - - @Override - default void regattaLogEventAdded(RegattaLogEvent event) { - defaultAction(); - } - - @Override - default boolean isTransient() { - return false; - } -} +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.tracking.TrackedRace; + +/** + * Delegates all listener operations to a default action which is implemented here to do nothing. This way, subclasses + * can easily provide a default action to be executed for all callback methods except for maybe a few which then need to + * be explicitly overridden. If not most methods need to perform the same action then instead of implementing a default + * action subclasses should rather override the {@link RaceColumnListener} operations individually. + * + * @author Alexander Ries (D062114) + * + */ +public interface RaceColumnListenerWithDefaultAction extends RaceColumnListener { + /** + * This implementation of the default action to which all other operations delegate does nothing. Subclasses can + * choose to override this method to provide this as the default behavior for all other methods and then still + * override individual {@link RaceColumnListener} methods, or they can leave the default action empty and just + * override {@link RaceColumnListener} methods. + */ + void defaultAction(); + + @Override + default void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + defaultAction(); + } + + @Override + default void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + defaultAction(); + } + + @Override + default void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { + defaultAction(); + } + + @Override + default void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + defaultAction(); + } + + @Override + default void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { + defaultAction(); + } + + @Override + default void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, + boolean firstColumnIsNonDiscardableCarryForward) { + defaultAction(); + } + + @Override + default void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { + defaultAction(); + } + + @Override + default void raceColumnAddedToContainer(RaceColumn raceColumn) { + defaultAction(); + } + + @Override + default void raceColumnRemovedFromContainer(RaceColumn raceColumn) { + defaultAction(); + } + + @Override + default void raceColumnMoved(RaceColumn raceColumn, int newIndex) { + defaultAction(); + } + + @Override + default void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { + defaultAction(); + } + + @Override + default void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { + defaultAction(); + } + + @Override + default void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, + ResultDiscardingRule newDiscardingRule) { + defaultAction(); + } + + @Override + default void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { + defaultAction(); + } + + @Override + default void regattaLogEventAdded(RegattaLogEvent event) { + defaultAction(); + } + + @Override + default boolean isTransient() { + return false; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 6c39ac72caa..a665090fd58 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -182,7 +182,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene RankingMetricConstructor rankingMetricConstructor) { this(raceLogStore, regattaLogStore, name, boatClass, startDate, endDate, Collections .singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, - /* isMedal */false, Collections + /* isMedal */false, /* isFleetsCanRunInParallel */ true, Collections .singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList(), trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea, /* useStartTimeInference */true, rankingMetricConstructor); @@ -429,6 +429,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene raceColumnListeners.notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + raceColumnListeners.notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { raceColumnListeners.notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java index 64b283d2476..80cd55b1260 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java @@ -36,6 +36,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe private final List fleetsInAscendingOrder; private final List raceColumns; private boolean isMedal; + private boolean isFleetsCanRunInParallel; private Regatta regatta; private final RaceColumnListeners raceColumnListeners; private ThresholdBasedResultDiscardingRule resultDiscardingRule; @@ -71,7 +72,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe * this column's series {@link Regatta}, respectively. If null, the re-association won't be * carried out. */ - public SeriesImpl(String name, boolean isMedal, Iterable fleets, Iterable raceColumnNames, + public SeriesImpl(String name, boolean isMedal, boolean isFleetsCanRunInParallel, Iterable fleets, Iterable raceColumnNames, TrackedRegattaRegistry trackedRegattaRegistry) { super(name); if (fleets == null || Util.isEmpty(fleets)) { @@ -86,6 +87,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe Collections.sort(fleetsInAscendingOrder); this.raceColumns = new ArrayList(); this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; this.raceColumnListeners = new RaceColumnListeners(); for (String raceColumnName : raceColumnNames) { addRaceColumn(raceColumnName, trackedRegattaRegistry); @@ -284,6 +286,22 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe } } + @Override + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + @Override + public void setIsFleetsCanRunInParallel(boolean isFleetsCanRunInParallel) { + boolean oldIsFleetsCanRunInParallel = this.isFleetsCanRunInParallel; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + if (oldIsFleetsCanRunInParallel != isFleetsCanRunInParallel) { + for (RaceColumn raceColumn : getRaceColumns()) { + raceColumnListeners.notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, isFleetsCanRunInParallel); + } + } + } + @Override public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { raceColumnListeners.notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); @@ -299,6 +317,10 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe raceColumnListeners.notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + raceColumnListeners.notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { raceColumnListeners.notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); @@ -434,10 +456,4 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe public boolean hasSplitFleetContiguousScoring() { return hasSplitFleetContiguousScoring; } - - @Override - public boolean isFleetsRunInParallel() { - // TODO This is a default implementation so far, allowing for parallel fleet races to not restrict generality. This shall become a configurable series property. See bug 3532 - return true; - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index c55d25d5ba6..76a200f12f6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -1,1943 +1,1948 @@ -package com.sap.sailing.domain.leaderboard.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Leg; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnInSeries; -import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LeaderboardType; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.ManeuverType; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.NoWindError; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.dto.BasicRaceDTO; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; -import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; -import com.sap.sailing.domain.common.dto.LegEntryDTO; -import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result; -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; -import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; -import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; -import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.Maneuver; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.MarkPassingManeuver; -import com.sap.sailing.domain.tracking.RaceChangeListener; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; -import com.sap.sailing.domain.tracking.WindPositionMode; -import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; -import com.sap.sailing.util.impl.RaceColumnListeners; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; -import com.sap.sse.util.impl.ThreadFactoryWithPriority; - -/** - * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events - * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes - * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful - * candidates. - * - * @author Axel Uhl (d043530) - * - */ -public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { - private static final long serialVersionUID = 330156778603279333L; - - private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); - - static final Double DOUBLE_0 = new Double(0); - - private final SettableScoreCorrection scoreCorrection; - - private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; - - /** - * The optional display name mappings for competitors. This allows a user to override the tracking-provided - * competitor names for display in a leaderboard. - */ - private final Map displayNames; - - /** the display name of the leaderboard */ - private String displayName; - - /** - * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard - * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points - * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}. - */ - private final Map carriedPoints; - - private final RaceColumnListeners raceColumnListeners; - - /** - * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access - * is controlled by the {@link #suppressedCompetitorsLock} lock. - */ - private final Set suppressedCompetitors; - private final NamedReentrantReadWriteLock suppressedCompetitorsLock; - - private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; - - /** - * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} - * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't - * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, - * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). - */ - private transient Executor raceDetailsExecutor; - - /** - * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. - */ - private transient Set cacheInvalidationListeners; - - private transient ThreadPoolExecutor executor; - - private transient LiveLeaderboardUpdater liveLeaderboardUpdater; - - private transient LeaderboardDTOCache leaderboardDTOCache; - - /** - * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. - * - * @author Axel Uhl (d043530) - * - */ - class EntryImpl implements Entry { - private final Callable trackedRankProvider; - private final Double netPoints; - private final Callable netPointsUncorrectedProvider; - private final boolean isNetPointsCorrected; - private final Double totalPoints; - private final MaxPointsReason maxPointsReason; - private final boolean discarded; - private final Fleet fleet; - - private EntryImpl(Callable trackedRankProvider, Double netPoints, - Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, - Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { - super(); - this.trackedRankProvider = trackedRankProvider; - this.netPoints = netPoints; - this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; - this.isNetPointsCorrected = isNetPointsCorrected; - this.totalPoints = totalPoints; - this.maxPointsReason = maxPointsReason; - this.discarded = discarded; - this.fleet = fleet; - } - @Override - public int getTrackedRank() { - try { - return trackedRankProvider.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - @Override - public Double getNetPoints() { - return netPoints; - } - @Override - public boolean isNetPointsCorrected() { - return isNetPointsCorrected; - } - @Override - public Double getTotalPoints() { - return totalPoints; - } - @Override - public MaxPointsReason getMaxPointsReason() { - return maxPointsReason; - } - @Override - public boolean isDiscarded() { - return discarded; - } - @Override - public Fleet getFleet() { - return fleet; - } - @Override - public Double getNetPointsUncorrected() { - try { - return netPointsUncorrectedProvider.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed - * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have - * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list - * would be cached, but that is again difficult because we would have to monitor all changes in all dependent - * leaderboards and columns and tracked races properly. - *

- * - * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} - * competitors list is to determine their number which in turn is only required for high-point scoring systems and - * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes - * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that - * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the - * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be - * constructed each time the number shall be computed. - * - * @author Axel Uhl (D043530) - * - */ - public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { - private int numberOfCompetitors = -1; - - @Override - public int getNumberOfCompetitorsInLeaderboard() { - if (numberOfCompetitors == -1) { - numberOfCompetitors = Util.size(getCompetitors()); - } - return numberOfCompetitors; - } - } - - /** - * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked - * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, - * calculations may start or resume, making it necessary to clear the cache. - * - * @author Axel Uhl (D043530) - * - */ - private class CacheInvalidationListener extends AbstractRaceChangeListener { - private final TrackedRace trackedRace; - private final Competitor competitor; - - public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { - this.trackedRace = trackedRace; - this.competitor = competitor; - } - - public TrackedRace getTrackedRace() { - return trackedRace; - } - - public void removeFromTrackedRace() { - trackedRace.removeListener(this); - } - - private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { - synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); - removeFromTrackedRace(); - } - } - - @Override - protected void defaultAction() { - invalidateCacheAndRemoveThisListenerFromTrackedRace(); - } - } - - private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { - @Override - public String generateRandomUUID() { - return UUID.randomUUID().toString(); - } - } - - public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { - this.carriedPoints = new HashMap(); - this.scoreCorrection = createScoreCorrection(); - this.displayNames = new HashMap(); - this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; - this.suppressedCompetitors = new HashSet(); - this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); - this.raceColumnListeners = new RaceColumnListeners(); - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); - initTransientFields(); - } - - /** - * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override - * this method to create a more specific type of score correction. This implementation produces an object of type - * {@link ScoreCorrectionImpl}. - */ - protected SettableScoreCorrection createScoreCorrection() { - return new ScoreCorrectionImpl(this); - } - - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - initTransientFields(); - } - - private void initTransientFields() { - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); - this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - this.cacheInvalidationListeners = new HashSet(); - // When many updates are triggered in a short period of time by a single thread, ensure that the single thread - // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one - // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. - final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); - executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, - /* maximumPoolSize */ THREAD_POOL_SIZE, - /* keepAliveTime */ 60, TimeUnit.SECONDS, - /* workQueue */ new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); - } - - @Override - public void destroy() { - for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { - cacheInvalidationListener.removeFromTrackedRace(); - } - } - - @Override - public SettableScoreCorrection getScoreCorrection() { - return scoreCorrection; - } - - @Override - public String getDisplayName(Competitor competitor) { - return displayNames.get(competitor); - } - - @Override - public String getDisplayName() { - return displayName; - } - - @Override - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - @Override - public ResultDiscardingRule getResultDiscardingRule() { - return crossLeaderboardResultDiscardingRule; - } - - @Override - public void setCarriedPoints(Competitor competitor, double carriedPoints) { - Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); - if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); - } - } - - @Override - public double getCarriedPoints(Competitor competitor) { - Double result = carriedPoints.get(competitor); - return result == null ? 0 : result; - } - - @Override - public Map getCompetitorsForWhichThereAreCarriedPoints() { - return Collections.unmodifiableMap(carriedPoints); - } - - @Override - public void unsetCarriedPoints(Competitor competitor) { - Double oldCarriedPoints = carriedPoints.remove(competitor); - if (oldCarriedPoints != null) { - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); - } - } - - @Override - public boolean hasCarriedPoints() { - return !carriedPoints.isEmpty(); - } - - @Override - public boolean hasCarriedPoints(Competitor competitor) { - return carriedPoints.containsKey(competitor); - } - - @Override - public void setDisplayName(Competitor competitor, String displayName) { - String oldDisplayName = displayNames.get(competitor); - displayNames.put(competitor, displayName); - if (!Util.equalsWithNull(oldDisplayName, displayName)) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); - } - } - - @Override - public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { - ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); - this.crossLeaderboardResultDiscardingRule = discardingRule; - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); - } - - @Override - public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { - return getScoreCorrection().getCorrectedScore( - new Callable() { - public Integer call() throws NoWindException { - return getTrackedRank(competitor, raceColumn, timePoint); - } - }, competitor, - raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); - } - - @Override - public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); - } - - @Override - public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); - } - - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); - } - - /** - * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). - * - * @param discardedRaceColumns - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. - */ - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, - final Set discardedRaceColumns) { - return !raceColumn.isMedalRace() - && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() - && discardedRaceColumns.contains(raceColumn); - } - - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); - } - - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - } - - /** - * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). - * - * @param discardedRaceColumns - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. - */ - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { - Double result; - if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { - result = 0.0; - } else { - final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints == null) { - result = null; - } else { - result = raceColumn.getFactor() * netPoints; - } - } - return result; - } - - @Override - public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { - return getTotalPoints(competitor, getRaceColumns(), timePoint); - } - - @Override - public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, - TimePoint timePoint) { - // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on - boolean needToResetScoreUponNextNonEmptyEntry = false; - double result = getCarriedPoints(competitor); - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - for (RaceColumn raceColumn : raceColumnsToConsider) { - if (raceColumn.isStartsWithZeroScore()) { - needToResetScoreUponNextNonEmptyEntry = true; - } - if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { - final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - if (totalPoints != null) { - if (needToResetScoreUponNextNonEmptyEntry) { - result = 0; - needToResetScoreUponNextNonEmptyEntry = false; - } - result += totalPoints; - } - } - } - return result; - } - - /** - * All competitors with non-null net points are added to the result which is then sorted by net points in ascending - * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. - */ - @Override - public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - final Map> netPointsAndFleet = new HashMap>(); - for (Competitor competitor : getCompetitors()) { - Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints != null) { - netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); - } - } - List result = new ArrayList(netPointsAndFleet.keySet()); - Collections.sort(result, new Comparator() { - @Override - public int compare(Competitor o1, Competitor o2) { - int comparisonResult; - if (o1 == o2) { - comparisonResult = 0; - } else { - if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { - // only check fleets if there are more than one and the column is not to be contiguously scored even in case - // of split fleets - final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); - final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); - if (o1Fleet == null) { - if (o2Fleet == null) { - comparisonResult = 0; - } else { - comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 - // has - } - } else { - if (o2Fleet == null) { - comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't - } else { - comparisonResult = o1Fleet.compareTo(o2Fleet); - } - } - } else { - // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets - // the scoring runs contiguously from top to bottom - comparisonResult = 0; - } - if (comparisonResult == 0) { - comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); - } - } - return comparisonResult; - } - }); - return result; - } - - @Override - public List getCompetitorsFromBestToWorst(TimePoint timePoint) { - return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); - } - - private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { - List result = new ArrayList(); - for (Competitor competitor : getCompetitors()) { - result.add(competitor); - } - Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); - return result; - } - - @Override - public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { - List competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); - return competitorsFromBestToWorst.indexOf(competitor) + 1; - } - - protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { - return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); - } - - @Override - public RaceColumn getRaceColumnByName(String columnName) { - RaceColumn result = null; - for (RaceColumn r : getRaceColumns()) { - if (r.getName().equals(columnName)) { - result = r; - break; - } - } - return result; - } - - @Override - public Competitor getCompetitorByName(String competitorName) { - for (Competitor competitor : getAllCompetitors()) { - if (competitor.getName().equals(competitorName)) { - return competitor; - } - } - return null; - } - - @Override - public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - TrackedRace trackedRaceForCompetitorInColumn; - return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && - (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || - ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && - trackedRaceForCompetitorInColumn.hasStarted(timePoint) && - trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); - } - - @Override - public void addRaceColumnListener(RaceColumnListener listener) { - getRaceColumnListeners().addRaceColumnListener(listener); - } - - @Override - public void removeRaceColumnListener(RaceColumnListener listener) { - getRaceColumnListeners().removeRaceColumnListener(listener); - } - - @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - return getEntry(competitor, race, timePoint, discardedRaceColumns); - } - - @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, - Set discardedRaceColumns) throws NoWindException { - Callable trackedRankProvider = new Callable() { - public Integer call() throws NoWindException { - return getTrackedRank(competitor, race, timePoint); - } - }; - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); - boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); - final Double correctedScore = correctedResults.getCorrectedScore(); - return new EntryImpl(trackedRankProvider, correctedScore, new Callable() { - @Override - public Double call() { - return correctedResults.getUncorrectedScore(); - } - }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null - : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, - race.getFleetOfCompetitor(competitor)); - } - - @Override - public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { - Map> result = new LinkedHashMap<>(); - List raceColumnsToConsider = new ArrayList<>(); - for (RaceColumn raceColumn : getRaceColumns()) { - raceColumnsToConsider.add(raceColumn); - result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); - } - return result; - } - - @Override - public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) - throws NoWindException { - final Map> result = new LinkedHashMap<>(); - List raceColumnsToConsider = new ArrayList<>(); - Map>> futures = new HashMap<>(); - for (final RaceColumn raceColumn : getRaceColumns()) { - raceColumnsToConsider.add(raceColumn); - final Iterable finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); - futures.put(raceColumn, executor.submit(new Callable>() { - @Override - public Map call() { - Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); - for (Competitor competitor : getCompetitors()) { - totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); - } - synchronized (result) { - return totalPointsSumPerCompetitorInColumn; - } - } - })); - } - for (RaceColumn raceColumn : getRaceColumns()) { - try { - result.put(raceColumn, futures.get(raceColumn).get()); - } catch (InterruptedException | ExecutionException e) { - if (e.getCause() instanceof NoWindError) { - throw ((NoWindError) e.getCause()).getCause(); - } else { - throw new RuntimeException(e); // no caught exceptions occur in the futures executed - } - } - } - return result; - } - - @Override - public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { - Map, Entry> result = new HashMap, Entry>(); - Map> discardedRaces = new HashMap>(); - for (final RaceColumn raceColumn : getRaceColumns()) { - for (final Competitor competitor : getCompetitors()) { - Callable trackedRankProvider = new Callable() { - @Override - public Integer call() throws Exception { - return getTrackedRank(competitor, raceColumn, timePoint); - } - }; - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); - Set discardedRacesForCompetitor = discardedRaces.get(competitor); - if (discardedRacesForCompetitor == null) { - discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - discardedRaces.put(competitor, discardedRacesForCompetitor); - } - boolean discarded = discardedRacesForCompetitor.contains(raceColumn); - final Double correctedScore = correctedResults.getCorrectedScore(); - Entry entry = new EntryImpl(trackedRankProvider, correctedScore, - new Callable() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, - correctedResults.isCorrected(), - discarded ? DOUBLE_0 : (correctedScore==null?null: - Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), - discarded, raceColumn.getFleetOfCompetitor(competitor)); - result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); - } - } - return result; - } - - @Override - public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); - } - - @Override - public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); - // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; - // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink - // all cache invalidation listeners for said trackedRace - if (!Util.contains(getTrackedRaces(), trackedRace)) { - synchronized (cacheInvalidationListeners) { - for (Iterator cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); - cacheInvalidationListenerIter.hasNext(); ) { - CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); - if (cacheInvalidationListener.getTrackedRace() == trackedRace) { - cacheInvalidationListener.removeFromTrackedRace(); - cacheInvalidationListenerIter.remove(); - } - } - } - } - } - - @Override - public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { - getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); - } - - @Override - public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { - getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); - } - - @Override - public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { - getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); - } - - @Override - public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { - getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); - } - - @Override - public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); - } - - @Override - public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { - getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); - } - - /** - * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. - */ - @Override - public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { - boolean result = true; - for (RaceColumn raceColumn : getRaceColumns()) { - if (raceColumn.getName().equals(newRaceColumn.getName())) { - result = false; - break; - } - } - return result; - } - - @Override - public void raceColumnAddedToContainer(RaceColumn raceColumn) { - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); - } - - @Override - public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); - } - - @Override - public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); - } - - @Override - public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); - } - - @Override - public boolean isTransient() { - return false; - } - - /** - * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to - * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been - * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, - * null is returned. The time point computed this way is a good choice for normalizing queries for later time - * points in an attempt to achieve more cache hits.

- * - * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used - * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point - * returned by this method but that column's attached tracked race has finished before the time point returned by this method, - * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. - * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache - * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and - * the column structure changing. - * - * @see TrackedRace#getTimePointOfNewestEvent() - * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() - */ - @Override - public TimePoint getTimePointOfLatestModification() { - TimePoint result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { - result = trackedRace.getTimePointOfNewestEvent(); - } - } - TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); - if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { - result = timePointOfLastScoreCorrection; - } - return result; - } - - @Override - public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { - com.sap.sse.common.Util.Pair result = null; - // TODO should we ensure that competitor participated in all race columns? - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - to = timePoint; - } - com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); - if (result == null || result.getB() == null || - (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { - result = maxSpeed; - } - } - } - } - return result; - } - - @Override - public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { - Speed result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && - timePoint.after(markPassings.last().getTimePoint())) { - result = null; - break; - } - to = timePoint; - } - Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceTraveled != null) { - result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); - } - } - } - } - return result; - } - - @Override - public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { - return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap()); - } - - private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map legTypeCache) throws NoWindException { - Duration result = null; - // TODO should we ensure that competitor participated in all race columns? - outerLoop: - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - trackedRace.getRace().getCourse().lockForRead(); - try { - for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { - TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { - // find out leg type at the time the competitor started the leg - try { - final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); - LegType trackedLegType = legTypeCache.get(trackedLeg); - if (trackedLegType == null) { - final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); - TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); - if (finishTime == null) { - finishTime = timePoint; - } - trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg - legTypeCache.put(trackedLeg, trackedLegType); - } - if (legType == trackedLegType) { - Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); - if (timeSpentOnDownwind != null) { - if (result == null) { - result = timeSpentOnDownwind; - } else { - result = result.plus(timeSpentOnDownwind); - } - } else { - // Although the competitor has started the leg, no value was produced. This - // means that the competitor didn't finish the leg before tracking ended. No useful value - // can be obtained for this competitor anymore. - result = null; - break outerLoop; - } - } - } catch (NoWindException nwe) { - // without wind there is no leg type and hence there is no reasonable value for this: - result = null; - break outerLoop; - } - } - } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); - } - } - } - return result; - } - - @Override - public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { - Duration result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line - if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) - && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() - .getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (trackedRace.getEndOfTracking() != null - && timePoint.after(trackedRace.getEndOfTracking())) { - result = null; // race not finished until end of tracking; no reasonable value can be - // computed for competitor - break; - } else { - to = timePoint; - } - } - Duration timeSpent = from.until(to); - if (result == null) { - result = timeSpent; - } else { - result=result.plus(timeSpent); - } - } - } - } - } - return result; - } - - @Override - public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { - Distance result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - TimePoint startOfRace; - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && - (startOfRace=trackedRace.getStartOfRace()) != null && - !startOfRace.after(timePoint)) { - Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceSailedInRace != null) { - if (result == null) { - result = distanceSailedInRace; - } else { - result = result.add(distanceSailedInRace); - } - } else { - // if competitor has not finished one single race in the whole - // series then we can not return a meaningful value for all - // all races - return null; - } - } - } - return result; - } - - protected RaceColumnListeners getRaceColumnListeners() { - return raceColumnListeners; - } - - @Override - public Iterable getCompetitors() { - final Iterable result; - // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the - // potentially large set of competitors - if (Util.isEmpty(getSuppressedCompetitors())) { - result = getAllCompetitors(); - } else { - final Iterable allCompetitors = getAllCompetitors(); - final Set suppressed = new HashSet<>(); - Util.addAll(getSuppressedCompetitors(), suppressed); - result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); - } - return result; - } - - @Override - public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { - return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); - } - - /** - * return an iterable with a smart iterator that filters out the suppressed elements on demand - */ - protected Iterable getCompetitorIterableSkippingSuppressed(final Iterable allCompetitors, - final Iterable suppressed) { - final Iterable result; - result = new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { - private final Iterator allIter = allCompetitors.iterator(); - private Competitor next = advance(); - - private Competitor advance() { - next = null; - while (allIter.hasNext() && next == null) { - next = allIter.next(); - if (Util.contains(suppressed, next)) { - next = null; - } - } - return next; - } - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public Competitor next() { - if (next == null) { - throw new NoSuchElementException(); - } - final Competitor result = next; - advance(); - return result; - } - }; - } - }; - return result; - } - - @Override - public Iterable getSuppressedCompetitors() { - LockUtil.lockForRead(suppressedCompetitorsLock); - try { - return new HashSet(suppressedCompetitors); - } finally { - LockUtil.unlockAfterRead(suppressedCompetitorsLock); - } - } - - @Override - public void setSuppressed(Competitor competitor, boolean suppressed) { - LockUtil.lockForWrite(suppressedCompetitorsLock); - try { - if (suppressed) { - suppressedCompetitors.add(competitor); - } else { - suppressedCompetitors.remove(competitor); - } - } finally { - LockUtil.unlockAfterWrite(suppressedCompetitorsLock); - } - getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); - } - - @Override - public TimePoint getNowMinusDelay() { - final TimePoint now = MillisecondsTimePoint.now(); - final Long delayToLiveInMillis = getDelayToLiveInMillis(); - TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); - return timePoint; - } - - @Override - public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { - getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); - if (event instanceof InvalidatesLeaderboardCache) { - // make sure to invalidate the cache as this event indicates that - // it changes values the cache could still hold - if (leaderboardDTOCache != null) { - leaderboardDTOCache.invalidate(this); - } - } - } - - @Override - public void regattaLogEventAdded(RegattaLogEvent event) { - getRaceColumnListeners().notifyListenersAboutRegattaLogEventAdded(event); - } - - @Override - public LeaderboardDTO computeDTO(final TimePoint timePoint, - final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, - final boolean fillNetPointsUncorrected) - throws NoWindException { - long startOfRequestHandling = System.currentTimeMillis(); - final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); - final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null - : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), - this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), - this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this - .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); - result.type = getLeaderboardType(); - result.competitors = new ArrayList(); - result.name = this.getName(); - result.competitorDisplayNames = new HashMap(); - for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { - result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); - } - // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these - // futures to finish: - Map>> competitorsFromBestToWorstTasks = new HashMap<>(); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; - RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, - isMetaLeaderboardColumn); - if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) { - calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, - trackedRegattaRegistry, baseDomainFactory); - } - for (Fleet fleet : raceColumn.getFleets()) { - RegattaAndRaceIdentifier raceIdentifier = null; - RaceDTO race = null; - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); - if (trackedRace != null) { - raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), - trackedRace.getRace().getName()); - race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); - } - // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created - // above by the result.createEmptyRaceColumn call - result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); - } - FutureTask> task = new FutureTask>( - () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); - executor.execute(task); - competitorsFromBestToWorstTasks.put(raceColumn, task); - } - // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data - for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { - try { - result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept - // null values. - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So - // this helps robustness from a user's perspective. - logger.log( - Level.SEVERE, - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails - + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); - } - } - result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); - result.rows = new HashMap(); - result.hasCarriedPoints = this.hasCarriedPoints(); - if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { - result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) - .getDiscardIndexResultsStartingWithHowManyRaces(); - } else { - result.discardThresholds = null; - } - // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep - // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for - // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg - // details are requested only once and pass them into getLeaderboardEntryDTO - final Map> legRanksCache = new HashMap>(); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - // if details for the column are requested, cache the leg's ranks - if (namesOfRaceColumnsForWhichToLoadLegDetails != null - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - trackedRace.getRace().getCourse().lockForRead(); - try { - for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { - legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); - } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); - } - } - } - } - } - for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { - CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); - LeaderboardRowDTO row = new LeaderboardRowDTO(); - row.competitor = competitorDTO; - row.fieldsByRaceColumnName = new HashMap(); - row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; - row.totalPoints = this.getTotalPoints(competitor, timePoint); - if (addOverallDetails) { - addOverallDetailsToRow(timePoint, competitor, row); - } - result.competitors.add(competitorDTO); - Map> futuresForColumnName = new HashMap>(); - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - RunnableFuture future = new FutureTask(() -> { - Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); - return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails != null - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn - .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, - fillNetPointsUncorrected, cache); - }); - executor.execute(future); - futuresForColumnName.put(raceColumn.getName(), future); - } - for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { - try { - row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept - // null values. - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So - // this helps robustness from a user's perspective. - logger.log( - Level.SEVERE, - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails - + "): exception during computing leaderboard entry for competitor " - + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() - + ". Leaving empty.", e); - } - } - result.rows.put(competitorDTO, row); - String displayName = this.getDisplayName(competitor); - if (displayName != null) { - result.competitorDisplayNames.put(competitorDTO, displayName); - } - } - logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " - + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " - + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); - return result; - } - - private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO, - TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) { - for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName(); - String raceName = trackedRace.getRace().getName(); - RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); - columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace))); - } - } - } - } - - private void addOverallDetailsToRow(final TimePoint timePoint, - final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { - final com.sap.sse.common.Util.Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); - if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { - row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); - row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); - } - Map legTypeCache = new HashMap<>(); - final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); - row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); - final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); - row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); - final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); - row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); - final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); - row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); - final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); - row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); - } - - /** - * @param waitForLatestAnalyses - * if false, this method is allowed to read the maneuver analysis results from a cache that - * may not reflect all data already received; otherwise, the method will always block for the latest - * cache updates to have happened before returning. - * @param fillNetPointsUncorrected - * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather - * expensive, especially when compared to simply retrieving a score correction, and particularly if in a - * larger fleet a number of competitors haven't properly finished the race. This should only be used for - * leaderboard editing where a user needs to see what the uncorrected score was that would be used when - * the correction was removed. - */ - private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, - TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, - Map> legRanksCache, DomainFactory baseDomainFactory, - boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { - LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); - TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); - entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); - entryDTO.netPoints = entry.getNetPoints(); - if (fillNetPointsUncorrected) { - entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); - } - entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); - entryDTO.totalPoints = entry.getTotalPoints(); - entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); - entryDTO.discarded = entry.isDiscarded(); - final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); - if (trackedRace != null) { - Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); - if (track != null) { - entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); - } - if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { - long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); - entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; - } else { - entryDTO.timeSinceLastPositionFixInSeconds = null; - } - } - if (addLegDetails && trackedRace != null) { - try { - final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); - RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, - legRanksCache, rankingInfo, cache); - entryDTO.legDetails = raceDetails.getLegDetails(); - entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null - : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); - entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); - entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null - : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); - entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null - : raceDetails.getAverageSignedCrossTrackError().getMeters(); - entryDTO.calculatedTime = raceDetails.getCorrectedTime(); - entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); - entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); - final TimePoint startOfRace = trackedRace.getStartOfRace(); - if (startOfRace != null) { - Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); - NavigableSet competitorMarkPassings = trackedRace.getMarkPassings(competitor); - trackedRace.lockForRead(competitorMarkPassings); - try { - if (!competitorMarkPassings.isEmpty()) { - final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); - if (firstMarkPassing.getWaypoint() == startWaypoint) { - Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); - entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null - : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); - Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); - entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null - : speedFiveSecondsBeforeStartOfRace.getKnots(); - Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( - competitor, startOfRace); - entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null - : distanceToStartLineAtStartOfRace.getMeters(); - Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); - entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null - : speedAtStartTime.getKnots(); - TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); - entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); - Speed competitorSpeedWhenPassingStart = track == null ? null : track - .getEstimatedSpeed(competitorStartTime); - entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null - : competitorSpeedWhenPassingStart.getKnots(); - try { - entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); - } catch (NoWindException nwe) { - entryDTO.startTack = null; // leave empty in case no wind information is available - } - Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace - .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); - entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null - : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); - } - } - } finally { - trackedRace.unlockAfterRead(competitorMarkPassings); - } - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception - } - } - final Fleet fleet = entry.getFleet(); - entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); - return entryDTO; - } - - /** - * Determines the time point of the last raw fix (with outliers not removed) for competitor in - * trackedRace. If the competitor's track is null or empty, null is returned. - * @param trackedRace must not be null - * @param atOrBefore find the last fix at or before the time point specified - */ - private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { - assert trackedRace != null; - final Date timePointOfLastPositionFix; - GPSFixTrack track = trackedRace.getTrack(competitor); - if (track == null) { - timePointOfLastPositionFix = null; - } else { - GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); - if (lastFix == null) { - timePointOfLastPositionFix = null; - } else { - timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); - } - } - return timePointOfLastPositionFix; - } - - private static class RaceDetails { - private final List legDetails; - private final Distance windwardDistanceToCompetitorFarthestAhead; - private final Distance averageAbsoluteCrossTrackError; - private final Distance averageSignedCrossTrackError; - private final Duration gapToLeaderInOwnTime; - private final Duration correctedTime; - private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - - public RaceDetails(List legDetails, Distance windwardDistanceToCompetitorFarthestAhead, - Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, - Duration gapToLeaderInOwnTime, Duration correctedTime, - Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { - super(); - this.legDetails = legDetails; - this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; - this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; - this.averageSignedCrossTrackError = averageSignedCrossTrackError; - this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; - this.correctedTime = correctedTime; - this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - } - public List getLegDetails() { - return legDetails; - } - public Distance getWindwardDistanceToCompetitorFarthestAhead() { - return windwardDistanceToCompetitorFarthestAhead; - } - public Distance getAverageAbsoluteCrossTrackError() { - return averageAbsoluteCrossTrackError; - } - public Distance getAverageSignedCrossTrackError() { - return averageSignedCrossTrackError; - } - public Duration getGapToLeaderInOwnTime() { - return gapToLeaderInOwnTime; - } - public Duration getCorrectedTime() { - return correctedTime; - } - public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { - return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - } - } - - /** - * If timePoint is after the end of the race's tracking the query will be adjusted to obtain the values - * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this - * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, - * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} - * will be registered with the race which will be triggered for any event received by the race. - * @param waitForLatestAnalyses - * if false, this method is allowed to read the maneuver analysis results from a cache that - * may not reflect all data already received; otherwise, the method will always block for the latest - * cache updates to have happened before returning. - */ - private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { - final RaceDetails raceDetails; - if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { - raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); - } else { - raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); - } - return raceDetails; - } - - private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, - final Competitor competitor, final Map> legRanksCache, - RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { - final com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(trackedRace, competitor); - RunnableFuture raceDetails; - synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetails = raceDetailsAtEndOfTrackingCache.get(key); - if (raceDetails == null) { - raceDetails = new FutureTask(new Callable() { - @Override - public RaceDetails call() throws Exception { - TimePoint end = trackedRace.getEndOfRace(); - if (end == null) { - end = trackedRace.getEndOfTracking(); - } - return calculateRaceDetails(trackedRace, competitor, end, - // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock - /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ - /* waitForLatestAnalyses (maneuver and cross track error) */ false, - legRanksCache, cache, rankingInfo); - } - }); - raceDetailsExecutor.execute(raceDetails); - raceDetailsAtEndOfTrackingCache.put(key, raceDetails); - final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); - trackedRace.addListener(cacheInvalidationListener); - cacheInvalidationListeners.add(cacheInvalidationListener); - } - } - return raceDetails.get(); - } - - private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { - final List legDetails = new ArrayList(); - final Course course = trackedRace.getRace().getCourse(); - course.lockForRead(); // hold back any course re-configurations while looping over the legs - try { - for (Leg leg : course.getLegs()) { - LegEntryDTO legEntry; - // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with - // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change - // immediately. That's why we've acquired a read lock for the course above. - TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { - legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); - } else { - legEntry = null; - } - legDetails.add(legEntry); - } - final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace - .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); - Distance averageAbsoluteCrossTrackError; - try { - averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( - competitor, timePoint, waitForLatestAnalyses, cache); - } catch (NoWindException nwe) { - // without wind information, use null meaning "unknown" - averageAbsoluteCrossTrackError = null; - } - Distance averageSignedCrossTrackError; - try { - averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( - competitor, timePoint, waitForLatestAnalyses, cache); - } catch (NoWindException nwe) { - // without wind information, use null meaning "unknown" - averageSignedCrossTrackError = null; - } - final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); - return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, - trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), - trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), - competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); - } finally { - course.unlockAfterRead(); - } - } - - private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { - LegEntryDTO result; - final Duration time = trackedLeg.getTime(timePoint); - if (trackedLeg == null || time == null) { - result = null; - } else { - result = new LegEntryDTO(); - try { - result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); - } catch (NoWindException nwe) { - result.legType = null; // can't determine leg type without wind data - } - final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); - result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - Distance averageAbsoluteCrossTrackError; - try { - averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); - } catch (NoWindException nwe) { - // leave averageAbsoluteCrossTrackError as null, meaning "unknown" - averageAbsoluteCrossTrackError = null; - } - result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); - Distance averageSignedCrossTrackError; - try { - averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); - } catch (NoWindException nwe) { - // leave averageSignedCrossTrackError as null, meaning "unknown" - averageSignedCrossTrackError = null; - } - result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); - Double speedOverGroundInKnots; - if (trackedLeg.hasFinishedLeg(timePoint)) { - speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - } else { - final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); - speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); - } - result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; - Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); - result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); - Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); - result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); - final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); - result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); - result.timeInMilliseconds = time.asMillis(); - result.finished = trackedLeg.hasFinishedLeg(timePoint); - final TimePoint legFinishTime = trackedLeg.getFinishTime(); - result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), - trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; - // fetch the leg gap in own corrected time from the ranking metric - final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). - getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); - result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); - if (result.gapToLeaderInSeconds != null) { - final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); - if (gapAtEndOfPreviousLeg != null) { - result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); - } - } - LinkedHashMap legRanks = legRanksCache.get(trackedLeg.getLeg()); - if (legRanks != null) { - result.rank = legRanks.get(trackedLeg.getCompetitor()); - } else { - result.rank = trackedLeg.getRank(timePoint, cache); - } - result.started = trackedLeg.hasStartedLeg(timePoint); - Speed velocityMadeGood; - if (trackedLeg.hasFinishedLeg(timePoint)) { - velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); - } else { - velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); - } - result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); - Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); - result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo - .getMeters(); - final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); - if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { - // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg - // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg - List maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() - .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); - if (maneuvers != null) { - result.numberOfManeuvers = new HashMap(); - result.numberOfManeuvers.put(ManeuverType.TACK, 0); - result.numberOfManeuvers.put(ManeuverType.JIBE, 0); - result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); - Map totalManeuverLossInMeters = new HashMap(); - totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); - totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); - totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); - TimePoint startOfLeg = trackedLeg.getStartTime(); - for (Maneuver maneuver : maneuvers) { - // don't count maneuvers that were in previous legs - switch (maneuver.getType()) { - case TACK: - case JIBE: - case PENALTY_CIRCLE: - if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || - maneuver.getTimePoint().before(legFinishTime))) { - if (maneuver.getManeuverLoss() != null) { - result.numberOfManeuvers.put(maneuver.getType(), - result.numberOfManeuvers.get(maneuver.getType()) + 1); - totalManeuverLossInMeters.put(maneuver.getType(), - totalManeuverLossInMeters.get(maneuver.getType()) - + maneuver.getManeuverLoss().getMeters()); - } - } - break; - case MARK_PASSING: - // analyze all mark passings, not only those after this leg's start, to catch the mark passing - // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg - MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; - if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { - result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); - } - break; - default: - /* Do nothing here. - * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. - */ - } - } - result.averageManeuverLossInMeters = new HashMap(); - for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, - ManeuverType.PENALTY_CIRCLE }) { - if (result.numberOfManeuvers.get(maneuverType) != 0) { - result.averageManeuverLossInMeters.put( - maneuverType, - totalManeuverLossInMeters.get(maneuverType) - / result.numberOfManeuvers.get(maneuverType)); - } - } - } - } - } - return result; - } - - private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { - final Duration result; - final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); - // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap - // at the end of the previous leg - final TimePoint timePoint = trackedLeg.getStartTime(); - final TrackedLegOfCompetitor tloc; - if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { - tloc = trackedLeg; - } else { - tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) - .getTrackedLeg(trackedLeg.getCompetitor()); - } - result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); - return result; - } - - private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, - boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { - LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, - baseDomainFactory); - return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); - } - - private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, - DomainFactory baseDomainFactory) { - LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; - if (result == null) { - synchronized (this) { - result = this.liveLeaderboardUpdater; - if (result == null) { - this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); - result = this.liveLeaderboardUpdater; - } - } - } - return result; - } - - private LeaderboardDTOCache getLeaderboardDTOCache() { - LeaderboardDTOCache result = this.leaderboardDTOCache; - if (result == null) { - synchronized (this) { - result = this.leaderboardDTOCache; - if (result == null) { - // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes - // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a - // single fix was updated is too expensive if users use the replay feature while a race is still running. - // Therefore, using waitForLatestAnalyses==false seems appropriate here. - this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); - result = this.leaderboardDTOCache; - } - } - } - return result; - } - - @Override - public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, - Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, - InterruptedException, ExecutionException { - LeaderboardDTO result = null; - if (timePoint == null) { - // timePoint==null means live mode; however, if we're after the end of all races and after all score - // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map - // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly - // when the tracked race associations or score corrections or tracked race contents changes: - final TimePoint nowMinusDelay = this.getNowMinusDelay(); - final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); - if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { - // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode - // and pull the result from the regular leaderboard cache: - timePoint = timePointOfLatestModification; - } else { - // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: - timePoint = null; - result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); - } - } - if (timePoint != null) { - if (fillNetPointsUncorrected) { - // explicitly filling the uncorrected net points requires uncached recalculation - result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, - trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); - } else { - // in replay we'd like up-to-date results; they are still cached - // which is OK because the cache is invalidated whenever any of the tracked races attached to the - // leaderboard changes. - result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, - trackedRegattaRegistry); - } - } - return result; - } - - public String toString() { - return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); - } - - @Override - public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { - return new NumberOfCompetitorsFetcherImpl(); - } - - protected abstract LeaderboardType getLeaderboardType(); - -} +package com.sap.sailing.domain.leaderboard.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.NoWindError; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.dto.BasicRaceDTO; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; +import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; +import com.sap.sailing.domain.common.dto.LegEntryDTO; +import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result; +import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; +import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; +import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; +import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.MarkPassingManeuver; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; +import com.sap.sailing.domain.tracking.WindPositionMode; +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; +import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; +import com.sap.sse.util.impl.ThreadFactoryWithPriority; + +/** + * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events + * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes + * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful + * candidates. + * + * @author Axel Uhl (d043530) + * + */ +public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { + private static final long serialVersionUID = 330156778603279333L; + + private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); + + static final Double DOUBLE_0 = new Double(0); + + private final SettableScoreCorrection scoreCorrection; + + private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; + + /** + * The optional display name mappings for competitors. This allows a user to override the tracking-provided + * competitor names for display in a leaderboard. + */ + private final Map displayNames; + + /** the display name of the leaderboard */ + private String displayName; + + /** + * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard + * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points + * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}. + */ + private final Map carriedPoints; + + private final RaceColumnListeners raceColumnListeners; + + /** + * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access + * is controlled by the {@link #suppressedCompetitorsLock} lock. + */ + private final Set suppressedCompetitors; + private final NamedReentrantReadWriteLock suppressedCompetitorsLock; + + private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; + + /** + * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} + * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't + * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, + * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). + */ + private transient Executor raceDetailsExecutor; + + /** + * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. + */ + private transient Set cacheInvalidationListeners; + + private transient ThreadPoolExecutor executor; + + private transient LiveLeaderboardUpdater liveLeaderboardUpdater; + + private transient LeaderboardDTOCache leaderboardDTOCache; + + /** + * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. + * + * @author Axel Uhl (d043530) + * + */ + class EntryImpl implements Entry { + private final Callable trackedRankProvider; + private final Double netPoints; + private final Callable netPointsUncorrectedProvider; + private final boolean isNetPointsCorrected; + private final Double totalPoints; + private final MaxPointsReason maxPointsReason; + private final boolean discarded; + private final Fleet fleet; + + private EntryImpl(Callable trackedRankProvider, Double netPoints, + Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, + Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { + super(); + this.trackedRankProvider = trackedRankProvider; + this.netPoints = netPoints; + this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; + this.isNetPointsCorrected = isNetPointsCorrected; + this.totalPoints = totalPoints; + this.maxPointsReason = maxPointsReason; + this.discarded = discarded; + this.fleet = fleet; + } + @Override + public int getTrackedRank() { + try { + return trackedRankProvider.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override + public Double getNetPoints() { + return netPoints; + } + @Override + public boolean isNetPointsCorrected() { + return isNetPointsCorrected; + } + @Override + public Double getTotalPoints() { + return totalPoints; + } + @Override + public MaxPointsReason getMaxPointsReason() { + return maxPointsReason; + } + @Override + public boolean isDiscarded() { + return discarded; + } + @Override + public Fleet getFleet() { + return fleet; + } + @Override + public Double getNetPointsUncorrected() { + try { + return netPointsUncorrectedProvider.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed + * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have + * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list + * would be cached, but that is again difficult because we would have to monitor all changes in all dependent + * leaderboards and columns and tracked races properly. + *

+ * + * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} + * competitors list is to determine their number which in turn is only required for high-point scoring systems and + * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes + * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that + * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the + * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be + * constructed each time the number shall be computed. + * + * @author Axel Uhl (D043530) + * + */ + public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { + private int numberOfCompetitors = -1; + + @Override + public int getNumberOfCompetitorsInLeaderboard() { + if (numberOfCompetitors == -1) { + numberOfCompetitors = Util.size(getCompetitors()); + } + return numberOfCompetitors; + } + } + + /** + * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked + * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, + * calculations may start or resume, making it necessary to clear the cache. + * + * @author Axel Uhl (D043530) + * + */ + private class CacheInvalidationListener extends AbstractRaceChangeListener { + private final TrackedRace trackedRace; + private final Competitor competitor; + + public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { + this.trackedRace = trackedRace; + this.competitor = competitor; + } + + public TrackedRace getTrackedRace() { + return trackedRace; + } + + public void removeFromTrackedRace() { + trackedRace.removeListener(this); + } + + private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { + synchronized (raceDetailsAtEndOfTrackingCache) { + raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); + removeFromTrackedRace(); + } + } + + @Override + protected void defaultAction() { + invalidateCacheAndRemoveThisListenerFromTrackedRace(); + } + } + + private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { + @Override + public String generateRandomUUID() { + return UUID.randomUUID().toString(); + } + } + + public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { + this.carriedPoints = new HashMap(); + this.scoreCorrection = createScoreCorrection(); + this.displayNames = new HashMap(); + this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; + this.suppressedCompetitors = new HashSet(); + this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); + this.raceColumnListeners = new RaceColumnListeners(); + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + initTransientFields(); + } + + /** + * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override + * this method to create a more specific type of score correction. This implementation produces an object of type + * {@link ScoreCorrectionImpl}. + */ + protected SettableScoreCorrection createScoreCorrection() { + return new ScoreCorrectionImpl(this); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + initTransientFields(); + } + + private void initTransientFields() { + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + this.cacheInvalidationListeners = new HashSet(); + // When many updates are triggered in a short period of time by a single thread, ensure that the single thread + // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one + // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. + final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); + executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, + /* maximumPoolSize */ THREAD_POOL_SIZE, + /* keepAliveTime */ 60, TimeUnit.SECONDS, + /* workQueue */ new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); + } + + @Override + public void destroy() { + for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { + cacheInvalidationListener.removeFromTrackedRace(); + } + } + + @Override + public SettableScoreCorrection getScoreCorrection() { + return scoreCorrection; + } + + @Override + public String getDisplayName(Competitor competitor) { + return displayNames.get(competitor); + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Override + public ResultDiscardingRule getResultDiscardingRule() { + return crossLeaderboardResultDiscardingRule; + } + + @Override + public void setCarriedPoints(Competitor competitor, double carriedPoints) { + Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); + if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); + } + } + + @Override + public double getCarriedPoints(Competitor competitor) { + Double result = carriedPoints.get(competitor); + return result == null ? 0 : result; + } + + @Override + public Map getCompetitorsForWhichThereAreCarriedPoints() { + return Collections.unmodifiableMap(carriedPoints); + } + + @Override + public void unsetCarriedPoints(Competitor competitor) { + Double oldCarriedPoints = carriedPoints.remove(competitor); + if (oldCarriedPoints != null) { + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); + } + } + + @Override + public boolean hasCarriedPoints() { + return !carriedPoints.isEmpty(); + } + + @Override + public boolean hasCarriedPoints(Competitor competitor) { + return carriedPoints.containsKey(competitor); + } + + @Override + public void setDisplayName(Competitor competitor, String displayName) { + String oldDisplayName = displayNames.get(competitor); + displayNames.put(competitor, displayName); + if (!Util.equalsWithNull(oldDisplayName, displayName)) { + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + } + } + + @Override + public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { + ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); + this.crossLeaderboardResultDiscardingRule = discardingRule; + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); + } + + @Override + public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { + return getScoreCorrection().getCorrectedScore( + new Callable() { + public Integer call() throws NoWindException { + return getTrackedRank(competitor, raceColumn, timePoint); + } + }, competitor, + raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); + } + + @Override + public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); + } + + @Override + public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); + } + + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, + Iterable raceColumnsToConsider, TimePoint timePoint) { + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); + } + + /** + * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when total points are to be computed for more than one column for the same + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done + * once and not again for each column (which would lead to quadratic effort). + * + * @param discardedRaceColumns + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. + */ + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, + final Set discardedRaceColumns) { + return !raceColumn.isMedalRace() + && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() + && discardedRaceColumns.contains(raceColumn); + } + + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); + } + + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, + Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + } + + /** + * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when total points are to be computed for more than one column for the same + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done + * once and not again for each column (which would lead to quadratic effort). + * + * @param discardedRaceColumns + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. + */ + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { + Double result; + if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { + result = 0.0; + } else { + final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); + if (netPoints == null) { + result = null; + } else { + result = raceColumn.getFactor() * netPoints; + } + } + return result; + } + + @Override + public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { + return getTotalPoints(competitor, getRaceColumns(), timePoint); + } + + @Override + public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, + TimePoint timePoint) { + // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on + boolean needToResetScoreUponNextNonEmptyEntry = false; + double result = getCarriedPoints(competitor); + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + for (RaceColumn raceColumn : raceColumnsToConsider) { + if (raceColumn.isStartsWithZeroScore()) { + needToResetScoreUponNextNonEmptyEntry = true; + } + if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { + final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + if (totalPoints != null) { + if (needToResetScoreUponNextNonEmptyEntry) { + result = 0; + needToResetScoreUponNextNonEmptyEntry = false; + } + result += totalPoints; + } + } + } + return result; + } + + /** + * All competitors with non-null net points are added to the result which is then sorted by net points in ascending + * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. + */ + @Override + public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + final Map> netPointsAndFleet = new HashMap>(); + for (Competitor competitor : getCompetitors()) { + Double netPoints = getNetPoints(competitor, raceColumn, timePoint); + if (netPoints != null) { + netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); + } + } + List result = new ArrayList(netPointsAndFleet.keySet()); + Collections.sort(result, new Comparator() { + @Override + public int compare(Competitor o1, Competitor o2) { + int comparisonResult; + if (o1 == o2) { + comparisonResult = 0; + } else { + if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { + // only check fleets if there are more than one and the column is not to be contiguously scored even in case + // of split fleets + final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); + final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); + if (o1Fleet == null) { + if (o2Fleet == null) { + comparisonResult = 0; + } else { + comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 + // has + } + } else { + if (o2Fleet == null) { + comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't + } else { + comparisonResult = o1Fleet.compareTo(o2Fleet); + } + } + } else { + // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets + // the scoring runs contiguously from top to bottom + comparisonResult = 0; + } + if (comparisonResult == 0) { + comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( + netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); + } + } + return comparisonResult; + } + }); + return result; + } + + @Override + public List getCompetitorsFromBestToWorst(TimePoint timePoint) { + return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); + } + + private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { + List result = new ArrayList(); + for (Competitor competitor : getCompetitors()) { + result.add(competitor); + } + Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); + return result; + } + + @Override + public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { + List competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); + return competitorsFromBestToWorst.indexOf(competitor) + 1; + } + + protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { + return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); + } + + @Override + public RaceColumn getRaceColumnByName(String columnName) { + RaceColumn result = null; + for (RaceColumn r : getRaceColumns()) { + if (r.getName().equals(columnName)) { + result = r; + break; + } + } + return result; + } + + @Override + public Competitor getCompetitorByName(String competitorName) { + for (Competitor competitor : getAllCompetitors()) { + if (competitor.getName().equals(competitorName)) { + return competitor; + } + } + return null; + } + + @Override + public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + TrackedRace trackedRaceForCompetitorInColumn; + return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && + (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || + ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && + trackedRaceForCompetitorInColumn.hasStarted(timePoint) && + trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); + } + + @Override + public void addRaceColumnListener(RaceColumnListener listener) { + getRaceColumnListeners().addRaceColumnListener(listener); + } + + @Override + public void removeRaceColumnListener(RaceColumnListener listener) { + getRaceColumnListeners().removeRaceColumnListener(listener); + } + + @Override + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + return getEntry(competitor, race, timePoint, discardedRaceColumns); + } + + @Override + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, + Set discardedRaceColumns) throws NoWindException { + Callable trackedRankProvider = new Callable() { + public Integer call() throws NoWindException { + return getTrackedRank(competitor, race, timePoint); + } + }; + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); + final Double correctedScore = correctedResults.getCorrectedScore(); + return new EntryImpl(trackedRankProvider, correctedScore, new Callable() { + @Override + public Double call() { + return correctedResults.getUncorrectedScore(); + } + }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null + : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, + race.getFleetOfCompetitor(competitor)); + } + + @Override + public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { + Map> result = new LinkedHashMap<>(); + List raceColumnsToConsider = new ArrayList<>(); + for (RaceColumn raceColumn : getRaceColumns()) { + raceColumnsToConsider.add(raceColumn); + result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); + } + return result; + } + + @Override + public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) + throws NoWindException { + final Map> result = new LinkedHashMap<>(); + List raceColumnsToConsider = new ArrayList<>(); + Map>> futures = new HashMap<>(); + for (final RaceColumn raceColumn : getRaceColumns()) { + raceColumnsToConsider.add(raceColumn); + final Iterable finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); + futures.put(raceColumn, executor.submit(new Callable>() { + @Override + public Map call() { + Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); + for (Competitor competitor : getCompetitors()) { + totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); + } + synchronized (result) { + return totalPointsSumPerCompetitorInColumn; + } + } + })); + } + for (RaceColumn raceColumn : getRaceColumns()) { + try { + result.put(raceColumn, futures.get(raceColumn).get()); + } catch (InterruptedException | ExecutionException e) { + if (e.getCause() instanceof NoWindError) { + throw ((NoWindError) e.getCause()).getCause(); + } else { + throw new RuntimeException(e); // no caught exceptions occur in the futures executed + } + } + } + return result; + } + + @Override + public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { + Map, Entry> result = new HashMap, Entry>(); + Map> discardedRaces = new HashMap>(); + for (final RaceColumn raceColumn : getRaceColumns()) { + for (final Competitor competitor : getCompetitors()) { + Callable trackedRankProvider = new Callable() { + @Override + public Integer call() throws Exception { + return getTrackedRank(competitor, raceColumn, timePoint); + } + }; + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + Set discardedRacesForCompetitor = discardedRaces.get(competitor); + if (discardedRacesForCompetitor == null) { + discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + discardedRaces.put(competitor, discardedRacesForCompetitor); + } + boolean discarded = discardedRacesForCompetitor.contains(raceColumn); + final Double correctedScore = correctedResults.getCorrectedScore(); + Entry entry = new EntryImpl(trackedRankProvider, correctedScore, + new Callable() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, + correctedResults.isCorrected(), + discarded ? DOUBLE_0 : (correctedScore==null?null: + Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), + discarded, raceColumn.getFleetOfCompetitor(competitor)); + result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); + } + } + return result; + } + + @Override + public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); + } + + @Override + public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); + // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; + // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink + // all cache invalidation listeners for said trackedRace + if (!Util.contains(getTrackedRaces(), trackedRace)) { + synchronized (cacheInvalidationListeners) { + for (Iterator cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); + cacheInvalidationListenerIter.hasNext(); ) { + CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); + if (cacheInvalidationListener.getTrackedRace() == trackedRace) { + cacheInvalidationListener.removeFromTrackedRace(); + cacheInvalidationListenerIter.remove(); + } + } + } + } + } + + @Override + public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { + getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); + } + + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + + @Override + public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { + getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); + } + + @Override + public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { + getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); + } + + @Override + public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { + getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); + } + + @Override + public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); + } + + @Override + public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { + getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); + } + + /** + * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. + */ + @Override + public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { + boolean result = true; + for (RaceColumn raceColumn : getRaceColumns()) { + if (raceColumn.getName().equals(newRaceColumn.getName())) { + result = false; + break; + } + } + return result; + } + + @Override + public void raceColumnAddedToContainer(RaceColumn raceColumn) { + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); + } + + @Override + public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); + } + + @Override + public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + } + + @Override + public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); + } + + @Override + public boolean isTransient() { + return false; + } + + /** + * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to + * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been + * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, + * null is returned. The time point computed this way is a good choice for normalizing queries for later time + * points in an attempt to achieve more cache hits.

+ * + * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used + * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point + * returned by this method but that column's attached tracked race has finished before the time point returned by this method, + * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. + * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache + * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and + * the column structure changing. + * + * @see TrackedRace#getTimePointOfNewestEvent() + * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() + */ + @Override + public TimePoint getTimePointOfLatestModification() { + TimePoint result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { + result = trackedRace.getTimePointOfNewestEvent(); + } + } + TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); + if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { + result = timePointOfLastScoreCorrection; + } + return result; + } + + @Override + public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { + com.sap.sse.common.Util.Pair result = null; + // TODO should we ensure that competitor participated in all race columns? + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = markPassings.first().getTimePoint(); + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) && + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + to = timePoint; + } + com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); + if (result == null || result.getB() == null || + (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { + result = maxSpeed; + } + } + } + } + return result; + } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + Speed result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = markPassings.first().getTimePoint(); + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) && + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && + timePoint.after(markPassings.last().getTimePoint())) { + result = null; + break; + } + to = timePoint; + } + Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); + if (distanceTraveled != null) { + result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); + } + } + } + } + return result; + } + + @Override + public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { + return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap()); + } + + private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map legTypeCache) throws NoWindException { + Duration result = null; + // TODO should we ensure that competitor participated in all race columns? + outerLoop: + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + trackedRace.getRace().getCourse().lockForRead(); + try { + for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { + TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { + // find out leg type at the time the competitor started the leg + try { + final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); + LegType trackedLegType = legTypeCache.get(trackedLeg); + if (trackedLegType == null) { + final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); + TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); + if (finishTime == null) { + finishTime = timePoint; + } + trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg + legTypeCache.put(trackedLeg, trackedLegType); + } + if (legType == trackedLegType) { + Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); + if (timeSpentOnDownwind != null) { + if (result == null) { + result = timeSpentOnDownwind; + } else { + result = result.plus(timeSpentOnDownwind); + } + } else { + // Although the competitor has started the leg, no value was produced. This + // means that the competitor didn't finish the leg before tracking ended. No useful value + // can be obtained for this competitor anymore. + result = null; + break outerLoop; + } + } + } catch (NoWindException nwe) { + // without wind there is no leg type and hence there is no reasonable value for this: + result = null; + break outerLoop; + } + } + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + } + } + return result; + } + + @Override + public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { + Duration result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line + if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) + && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() + .getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + if (trackedRace.getEndOfTracking() != null + && timePoint.after(trackedRace.getEndOfTracking())) { + result = null; // race not finished until end of tracking; no reasonable value can be + // computed for competitor + break; + } else { + to = timePoint; + } + } + Duration timeSpent = from.until(to); + if (result == null) { + result = timeSpent; + } else { + result=result.plus(timeSpent); + } + } + } + } + } + return result; + } + + @Override + public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { + Distance result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + TimePoint startOfRace; + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && + (startOfRace=trackedRace.getStartOfRace()) != null && + !startOfRace.after(timePoint)) { + Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); + if (distanceSailedInRace != null) { + if (result == null) { + result = distanceSailedInRace; + } else { + result = result.add(distanceSailedInRace); + } + } else { + // if competitor has not finished one single race in the whole + // series then we can not return a meaningful value for all + // all races + return null; + } + } + } + return result; + } + + protected RaceColumnListeners getRaceColumnListeners() { + return raceColumnListeners; + } + + @Override + public Iterable getCompetitors() { + final Iterable result; + // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the + // potentially large set of competitors + if (Util.isEmpty(getSuppressedCompetitors())) { + result = getAllCompetitors(); + } else { + final Iterable allCompetitors = getAllCompetitors(); + final Set suppressed = new HashSet<>(); + Util.addAll(getSuppressedCompetitors(), suppressed); + result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); + } + return result; + } + + @Override + public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { + return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); + } + + /** + * return an iterable with a smart iterator that filters out the suppressed elements on demand + */ + protected Iterable getCompetitorIterableSkippingSuppressed(final Iterable allCompetitors, + final Iterable suppressed) { + final Iterable result; + result = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator allIter = allCompetitors.iterator(); + private Competitor next = advance(); + + private Competitor advance() { + next = null; + while (allIter.hasNext() && next == null) { + next = allIter.next(); + if (Util.contains(suppressed, next)) { + next = null; + } + } + return next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Competitor next() { + if (next == null) { + throw new NoSuchElementException(); + } + final Competitor result = next; + advance(); + return result; + } + }; + } + }; + return result; + } + + @Override + public Iterable getSuppressedCompetitors() { + LockUtil.lockForRead(suppressedCompetitorsLock); + try { + return new HashSet(suppressedCompetitors); + } finally { + LockUtil.unlockAfterRead(suppressedCompetitorsLock); + } + } + + @Override + public void setSuppressed(Competitor competitor, boolean suppressed) { + LockUtil.lockForWrite(suppressedCompetitorsLock); + try { + if (suppressed) { + suppressedCompetitors.add(competitor); + } else { + suppressedCompetitors.remove(competitor); + } + } finally { + LockUtil.unlockAfterWrite(suppressedCompetitorsLock); + } + getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); + } + + @Override + public TimePoint getNowMinusDelay() { + final TimePoint now = MillisecondsTimePoint.now(); + final Long delayToLiveInMillis = getDelayToLiveInMillis(); + TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); + return timePoint; + } + + @Override + public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { + getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); + if (event instanceof InvalidatesLeaderboardCache) { + // make sure to invalidate the cache as this event indicates that + // it changes values the cache could still hold + if (leaderboardDTOCache != null) { + leaderboardDTOCache.invalidate(this); + } + } + } + + @Override + public void regattaLogEventAdded(RegattaLogEvent event) { + getRaceColumnListeners().notifyListenersAboutRegattaLogEventAdded(event); + } + + @Override + public LeaderboardDTO computeDTO(final TimePoint timePoint, + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, + final boolean fillNetPointsUncorrected) + throws NoWindException { + long startOfRequestHandling = System.currentTimeMillis(); + final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); + final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null + : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), + this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), + this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this + .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); + result.type = getLeaderboardType(); + result.competitors = new ArrayList(); + result.name = this.getName(); + result.competitorDisplayNames = new HashMap(); + for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { + result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); + } + // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these + // futures to finish: + Map>> competitorsFromBestToWorstTasks = new HashMap<>(); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; + RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + isMetaLeaderboardColumn); + if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) { + calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, + trackedRegattaRegistry, baseDomainFactory); + } + for (Fleet fleet : raceColumn.getFleets()) { + RegattaAndRaceIdentifier raceIdentifier = null; + RaceDTO race = null; + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); + if (trackedRace != null) { + raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), + trackedRace.getRace().getName()); + race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); + } + // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created + // above by the result.createEmptyRaceColumn call + result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); + } + FutureTask> task = new FutureTask>( + () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); + executor.execute(task); + competitorsFromBestToWorstTasks.put(raceColumn, task); + } + // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data + for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { + try { + result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept + // null values. + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So + // this helps robustness from a user's perspective. + logger.log( + Level.SEVERE, + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); + } + } + result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); + result.rows = new HashMap(); + result.hasCarriedPoints = this.hasCarriedPoints(); + if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { + result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) + .getDiscardIndexResultsStartingWithHowManyRaces(); + } else { + result.discardThresholds = null; + } + // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep + // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for + // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg + // details are requested only once and pass them into getLeaderboardEntryDTO + final Map> legRanksCache = new HashMap>(); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + // if details for the column are requested, cache the leg's ranks + if (namesOfRaceColumnsForWhichToLoadLegDetails != null + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + trackedRace.getRace().getCourse().lockForRead(); + try { + for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { + legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + } + } + } + } + for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { + CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); + LeaderboardRowDTO row = new LeaderboardRowDTO(); + row.competitor = competitorDTO; + row.fieldsByRaceColumnName = new HashMap(); + row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; + row.totalPoints = this.getTotalPoints(competitor, timePoint); + if (addOverallDetails) { + addOverallDetailsToRow(timePoint, competitor, row); + } + result.competitors.add(competitorDTO); + Map> futuresForColumnName = new HashMap>(); + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + RunnableFuture future = new FutureTask(() -> { + Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); + return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, + namesOfRaceColumnsForWhichToLoadLegDetails != null + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn + .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, + fillNetPointsUncorrected, cache); + }); + executor.execute(future); + futuresForColumnName.put(raceColumn.getName(), future); + } + for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { + try { + row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept + // null values. + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So + // this helps robustness from a user's perspective. + logger.log( + Level.SEVERE, + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + + "): exception during computing leaderboard entry for competitor " + + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() + + ". Leaving empty.", e); + } + } + result.rows.put(competitorDTO, row); + String displayName = this.getDisplayName(competitor); + if (displayName != null) { + result.competitorDisplayNames.put(competitorDTO, displayName); + } + } + logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " + + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " + + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); + return result; + } + + private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO, + TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) { + for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName(); + String raceName = trackedRace.getRace().getName(); + RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); + columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace))); + } + } + } + } + + private void addOverallDetailsToRow(final TimePoint timePoint, + final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { + final com.sap.sse.common.Util.Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); + if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { + row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); + row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); + } + Map legTypeCache = new HashMap<>(); + final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); + row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); + final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); + row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); + final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); + row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); + final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); + row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); + final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); + row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); + } + + /** + * @param waitForLatestAnalyses + * if false, this method is allowed to read the maneuver analysis results from a cache that + * may not reflect all data already received; otherwise, the method will always block for the latest + * cache updates to have happened before returning. + * @param fillNetPointsUncorrected + * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather + * expensive, especially when compared to simply retrieving a score correction, and particularly if in a + * larger fleet a number of competitors haven't properly finished the race. This should only be used for + * leaderboard editing where a user needs to see what the uncorrected score was that would be used when + * the correction was removed. + */ + private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, + TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, + Map> legRanksCache, DomainFactory baseDomainFactory, + boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { + LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); + TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); + entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); + entryDTO.netPoints = entry.getNetPoints(); + if (fillNetPointsUncorrected) { + entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); + } + entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); + entryDTO.totalPoints = entry.getTotalPoints(); + entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); + entryDTO.discarded = entry.isDiscarded(); + final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); + if (trackedRace != null) { + Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); + if (track != null) { + entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); + } + if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { + long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); + entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; + } else { + entryDTO.timeSinceLastPositionFixInSeconds = null; + } + } + if (addLegDetails && trackedRace != null) { + try { + final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); + RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, + legRanksCache, rankingInfo, cache); + entryDTO.legDetails = raceDetails.getLegDetails(); + entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null + : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); + entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); + entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null + : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); + entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null + : raceDetails.getAverageSignedCrossTrackError().getMeters(); + entryDTO.calculatedTime = raceDetails.getCorrectedTime(); + entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); + entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); + final TimePoint startOfRace = trackedRace.getStartOfRace(); + if (startOfRace != null) { + Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + NavigableSet competitorMarkPassings = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(competitorMarkPassings); + try { + if (!competitorMarkPassings.isEmpty()) { + final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); + if (firstMarkPassing.getWaypoint() == startWaypoint) { + Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); + entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null + : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); + Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); + entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null + : speedFiveSecondsBeforeStartOfRace.getKnots(); + Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( + competitor, startOfRace); + entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null + : distanceToStartLineAtStartOfRace.getMeters(); + Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); + entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null + : speedAtStartTime.getKnots(); + TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); + entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); + Speed competitorSpeedWhenPassingStart = track == null ? null : track + .getEstimatedSpeed(competitorStartTime); + entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null + : competitorSpeedWhenPassingStart.getKnots(); + try { + entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); + } catch (NoWindException nwe) { + entryDTO.startTack = null; // leave empty in case no wind information is available + } + Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace + .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); + entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null + : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); + } + } + } finally { + trackedRace.unlockAfterRead(competitorMarkPassings); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception + } + } + final Fleet fleet = entry.getFleet(); + entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); + return entryDTO; + } + + /** + * Determines the time point of the last raw fix (with outliers not removed) for competitor in + * trackedRace. If the competitor's track is null or empty, null is returned. + * @param trackedRace must not be null + * @param atOrBefore find the last fix at or before the time point specified + */ + private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { + assert trackedRace != null; + final Date timePointOfLastPositionFix; + GPSFixTrack track = trackedRace.getTrack(competitor); + if (track == null) { + timePointOfLastPositionFix = null; + } else { + GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); + if (lastFix == null) { + timePointOfLastPositionFix = null; + } else { + timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); + } + } + return timePointOfLastPositionFix; + } + + private static class RaceDetails { + private final List legDetails; + private final Distance windwardDistanceToCompetitorFarthestAhead; + private final Distance averageAbsoluteCrossTrackError; + private final Distance averageSignedCrossTrackError; + private final Duration gapToLeaderInOwnTime; + private final Duration correctedTime; + private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + + public RaceDetails(List legDetails, Distance windwardDistanceToCompetitorFarthestAhead, + Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, + Duration gapToLeaderInOwnTime, Duration correctedTime, + Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { + super(); + this.legDetails = legDetails; + this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; + this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; + this.averageSignedCrossTrackError = averageSignedCrossTrackError; + this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; + this.correctedTime = correctedTime; + this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + } + public List getLegDetails() { + return legDetails; + } + public Distance getWindwardDistanceToCompetitorFarthestAhead() { + return windwardDistanceToCompetitorFarthestAhead; + } + public Distance getAverageAbsoluteCrossTrackError() { + return averageAbsoluteCrossTrackError; + } + public Distance getAverageSignedCrossTrackError() { + return averageSignedCrossTrackError; + } + public Duration getGapToLeaderInOwnTime() { + return gapToLeaderInOwnTime; + } + public Duration getCorrectedTime() { + return correctedTime; + } + public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { + return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + } + } + + /** + * If timePoint is after the end of the race's tracking the query will be adjusted to obtain the values + * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this + * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, + * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} + * will be registered with the race which will be triggered for any event received by the race. + * @param waitForLatestAnalyses + * if false, this method is allowed to read the maneuver analysis results from a cache that + * may not reflect all data already received; otherwise, the method will always block for the latest + * cache updates to have happened before returning. + */ + private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { + final RaceDetails raceDetails; + if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { + raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); + } else { + raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); + } + return raceDetails; + } + + private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, + final Competitor competitor, final Map> legRanksCache, + RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { + final com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(trackedRace, competitor); + RunnableFuture raceDetails; + synchronized (raceDetailsAtEndOfTrackingCache) { + raceDetails = raceDetailsAtEndOfTrackingCache.get(key); + if (raceDetails == null) { + raceDetails = new FutureTask(new Callable() { + @Override + public RaceDetails call() throws Exception { + TimePoint end = trackedRace.getEndOfRace(); + if (end == null) { + end = trackedRace.getEndOfTracking(); + } + return calculateRaceDetails(trackedRace, competitor, end, + // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock + /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ + /* waitForLatestAnalyses (maneuver and cross track error) */ false, + legRanksCache, cache, rankingInfo); + } + }); + raceDetailsExecutor.execute(raceDetails); + raceDetailsAtEndOfTrackingCache.put(key, raceDetails); + final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); + trackedRace.addListener(cacheInvalidationListener); + cacheInvalidationListeners.add(cacheInvalidationListener); + } + } + return raceDetails.get(); + } + + private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { + final List legDetails = new ArrayList(); + final Course course = trackedRace.getRace().getCourse(); + course.lockForRead(); // hold back any course re-configurations while looping over the legs + try { + for (Leg leg : course.getLegs()) { + LegEntryDTO legEntry; + // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with + // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change + // immediately. That's why we've acquired a read lock for the course above. + TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { + legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); + } else { + legEntry = null; + } + legDetails.add(legEntry); + } + final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace + .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); + Distance averageAbsoluteCrossTrackError; + try { + averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( + competitor, timePoint, waitForLatestAnalyses, cache); + } catch (NoWindException nwe) { + // without wind information, use null meaning "unknown" + averageAbsoluteCrossTrackError = null; + } + Distance averageSignedCrossTrackError; + try { + averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( + competitor, timePoint, waitForLatestAnalyses, cache); + } catch (NoWindException nwe) { + // without wind information, use null meaning "unknown" + averageSignedCrossTrackError = null; + } + final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); + return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, + trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), + trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), + competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); + } finally { + course.unlockAfterRead(); + } + } + + private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { + LegEntryDTO result; + final Duration time = trackedLeg.getTime(timePoint); + if (trackedLeg == null || time == null) { + result = null; + } else { + result = new LegEntryDTO(); + try { + result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); + } catch (NoWindException nwe) { + result.legType = null; // can't determine leg type without wind data + } + final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); + result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); + Distance averageAbsoluteCrossTrackError; + try { + averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); + } catch (NoWindException nwe) { + // leave averageAbsoluteCrossTrackError as null, meaning "unknown" + averageAbsoluteCrossTrackError = null; + } + result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); + Distance averageSignedCrossTrackError; + try { + averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); + } catch (NoWindException nwe) { + // leave averageSignedCrossTrackError as null, meaning "unknown" + averageSignedCrossTrackError = null; + } + result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); + Double speedOverGroundInKnots; + if (trackedLeg.hasFinishedLeg(timePoint)) { + speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); + } else { + final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); + speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); + } + result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; + Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); + result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); + Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); + result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); + final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); + result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); + result.timeInMilliseconds = time.asMillis(); + result.finished = trackedLeg.hasFinishedLeg(timePoint); + final TimePoint legFinishTime = trackedLeg.getFinishTime(); + result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), + trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; + // fetch the leg gap in own corrected time from the ranking metric + final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). + getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); + result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); + if (result.gapToLeaderInSeconds != null) { + final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); + if (gapAtEndOfPreviousLeg != null) { + result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); + } + } + LinkedHashMap legRanks = legRanksCache.get(trackedLeg.getLeg()); + if (legRanks != null) { + result.rank = legRanks.get(trackedLeg.getCompetitor()); + } else { + result.rank = trackedLeg.getRank(timePoint, cache); + } + result.started = trackedLeg.hasStartedLeg(timePoint); + Speed velocityMadeGood; + if (trackedLeg.hasFinishedLeg(timePoint)) { + velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); + } else { + velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); + } + result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); + Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); + result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo + .getMeters(); + final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); + if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { + // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg + // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg + List maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() + .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); + if (maneuvers != null) { + result.numberOfManeuvers = new HashMap(); + result.numberOfManeuvers.put(ManeuverType.TACK, 0); + result.numberOfManeuvers.put(ManeuverType.JIBE, 0); + result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); + Map totalManeuverLossInMeters = new HashMap(); + totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); + totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); + totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); + TimePoint startOfLeg = trackedLeg.getStartTime(); + for (Maneuver maneuver : maneuvers) { + // don't count maneuvers that were in previous legs + switch (maneuver.getType()) { + case TACK: + case JIBE: + case PENALTY_CIRCLE: + if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || + maneuver.getTimePoint().before(legFinishTime))) { + if (maneuver.getManeuverLoss() != null) { + result.numberOfManeuvers.put(maneuver.getType(), + result.numberOfManeuvers.get(maneuver.getType()) + 1); + totalManeuverLossInMeters.put(maneuver.getType(), + totalManeuverLossInMeters.get(maneuver.getType()) + + maneuver.getManeuverLoss().getMeters()); + } + } + break; + case MARK_PASSING: + // analyze all mark passings, not only those after this leg's start, to catch the mark passing + // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg + MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; + if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { + result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); + } + break; + default: + /* Do nothing here. + * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. + */ + } + } + result.averageManeuverLossInMeters = new HashMap(); + for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, + ManeuverType.PENALTY_CIRCLE }) { + if (result.numberOfManeuvers.get(maneuverType) != 0) { + result.averageManeuverLossInMeters.put( + maneuverType, + totalManeuverLossInMeters.get(maneuverType) + / result.numberOfManeuvers.get(maneuverType)); + } + } + } + } + } + return result; + } + + private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { + final Duration result; + final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); + // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap + // at the end of the previous leg + final TimePoint timePoint = trackedLeg.getStartTime(); + final TrackedLegOfCompetitor tloc; + if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { + tloc = trackedLeg; + } else { + tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) + .getTrackedLeg(trackedLeg.getCompetitor()); + } + result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); + return result; + } + + private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { + LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, + baseDomainFactory); + return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); + } + + private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, + DomainFactory baseDomainFactory) { + LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; + if (result == null) { + synchronized (this) { + result = this.liveLeaderboardUpdater; + if (result == null) { + this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); + result = this.liveLeaderboardUpdater; + } + } + } + return result; + } + + private LeaderboardDTOCache getLeaderboardDTOCache() { + LeaderboardDTOCache result = this.leaderboardDTOCache; + if (result == null) { + synchronized (this) { + result = this.leaderboardDTOCache; + if (result == null) { + // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes + // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a + // single fix was updated is too expensive if users use the replay feature while a race is still running. + // Therefore, using waitForLatestAnalyses==false seems appropriate here. + this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); + result = this.leaderboardDTOCache; + } + } + } + return result; + } + + @Override + public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, + Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, + InterruptedException, ExecutionException { + LeaderboardDTO result = null; + if (timePoint == null) { + // timePoint==null means live mode; however, if we're after the end of all races and after all score + // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map + // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly + // when the tracked race associations or score corrections or tracked race contents changes: + final TimePoint nowMinusDelay = this.getNowMinusDelay(); + final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); + if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { + // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode + // and pull the result from the regular leaderboard cache: + timePoint = timePointOfLatestModification; + } else { + // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: + timePoint = null; + result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); + } + } + if (timePoint != null) { + if (fillNetPointsUncorrected) { + // explicitly filling the uncorrected net points requires uncached recalculation + result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, + trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); + } else { + // in replay we'd like up-to-date results; they are still cached + // which is OK because the cache is invalidated whenever any of the tracked races attached to the + // leaderboard changes. + result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, + trackedRegattaRegistry); + } + } + return result; + } + + public String toString() { + return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); + } + + @Override + public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { + return new NumberOfCompetitorsFetcherImpl(); + } + + protected abstract LeaderboardType getLeaderboardType(); + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java index 15e2db0a7cf..067c30d255d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java @@ -278,6 +278,10 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java index f9016dbef70..85f2cfa40fe 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java @@ -121,6 +121,11 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java index 92629e3aaca..c138fdf058b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java @@ -86,6 +86,12 @@ public class RaceColumnListeners implements Serializable { } } + public void notifyListenersAboutIsFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + for (RaceColumnListener listener : getRaceColumnListeners()) { + listener.isFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + } + public void notifyListenersAboutIsStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { for (RaceColumnListener listener : getRaceColumnListeners()) { listener.isStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java index 8809a8d1916..08a42409376 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java @@ -1,53 +1,53 @@ -package com.sap.sailing.gwt.ui.test; - -import java.util.Collections; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.HighPoint; -import com.sap.sailing.domain.ranking.RankingMetricsFactory; -import com.sap.sailing.gwt.ui.shared.MarkDTO; -import com.sap.sailing.server.RacingEventService; - -public class PingMarkViaRegattaLogTest { - private SailingServiceImplMock sailingService; - private RacingEventService service; - private final String columnName = "column"; - private final Fleet fleet = new FleetImpl("fleet"); - - @Before - public void prepare() { - sailingService = new SailingServiceImplMock(); - service = sailingService.getRacingEventService(); - } - - @Test - public void testPinging() throws DoesNotHaveRegattaLogException { - service.getMongoObjectFactory().getDatabase().dropDatabase(); - Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), - Collections.singletonList(columnName), service); - Regatta regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /*startDate*/ null, /*endDate*/ null, - UUID.randomUUID(), Collections.singletonList(series), - false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true, - RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); - RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); - - MarkDTO mark = new MarkDTO("mark", "mark"); - Position position = new DegreePosition(30, 40); - - sailingService.pingMark(leaderboard.getName(), mark, position); - } -} +package com.sap.sailing.gwt.ui.test; + +import java.util.Collections; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.ranking.RankingMetricsFactory; +import com.sap.sailing.gwt.ui.shared.MarkDTO; +import com.sap.sailing.server.RacingEventService; + +public class PingMarkViaRegattaLogTest { + private SailingServiceImplMock sailingService; + private RacingEventService service; + private final String columnName = "column"; + private final Fleet fleet = new FleetImpl("fleet"); + + @Before + public void prepare() { + sailingService = new SailingServiceImplMock(); + service = sailingService.getRacingEventService(); + } + + @Test + public void testPinging() throws DoesNotHaveRegattaLogException { + service.getMongoObjectFactory().getDatabase().dropDatabase(); + Series series = new SeriesImpl("series", false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(fleet), + Collections.singletonList(columnName), service); + Regatta regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /*startDate*/ null, /*endDate*/ null, + UUID.randomUUID(), Collections.singletonList(series), + false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true, + RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); + RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); + + MarkDTO mark = new MarkDTO("mark", "mark"); + Position position = new DegreePosition(30, 40); + + sailingService.pingMark(leaderboard.getName(), mark, position); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java index 628bb577a3c..0908b933709 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java @@ -58,7 +58,7 @@ public class CreateRegattaCallback implements DialogCallback{ LinkedHashMap seriesStructure = new LinkedHashMap(); for (SeriesDTO seriesDTO : newRegatta.series) { SeriesCreationParametersDTO seriesPair = new SeriesCreationParametersDTO(seriesDTO.getFleets(), - seriesDTO.isMedal(), seriesDTO.isStartsWithZeroScore(), + seriesDTO.isMedal(), seriesDTO.isFleetsCanRunInParallel(), seriesDTO.isStartsWithZeroScore(), seriesDTO.isFirstColumnIsNonDiscardableCarryForward(), seriesDTO.getDiscardThresholds(), seriesDTO.hasSplitFleetContiguousScoring()); seriesStructure.put(seriesDTO.getName(), seriesPair); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java index 7303c1479cc..18e088d6ddc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java @@ -287,6 +287,7 @@ public class RegattaDetailsComposite extends Composite { final SeriesDTO series = seriesDescriptor.getSeries(); final List newRaceColumns = seriesDescriptor.getRaces(); final boolean isMedalChanged = series.isMedal() != seriesDescriptor.isMedal(); + final boolean isFleetsCanRunInParallelChanged = series.isFleetsCanRunInParallel() != seriesDescriptor.isFleetsCanRunInParallel(); final boolean isStartsWithZeroScoreChanged = series.isStartsWithZeroScore() != seriesDescriptor.isStartsWithZeroScore(); final boolean isFirstColumnIsNonDiscardableCarryForwardChanged = series.isFirstColumnIsNonDiscardableCarryForward() != seriesDescriptor.isFirstColumnIsNonDiscardableCarryForward(); final boolean hasSplitFleetContiguousScoringChanged = series.hasSplitFleetContiguousScoring() != seriesDescriptor.hasSplitFleetContiguousScoring(); @@ -350,11 +351,11 @@ public class RegattaDetailsComposite extends Composite { }); } }); - if (isMedalChanged || seriesResultDiscardingThresholdsChanged || isStartsWithZeroScoreChanged + if (isMedalChanged || isFleetsCanRunInParallelChanged || seriesResultDiscardingThresholdsChanged || isStartsWithZeroScoreChanged || isFirstColumnIsNonDiscardableCarryForwardChanged || hasSplitFleetContiguousScoringChanged || seriesNameChanged) { sailingService.updateSeries(regattaIdentifier, series.getName(), seriesDescriptor.getSeriesName(), - seriesDescriptor.isMedal(), seriesDescriptor.getResultDiscardingThresholds(), + seriesDescriptor.isMedal(), seriesDescriptor.isFleetsCanRunInParallel(), seriesDescriptor.getResultDiscardingThresholds(), seriesDescriptor.isStartsWithZeroScore(), seriesDescriptor.isFirstColumnIsNonDiscardableCarryForward(), seriesDescriptor.hasSplitFleetContiguousScoring(), series.getFleets(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java index ba20696021a..1b2e45f1b51 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java @@ -309,7 +309,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer if (seriesIter.hasNext()) { final SeriesDTO series = seriesIter.next(); sailingService.updateSeries(regattaName, series.getName(), series.getName(), series.isMedal(), - series.getDiscardThresholds(), series.isStartsWithZeroScore(), + series.isFleetsCanRunInParallel(), series.getDiscardThresholds(), series.isStartsWithZeroScore(), series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring(), series.getFleets(), new MarkedAsyncCallback(new AsyncCallback() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java index 80bfa4ce806..8f2e0bf25f1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java @@ -1,69 +1,75 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.List; - -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.gwt.ui.shared.SeriesDTO; - -/** - * Used by {@link SeriesEditDialog} to capture the result of the user's configuration changes, - * including the race columns that the series shall define, the medal status and the discarding rules, if any. - * - * @author Axel Uhl (d043530) - * - */ -public class SeriesDescriptor { - private final SeriesDTO series; - private final List races; - private final String seriesName; - private final boolean isMedal; - private final boolean startsWithZeroScore; - private final boolean hasSplitFleetContiguousScoring; - private final boolean firstColumnIsNonDiscardableCarryForward; - private final int[] resultDiscardingThresholds; - - public SeriesDescriptor(SeriesDTO series, String seriesName, List races, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, - boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring) { - this.seriesName = seriesName; - this.series = series; - this.races = races; - this.isMedal = isMedal; - this.startsWithZeroScore = startsWithZeroScore; - this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; - this.resultDiscardingThresholds = resultDiscardingThresholds; - this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; - } - - public SeriesDTO getSeries() { - return series; - } - - public String getSeriesName() { - return seriesName; - } - - public List getRaces() { - return races; - } - - public boolean isMedal() { - return isMedal; - } - - public int[] getResultDiscardingThresholds() { - return resultDiscardingThresholds; - } - - public boolean isStartsWithZeroScore() { - return startsWithZeroScore; - } - - public boolean hasSplitFleetContiguousScoring() { - return hasSplitFleetContiguousScoring; - } - - public boolean isFirstColumnIsNonDiscardableCarryForward() { - return firstColumnIsNonDiscardableCarryForward; - } -} +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.shared.SeriesDTO; + +/** + * Used by {@link SeriesEditDialog} to capture the result of the user's configuration changes, + * including the race columns that the series shall define, the medal status and the discarding rules, if any. + * + * @author Axel Uhl (d043530) + * + */ +public class SeriesDescriptor { + private final SeriesDTO series; + private final List races; + private final String seriesName; + private final boolean isMedal; + private final boolean isFleetsCanRunInParallel; + private final boolean startsWithZeroScore; + private final boolean hasSplitFleetContiguousScoring; + private final boolean firstColumnIsNonDiscardableCarryForward; + private final int[] resultDiscardingThresholds; + + public SeriesDescriptor(SeriesDTO series, String seriesName, List races, boolean isMedal, boolean isFleetsCanRunInParallel, + int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring) { + this.seriesName = seriesName; + this.series = series; + this.races = races; + this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + this.startsWithZeroScore = startsWithZeroScore; + this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; + this.resultDiscardingThresholds = resultDiscardingThresholds; + this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; + } + + public SeriesDTO getSeries() { + return series; + } + + public String getSeriesName() { + return seriesName; + } + + public List getRaces() { + return races; + } + + public boolean isMedal() { + return isMedal; + } + + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + public int[] getResultDiscardingThresholds() { + return resultDiscardingThresholds; + } + + public boolean isStartsWithZeroScore() { + return startsWithZeroScore; + } + + public boolean hasSplitFleetContiguousScoring() { + return hasSplitFleetContiguousScoring; + } + + public boolean isFirstColumnIsNonDiscardableCarryForward() { + return firstColumnIsNonDiscardableCarryForward; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 5e5a0d1952c..aba2040308e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -39,6 +39,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class SeriesEditDialog extends DataEntryDialog { private TextBox seriesNameTextBox; private CheckBox isMedalCheckbox; + private CheckBox fleetsCanRunInParallelCheckbox; private CheckBox startWithZeroScoreCheckbox; private CheckBox hasSplitFleetContiguousScoringCheckbox; private CheckBox firstColumnIsNonDiscardableCarryForwardCheckbox; @@ -133,6 +134,7 @@ public class SeriesEditDialog extends DataEntryDialog { races.add(raceColumnDTO); } return new SeriesDescriptor(selectedSeries, seriesNameTextBox.getValue(), races, isMedalCheckbox.getValue(), + fleetsCanRunInParallelCheckbox.getValue(), useSeriesResultDiscardingThresholdsCheckbox.getValue() ? discardThresholdBoxes.getDiscardThresholds() : null, startWithZeroScoreCheckbox.getValue(), firstColumnIsNonDiscardableCarryForwardCheckbox.getValue(), hasSplitFleetContiguousScoringCheckbox.getValue()); @@ -171,7 +173,12 @@ public class SeriesEditDialog extends DataEntryDialog { isMedalCheckbox.ensureDebugId("MedalSeriesCheckbox"); isMedalCheckbox.setValue(selectedSeries.isMedal()); additionalWidgetPanel.add(isMedalCheckbox); - + + fleetsCanRunInParallelCheckbox = createCheckbox(stringMessages.canFleetsRunInParallel()); + fleetsCanRunInParallelCheckbox.ensureDebugId("FleetsCanRaceInParallelSeriesCheckbox"); + fleetsCanRunInParallelCheckbox.setValue(selectedSeries.isFleetsCanRunInParallel()); + additionalWidgetPanel.add(fleetsCanRunInParallelCheckbox); + startWithZeroScoreCheckbox = createCheckbox(stringMessages.startsWithZeroScore()); startWithZeroScoreCheckbox.ensureDebugId("StartsWithZeroScoreCheckbox"); startWithZeroScoreCheckbox.setValue(selectedSeries.isStartsWithZeroScore()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 923b0224751..9843ffe661f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -227,7 +227,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List> columnNames); void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore, boolean firstRaceIsNonDiscardableCarryForward, boolean hasSplitFleetScore, List fleets); RaceColumnInSeriesDTO addRaceColumnToSeries(RegattaIdentifier regattaIdentifier, String seriesName, String columnName); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 65fb4725221..1b4d9aad5a1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -443,7 +443,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan List> columnNames, AsyncCallback> callback); void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore, boolean firstRaceIsNonDiscardableCarryForward, boolean hasSplitFleetScore, List fleets, AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 7542b21290d..fc6a57e698a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -346,6 +346,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String splitFleet(); String color(); String medalSeries(); + String canFleetsRunInParallel(); String noColor(); String pleaseSelectARegatta(); String addFleet(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 6d693b221cb..0696d841995 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -358,6 +358,7 @@ series=Series splitFleet=Split fleet color=Color medalSeries=Medal Series +canFleetsRunInParallel=Fleets can run in parallel noColor=No color pleaseSelectARegatta=Please select a regatta. addFleet=Add fleet diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 80977349b2c..e9a77874f5a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -359,6 +359,7 @@ fleets=Flotten splitFleet=Aufteilung in Gruppen color=Farbe medalSeries=Medal Series +canFleetsRunInParallel=Flotten können parallel segeln noColor=Keine Farbe pleaseSelectARegatta=Bitte wählen Sie eine Regatta aus. addFleet=Gruppe hinzufügen diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 936efd1563b..03c2da16d74 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -356,6 +356,7 @@ series=Серия splitFleet=Разделить флот color=Цвет medalSeries=Серия на медали +canFleetsRunInParallel=Fleets can run in parallel noColor=Без цвета pleaseSelectARegatta=Выберите регату. addFleet=Добавить флот diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 3470236cd97..2eb21b55251 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -356,6 +356,7 @@ series=系列 splitFleet=船队区分 color=颜色 medalSeries=奖牌轮系列 +canFleetsRunInParallel=Fleets can run in parallel noColor=没有颜色 pleaseSelectARegatta=请选择比赛。 addFleet=添加船队 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties index 8965d3606a4..3daacf366e9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties @@ -356,6 +356,7 @@ series=系列 splitFleet=船队区分 color=颜色 medalSeries=奖牌轮系列 +canFleetsRunInParallel=Fleets can run in parallel noColor=没有颜色 pleaseSelectARegatta=请选择比赛。 addFleet=添加船队 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 4f1bf6e465b..610f4305165 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -846,7 +846,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S fleets.add(baseDomainFactory.convertToFleetDTO(fleet)); } List raceColumns = convertToRaceColumnDTOs(series.getRaceColumns()); - SeriesDTO result = new SeriesDTO(series.getName(), fleets, raceColumns, series.isMedal(), + SeriesDTO result = new SeriesDTO(series.getName(), fleets, raceColumns, series.isMedal(), series.isFleetsCanRunInParallel(), series.getResultDiscardingRule() == null ? null : series.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), series.isStartsWithZeroScore(), series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring()); return result; @@ -2803,7 +2803,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S /* startDate*/ null, /*endDate*/ null, RegattaImpl.getDefaultName(replayRaceDTO.rsc, replayRaceDTO.boat_class), Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, - /* isMedal */false, Collections.singletonList(new FleetImpl( + /* isMedal */false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(new FleetImpl( LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList(), getService())), false, baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, @@ -3917,7 +3917,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, + public void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, List fleets) { @@ -3926,7 +3926,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S SecurityUtils.getSubject().checkPermission(Permission.REGATTA.getStringPermissionForObjects(Mode.UPDATE, regatta.getName())); } getService().apply( - new UpdateSeries(regattaIdentifier, seriesName, newSeriesName, isMedal, resultDiscardingThresholds, + new UpdateSeries(regattaIdentifier, seriesName, newSeriesName, isMedal, isFleetsCanRunInParallel, resultDiscardingThresholds, startsWithZeroScore, firstColumnIsNonDiscardableCarryForward, hasSplitFleetContiguousScoring, fleets)); } @@ -4257,7 +4257,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S for (SeriesDTO series : regattaDTO.series){ SeriesParameters seriesParameters = getSeriesParameters(series); seriesCreationParams.put(series.getName(), new SeriesCreationParametersDTO(series.getFleets(), - false, seriesParameters.isStartswithZeroScore(), seriesParameters.isFirstColumnIsNonDiscardableCarryForward(), + false, true, seriesParameters.isStartswithZeroScore(), seriesParameters.isFirstColumnIsNonDiscardableCarryForward(), seriesParameters.getDiscardingThresholds(), seriesParameters.isHasSplitFleetContiguousScoring())); } return seriesCreationParams; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java index 39125aa0e75..caf3aa7467b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java @@ -12,6 +12,7 @@ public class SeriesDTO extends NamedDTO { private List fleets; private List raceColumns; private boolean isMedal; + private boolean isFleetsCanRunInParallel; private int[] discardThresholds; private Boolean startsWithZeroScore; private boolean firstColumnIsNonDiscardableCarryForward; @@ -19,13 +20,14 @@ public class SeriesDTO extends NamedDTO { public SeriesDTO() {} - public SeriesDTO(String name, List fleets, List raceColumns, boolean isMedal, + public SeriesDTO(String name, List fleets, List raceColumns, boolean isMedal, boolean isFleetsCanRunInParallel, int[] discardThresholds, boolean startsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring) { super(name); this.fleets = fleets; this.raceColumns = raceColumns; this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; this.startsWithZeroScore = startsWithZeroScore; this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; this.discardThresholds = discardThresholds; @@ -40,7 +42,7 @@ public class SeriesDTO extends NamedDTO { public SeriesDTO(SeriesDTO otherSeries) { this(otherSeries.getName(), otherSeries.getFleets(), otherSeries.getRaceColumns() == null ? null : new ArrayList(otherSeries.getRaceColumns()), - otherSeries.isMedal(), otherSeries.getDiscardThresholds(), otherSeries.isStartsWithZeroScore(), + otherSeries.isMedal(), otherSeries.isFleetsCanRunInParallel(), otherSeries.getDiscardThresholds(), otherSeries.isStartsWithZeroScore(), otherSeries.isFirstColumnIsNonDiscardableCarryForward(), otherSeries.hasSplitFleetContiguousScoring()); } @@ -48,10 +50,6 @@ public class SeriesDTO extends NamedDTO { return hasSplitFleetContiguousScoring; } - public boolean isMedal() { - return isMedal; - } - public List getFleets() { return fleets; } @@ -60,10 +58,22 @@ public class SeriesDTO extends NamedDTO { this.fleets = fleets; } + public boolean isMedal() { + return isMedal; + } + public void setMedal(boolean isMedal) { this.isMedal = isMedal; } + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + public void setFleetsCanRunInParallel(boolean isFleetsCanRunInParallel) { + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + } + public List getRaceColumns() { return raceColumns; } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java index ac1099c184c..f018f43c642 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java @@ -1,81 +1,81 @@ -package com.sap.sailing.mongodb.test; - -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.Before; - -import com.mongodb.MongoException; -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.persistence.DomainObjectFactory; -import com.sap.sailing.domain.persistence.MongoObjectFactory; -import com.sap.sailing.domain.persistence.PersistenceFactory; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public abstract class AbstractTestStoringAndRetrievingRaceLogInRegatta extends RaceLogMongoDBTest { - - protected final String raceColumnName = "My.First$Race$1"; - protected final String regattaName = "TestRegatta"; - protected final String yellowFleetName = "Yellow"; - protected final String seriesName = "Qualifying"; - protected MongoObjectFactory mongoObjectFactory = null; - protected DomainObjectFactory domainObjectFactory = null; - protected Regatta regatta = null; - protected AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); - - public AbstractTestStoringAndRetrievingRaceLogInRegatta() throws UnknownHostException, MongoException { - super(); - } - - @Before - public void setUp() { - now = MillisecondsTimePoint.now(); - mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); - domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); - regatta = createRegattaAndAddRaceColumns(1, regattaName, boatClass, true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); - } - - private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final String regattaBaseName, BoatClass boatClass, boolean persistent, ScoringScheme scoringScheme) { - Regatta regatta = createRegattaWithoutRaceColumns(regattaBaseName, boatClass, persistent, scoringScheme, null); - regatta.getSeriesByName(seriesName).addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null); - return regatta; - } - - private Regatta createRegattaWithoutRaceColumns(final String regattaBaseName, BoatClass boatClass, - boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea) { - List series = createSeriesForTestRegatta(); - Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), RegattaImpl.getDefaultName(regattaBaseName, - boatClass == null ? null : boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, series, persistent, scoringScheme, "123", - courseArea, /* useStartTimeInference */true, OneDesignRankingMetric::new); - return regatta; - } - - private List createSeriesForTestRegatta() { - List series = new ArrayList(); - // -------- qualifying series ------------ - List emptyRaceColumnNames = Collections.emptyList(); - List qualifyingFleets = createQualifyingFleets(); - Series qualifyingSeries = new SeriesImpl(seriesName, /* isMedal */false, qualifyingFleets, - emptyRaceColumnNames, /* trackedRegattaRegistry */ null); - series.add(qualifyingSeries); - return series; - } - - abstract protected List createQualifyingFleets(); -} +package com.sap.sailing.mongodb.test; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; + +import com.mongodb.MongoException; +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.PersistenceFactory; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public abstract class AbstractTestStoringAndRetrievingRaceLogInRegatta extends RaceLogMongoDBTest { + + protected final String raceColumnName = "My.First$Race$1"; + protected final String regattaName = "TestRegatta"; + protected final String yellowFleetName = "Yellow"; + protected final String seriesName = "Qualifying"; + protected MongoObjectFactory mongoObjectFactory = null; + protected DomainObjectFactory domainObjectFactory = null; + protected Regatta regatta = null; + protected AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); + + public AbstractTestStoringAndRetrievingRaceLogInRegatta() throws UnknownHostException, MongoException { + super(); + } + + @Before + public void setUp() { + now = MillisecondsTimePoint.now(); + mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); + regatta = createRegattaAndAddRaceColumns(1, regattaName, boatClass, true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); + } + + private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final String regattaBaseName, BoatClass boatClass, boolean persistent, ScoringScheme scoringScheme) { + Regatta regatta = createRegattaWithoutRaceColumns(regattaBaseName, boatClass, persistent, scoringScheme, null); + regatta.getSeriesByName(seriesName).addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null); + return regatta; + } + + private Regatta createRegattaWithoutRaceColumns(final String regattaBaseName, BoatClass boatClass, + boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea) { + List series = createSeriesForTestRegatta(); + Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), RegattaImpl.getDefaultName(regattaBaseName, + boatClass == null ? null : boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, series, persistent, scoringScheme, "123", + courseArea, /* useStartTimeInference */true, OneDesignRankingMetric::new); + return regatta; + } + + private List createSeriesForTestRegatta() { + List series = new ArrayList(); + // -------- qualifying series ------------ + List emptyRaceColumnNames = Collections.emptyList(); + List qualifyingFleets = createQualifyingFleets(); + Series qualifyingSeries = new SeriesImpl(seriesName, /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, + emptyRaceColumnNames, /* trackedRegattaRegistry */ null); + series.add(qualifyingSeries); + return series; + } + + abstract protected List createQualifyingFleets(); +} diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 197c9e784a6..6b7bb6bf1ec 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -637,6 +637,28 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest assertTrue(loadedRegatta.getSeriesByName("Medal").isStartsWithZeroScore()); } + @Test + public void testLoadStoreRegattaWithSeriesWhereFleetsCanRunInParallel() { + final int numberOfQualifyingRaces = 5; + final int numberOfFinalRaces = 7; + final String regattaBaseName = "Kieler Woche"; + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); + regatta.getSeriesByName("Qualifying").setIsFleetsCanRunInParallel(true); + regatta.getSeriesByName("Final").setIsFleetsCanRunInParallel(false); + regatta.getSeriesByName("Medal").setIsFleetsCanRunInParallel(false); + + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + mof.storeRegatta(regatta); + + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); + assertTrue(loadedRegatta.getSeriesByName("Qualifying").isFleetsCanRunInParallel()); + assertFalse(loadedRegatta.getSeriesByName("Final").isFleetsCanRunInParallel()); + assertFalse(loadedRegatta.getSeriesByName("Medal").isFleetsCanRunInParallel()); + } + @Test public void testLoadStoreSimpleRegattaWithHighPointScoringScheme() { final int numberOfQualifyingRaces = 5; @@ -787,7 +809,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest List qualifyingFleets = new ArrayList(); qualifyingFleets.add(new FleetImpl("Yellow")); qualifyingFleets.add(new FleetImpl("Blue")); - Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets, + Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(qualifyingSeries); @@ -795,13 +817,13 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest List finalFleets = new ArrayList(); finalFleets.add(new FleetImpl("Gold", 1)); finalFleets.add(new FleetImpl("Silver", 2)); - Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(finalSeries); // ------------ medal -------------- List medalFleets = new ArrayList(); medalFleets.add(new FleetImpl("Medal")); - Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); + Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(medalSeries); Regatta regatta = new RegattaImpl(regattaName, boatClass, startDate, endDate, series, persistent, scoringScheme, "123", courseArea, rankingMetricConstructor); return regatta; diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java index ab8078a91b9..2663a47fc32 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java @@ -1,37 +1,38 @@ -package com.sap.sailing.server.gateway.deserialization.racegroup.impl; - -import java.util.ArrayList; -import java.util.Collection; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.racegroup.RaceRow; -import com.sap.sailing.domain.base.racegroup.SeriesWithRows; -import com.sap.sailing.domain.base.racegroup.impl.SeriesWithRowsImpl; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.Helpers; -import com.sap.sailing.server.gateway.serialization.racegroup.impl.RaceRowsOfSeriesWithRowsSerializer; - -public class SeriesWithRowsDeserializer implements JsonDeserializer { - - private JsonDeserializer raceRowDeserializer; - - public SeriesWithRowsDeserializer(JsonDeserializer raceRowDeserializer) { - this.raceRowDeserializer = raceRowDeserializer; - } - - public SeriesWithRows deserialize(JSONObject object) throws JsonDeserializationException { - String name = object.get("name").toString(); - boolean isMedal = (Boolean) object.get("isMedal"); - - Collection rows = new ArrayList(); - for (Object fleetObject : Helpers.getNestedArraySafe(object, RaceRowsOfSeriesWithRowsSerializer.FIELD_FLEETS)) { - JSONObject fleetJson = Helpers.toJSONObjectSafe(fleetObject); - rows.add(raceRowDeserializer.deserialize(fleetJson)); - } - - return new SeriesWithRowsImpl(name, isMedal, rows); - } - +package com.sap.sailing.server.gateway.deserialization.racegroup.impl; + +import java.util.ArrayList; +import java.util.Collection; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.racegroup.RaceRow; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; +import com.sap.sailing.domain.base.racegroup.impl.SeriesWithRowsImpl; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.Helpers; +import com.sap.sailing.server.gateway.serialization.racegroup.impl.RaceRowsOfSeriesWithRowsSerializer; + +public class SeriesWithRowsDeserializer implements JsonDeserializer { + + private JsonDeserializer raceRowDeserializer; + + public SeriesWithRowsDeserializer(JsonDeserializer raceRowDeserializer) { + this.raceRowDeserializer = raceRowDeserializer; + } + + public SeriesWithRows deserialize(JSONObject object) throws JsonDeserializationException { + String name = object.get("name").toString(); + boolean isMedal = (Boolean) object.get("isMedal"); + boolean isFleetsCanRunInParallel = (Boolean) object.get("isFleetsCanRunInParallel"); + + Collection rows = new ArrayList(); + for (Object fleetObject : Helpers.getNestedArraySafe(object, RaceRowsOfSeriesWithRowsSerializer.FIELD_FLEETS)) { + JSONObject fleetJson = Helpers.toJSONObjectSafe(fleetObject); + rows.add(raceRowDeserializer.deserialize(fleetJson)); + } + + return new SeriesWithRowsImpl(name, isMedal, isFleetsCanRunInParallel, rows); + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java index c2f63c2009c..d35ba2bc03f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java @@ -1,92 +1,92 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import javax.ws.rs.core.Response; - -import org.json.simple.JSONArray; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest { - private Regatta regatta; - private LeaderboardGroup leaderboardGroup; - private String boatClassName = "49er"; - private String regattaName = "TestRegatta"; - private String leaderboardGroupName = "TestLeaderboardGroup"; - - @Before - public void setUp() { - super.setUp(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, - raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - List competitors = createCompetitors(4); - List fleet1Competitors = competitors.subList(0, 2); - List fleet2Competitors = competitors.subList(2, 4); - - TimePoint now = MillisecondsTimePoint.now(); - - RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); - TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors); - TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); - - racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); - - List leaderboardNames = new ArrayList(); - leaderboardNames.add(regatta.getName()); - leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), leaderboardGroupName, "description", - "The LG displayName", /* displayGroupsInReverseOrder */ false, - leaderboardNames, /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); - } - - @Test - public void testExportLeaderboardGroupsAsJson() throws Exception { - LeaderboardGroupsResource resource = new LeaderboardGroupsResource(); - LeaderboardGroupsResource spyResource = spyResource(resource); - Response leaderboardGroupsResponse = spyResource.getLeaderboardGroups(); - - String jsonString = (String) leaderboardGroupsResponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONArray array= (JSONArray) obj; - - assertTrue(array.size() == 1); - String jsonFirstLeaderboardGroup = (String) array.get(0); - assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup)); - } -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.ws.rs.core.Response; + +import org.json.simple.JSONArray; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest { + private Regatta regatta; + private LeaderboardGroup leaderboardGroup; + private String boatClassName = "49er"; + private String regattaName = "TestRegatta"; + private String leaderboardGroupName = "TestLeaderboardGroup"; + + @Before + public void setUp() { + super.setUp(); + List series = new ArrayList(); + List fleets = new ArrayList(); + List raceColumnNames = new ArrayList(); + fleets.add(new FleetImpl("Fleet1")); + fleets.add(new FleetImpl("Fleet2")); + Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, + raceColumnNames, /* trackedRegattaRegistry */null); + series.add(testSeries); + regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); + testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + List competitors = createCompetitors(4); + List fleet1Competitors = competitors.subList(0, 2); + List fleet2Competitors = competitors.subList(2, 4); + + TimePoint now = MillisecondsTimePoint.now(); + + RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); + TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors); + TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); + + racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); + + List leaderboardNames = new ArrayList(); + leaderboardNames.add(regatta.getName()); + leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), leaderboardGroupName, "description", + "The LG displayName", /* displayGroupsInReverseOrder */ false, + leaderboardNames, /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); + } + + @Test + public void testExportLeaderboardGroupsAsJson() throws Exception { + LeaderboardGroupsResource resource = new LeaderboardGroupsResource(); + LeaderboardGroupsResource spyResource = spyResource(resource); + Response leaderboardGroupsResponse = spyResource.getLeaderboardGroups(); + + String jsonString = (String) leaderboardGroupsResponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONArray array= (JSONArray) obj; + + assertTrue(array.size() == 1); + String jsonFirstLeaderboardGroup = (String) array.get(0); + assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup)); + } +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java index 23f3daff110..28178d52f9d 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java @@ -49,7 +49,7 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest c.getColor(), c.getEmail(), c.getFlagImage(), (DynamicTeam) c.getTeam(), (DynamicBoat) c.getBoat(), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), - MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections + MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, /* isFleetsCanRunInParallel */ true, Collections .singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false, new HighPoint(), 0, null, OneDesignRankingMetric::new); racingEventService.addRegattaWithoutReplication(regatta); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java index 6268ea97ab3..dc33adf1a2d 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java @@ -1,105 +1,105 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.doReturn; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.ws.rs.core.Response; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.HighPoint; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceMapping; -import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class LeaderboardsResourcePingMarkTest extends AbstractJaxRsApiTest { - private Mark mark; - private RegattaLog log; - private RegattaLeaderboard leaderboard; - - public static final String PING_MARK_JSON = - " {\n" + - " \"timestamp\" : 1427142552000,\n" + - " \"latitude\" : 54.325246,\n" + - " \"longitude\" : 10.148556,\n" + - " \"speed\" : 3.61,\n" + - " \"course\" : 258.11,\n" + - " }"; - - public static final String PING2_MARK_JSON = - " {\n" + - " \"timestamp\" : 1427142562000,\n" + - " \"latitude\" : 54.425246,\n" + - " \"longitude\" : 10.248556,\n" + - " \"speed\" : 3.61,\n" + - " \"course\" : 258.11,\n" + - " }"; - - @Before - public void setUp() { - super.setUp(); - - mark = racingEventService.getBaseDomainFactory().getOrCreateMark("id", "name"); - Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), - MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections - .singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false, - new HighPoint(), 0, null, OneDesignRankingMetric::new); - racingEventService.addRegattaWithoutReplication(regatta); - leaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "regatta", new int[] {}); - log = leaderboard.getRegattaLike().getRegattaLog(); - } - - @Test - public void testCheckinAndCheckout() throws Exception { - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - doReturn(RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(racingEventService.getBaseDomainFactory())).when(resource).getRaceLogTrackingAdapter(); - { - Response response = resource.pingMark(PING_MARK_JSON, leaderboard.getName(), mark.getId().toString()); - assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); - Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); - List> mappingsForMark = mappings.get(mark); - assertThat("one mapping was created for the one ping", mappingsForMark.size(), equalTo(1)); - assertOneFixPerMapping(mappingsForMark); - } - { - // now produce a second ping; this should produce also only one fix; no additional artificial fixes are - // created (anymore; they used to be before bug2851) - Response response = resource.pingMark(PING2_MARK_JSON, leaderboard.getName(), mark.getId().toString()); - assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); - Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); - List> mappingsForMark = mappings.get(mark); - assertThat("Assert no additional mapping was created for the second ping", mappingsForMark.size(), equalTo(2)); - assertOneFixPerMapping(mappingsForMark); - } - } - - private void assertOneFixPerMapping(List> mappingsForMark) throws TransformationException, - NoCorrespondingServiceRegisteredException { - for (DeviceMapping i : mappingsForMark) { - DeviceIdentifier device = i.getDevice(); - // first ping produces exactly one fix: - assertThat("all fixes stored", racingEventService.getGPSFixStore().getNumberOfFixes(device), equalTo(1L)); - } - } -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.doReturn; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.racelogtracking.DeviceMapping; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class LeaderboardsResourcePingMarkTest extends AbstractJaxRsApiTest { + private Mark mark; + private RegattaLog log; + private RegattaLeaderboard leaderboard; + + public static final String PING_MARK_JSON = + " {\n" + + " \"timestamp\" : 1427142552000,\n" + + " \"latitude\" : 54.325246,\n" + + " \"longitude\" : 10.148556,\n" + + " \"speed\" : 3.61,\n" + + " \"course\" : 258.11,\n" + + " }"; + + public static final String PING2_MARK_JSON = + " {\n" + + " \"timestamp\" : 1427142562000,\n" + + " \"latitude\" : 54.425246,\n" + + " \"longitude\" : 10.248556,\n" + + " \"speed\" : 3.61,\n" + + " \"course\" : 258.11,\n" + + " }"; + + @Before + public void setUp() { + super.setUp(); + + mark = racingEventService.getBaseDomainFactory().getOrCreateMark("id", "name"); + Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), + MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, /* isFleetsCanRunInParallel */ true, Collections + .singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false, + new HighPoint(), 0, null, OneDesignRankingMetric::new); + racingEventService.addRegattaWithoutReplication(regatta); + leaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "regatta", new int[] {}); + log = leaderboard.getRegattaLike().getRegattaLog(); + } + + @Test + public void testCheckinAndCheckout() throws Exception { + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + doReturn(RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(racingEventService.getBaseDomainFactory())).when(resource).getRaceLogTrackingAdapter(); + { + Response response = resource.pingMark(PING_MARK_JSON, leaderboard.getName(), mark.getId().toString()); + assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); + Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); + List> mappingsForMark = mappings.get(mark); + assertThat("one mapping was created for the one ping", mappingsForMark.size(), equalTo(1)); + assertOneFixPerMapping(mappingsForMark); + } + { + // now produce a second ping; this should produce also only one fix; no additional artificial fixes are + // created (anymore; they used to be before bug2851) + Response response = resource.pingMark(PING2_MARK_JSON, leaderboard.getName(), mark.getId().toString()); + assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); + Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); + List> mappingsForMark = mappings.get(mark); + assertThat("Assert no additional mapping was created for the second ping", mappingsForMark.size(), equalTo(2)); + assertOneFixPerMapping(mappingsForMark); + } + } + + private void assertOneFixPerMapping(List> mappingsForMark) throws TransformationException, + NoCorrespondingServiceRegisteredException { + for (DeviceMapping i : mappingsForMark) { + DeviceIdentifier device = i.getDevice(); + // first ping produces exactly one fix: + assertThat("all fixes stored", racingEventService.getGPSFixStore().getNumberOfFixes(device), equalTo(1L)); + } + } +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java index 509dbc22b1f..574c5a5bbfc 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java @@ -1,266 +1,266 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import javax.ws.rs.core.Response; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class LeaderboardsResourceTest extends AbstractJaxRsApiTest { - private Regatta regatta; - private RegattaLeaderboard regattaLeaderboard; - private String boatClassName = "49er"; - private String regattaBaseName = "TestRegatta"; - - @Before - public void setUp() { - super.setUp(); - } - - private void dropAndCreateRegatta() { - service.getDB().dropDatabase(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, - raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClassName), boatClassName, - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - List competitors = createCompetitors(4); - List fleet1Competitors = competitors.subList(0, 2); - List fleet2Competitors = competitors.subList(2, 4); - - TimePoint now = MillisecondsTimePoint.now(); - - RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); - TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors, regatta); - TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors, regatta); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); - - regattaLeaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); - - } - - @Test - public void testExportEmptyLeaderboardAsJson() throws Exception { - dropAndCreateRegatta(); - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - // resultTimepoint should be null if there are no 'final' results yet - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNull(resultTimePoint); - - JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertTrue(jsonCompetitors.size() == 4); - } - - @Test - public void testExportLeaderboardWithFinalResultStateAsJson() throws Exception { - dropAndCreateRegatta(); - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNull(resultTimePoint); - - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - - LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString2 = (String) leaderboardReponse2.getEntity(); - obj= JSONValue.parse(jsonString2); - jsonObject = (JSONObject) obj; - - resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint); - } - - @Test - public void testExportLeaderboardWithLiveResultStateAsJson() throws Exception { - dropAndCreateRegatta(); - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Live, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Live.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint); - - JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertTrue(jsonCompetitors.size() == 4); - } - - @Test - public void testSmartFutureCacheInExportLeaderboardAsJson() throws Exception { - dropAndCreateRegatta(); - - TimePoint now = MillisecondsTimePoint.now(); - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); - - Thread.sleep(100); - - // call the servlet for the first time - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint); - - // second call - it's expected to get the same result from the cache - Thread.sleep(100); - - LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString2 = (String) leaderboardReponse2.getEntity(); - Object obj2= JSONValue.parse(jsonString2); - JSONObject jsonObject2 = (JSONObject) obj2; - - String jsonLeaderboardName2 = (String) jsonObject2.get("name"); - String jsonResultState2 = (String) jsonObject2.get("resultState"); - - assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2)); - assertTrue(jsonResultState.equals(jsonResultState2)); - - TimePoint resultTimePoint2 = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint2); - assertTrue(resultTimePoint.equals(resultTimePoint2)); - } - - @Test - public void testExportLeaderboardWithMaxCompetitorsCountAsJson() throws Exception { - dropAndCreateRegatta(); - Integer maxCompetitorsCount = 2; - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNull(resultTimePoint); - - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - - // first call for 'all' competitors - LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - jsonString = (String) leaderboardReponse2.getEntity(); - obj= JSONValue.parse(jsonString); - jsonObject = (JSONObject) obj; - JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertEquals(jsonCompetitors.size(), 4); - - Thread.sleep(100); - - // second call with maxCompetitorsCount set - LeaderboardsResource resource3 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse3 = resource3.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, maxCompetitorsCount); - - jsonString = (String) leaderboardReponse3.getEntity(); - obj= JSONValue.parse(jsonString); - jsonObject = (JSONObject) obj; - jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertEquals((Integer) jsonCompetitors.size(), maxCompetitorsCount); - - Thread.sleep(100); - - // third call for 'all' competitors - LeaderboardsResource resource4 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse4 = resource4.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - jsonString = (String) leaderboardReponse4.getEntity(); - obj= JSONValue.parse(jsonString); - jsonObject = (JSONObject) obj; - jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertEquals(jsonCompetitors.size(), 4); - } -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.ws.rs.core.Response; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class LeaderboardsResourceTest extends AbstractJaxRsApiTest { + private Regatta regatta; + private RegattaLeaderboard regattaLeaderboard; + private String boatClassName = "49er"; + private String regattaBaseName = "TestRegatta"; + + @Before + public void setUp() { + super.setUp(); + } + + private void dropAndCreateRegatta() { + service.getDB().dropDatabase(); + List series = new ArrayList(); + List fleets = new ArrayList(); + List raceColumnNames = new ArrayList(); + fleets.add(new FleetImpl("Fleet1")); + fleets.add(new FleetImpl("Fleet2")); + Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, + raceColumnNames, /* trackedRegattaRegistry */null); + series.add(testSeries); + regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClassName), boatClassName, + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); + testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + List competitors = createCompetitors(4); + List fleet1Competitors = competitors.subList(0, 2); + List fleet2Competitors = competitors.subList(2, 4); + + TimePoint now = MillisecondsTimePoint.now(); + + RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); + TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors, regatta); + TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors, regatta); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); + + regattaLeaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); + + } + + @Test + public void testExportEmptyLeaderboardAsJson() throws Exception { + dropAndCreateRegatta(); + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + // resultTimepoint should be null if there are no 'final' results yet + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNull(resultTimePoint); + + JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertTrue(jsonCompetitors.size() == 4); + } + + @Test + public void testExportLeaderboardWithFinalResultStateAsJson() throws Exception { + dropAndCreateRegatta(); + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNull(resultTimePoint); + + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); + + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString2 = (String) leaderboardReponse2.getEntity(); + obj= JSONValue.parse(jsonString2); + jsonObject = (JSONObject) obj; + + resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint); + } + + @Test + public void testExportLeaderboardWithLiveResultStateAsJson() throws Exception { + dropAndCreateRegatta(); + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Live, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Live.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint); + + JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertTrue(jsonCompetitors.size() == 4); + } + + @Test + public void testSmartFutureCacheInExportLeaderboardAsJson() throws Exception { + dropAndCreateRegatta(); + + TimePoint now = MillisecondsTimePoint.now(); + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); + + Thread.sleep(100); + + // call the servlet for the first time + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint); + + // second call - it's expected to get the same result from the cache + Thread.sleep(100); + + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString2 = (String) leaderboardReponse2.getEntity(); + Object obj2= JSONValue.parse(jsonString2); + JSONObject jsonObject2 = (JSONObject) obj2; + + String jsonLeaderboardName2 = (String) jsonObject2.get("name"); + String jsonResultState2 = (String) jsonObject2.get("resultState"); + + assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2)); + assertTrue(jsonResultState.equals(jsonResultState2)); + + TimePoint resultTimePoint2 = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint2); + assertTrue(resultTimePoint.equals(resultTimePoint2)); + } + + @Test + public void testExportLeaderboardWithMaxCompetitorsCountAsJson() throws Exception { + dropAndCreateRegatta(); + Integer maxCompetitorsCount = 2; + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNull(resultTimePoint); + + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); + + // first call for 'all' competitors + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + jsonString = (String) leaderboardReponse2.getEntity(); + obj= JSONValue.parse(jsonString); + jsonObject = (JSONObject) obj; + JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertEquals(jsonCompetitors.size(), 4); + + Thread.sleep(100); + + // second call with maxCompetitorsCount set + LeaderboardsResource resource3 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse3 = resource3.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, maxCompetitorsCount); + + jsonString = (String) leaderboardReponse3.getEntity(); + obj= JSONValue.parse(jsonString); + jsonObject = (JSONObject) obj; + jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertEquals((Integer) jsonCompetitors.size(), maxCompetitorsCount); + + Thread.sleep(100); + + // third call for 'all' competitors + LeaderboardsResource resource4 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse4 = resource4.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + jsonString = (String) leaderboardReponse4.getEntity(); + obj= JSONValue.parse(jsonString); + jsonObject = (JSONObject) obj; + jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertEquals(jsonCompetitors.size(), 4); + } +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java index aaecab6e0ac..00b3b158769 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java @@ -1,78 +1,78 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.UUID; - -import javax.ws.rs.core.Response; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.server.gateway.jaxrs.api.RegattasResource; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class RegattasResourceTest extends AbstractJaxRsApiTest { - private String boatClassName = "49er"; - private String regattaName = "TestRegatta"; - - @Before - public void setUp() { - super.setUp(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - final Calendar cal = new GregorianCalendar(); - cal.set(2014, 5, 6, 10, 00); - final TimePoint startDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 16, 00); - final TimePoint endDate = new MillisecondsTimePoint(cal.getTime()); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, - startDate, endDate, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - } - - @Test - public void testGetRegattas() throws Exception { - RegattasResource resource = new RegattasResource(); - RegattasResource spyResource = spyResource(resource); - - Response regattasResponse = spyResource.getRegattas(); - - String jsonString = (String) regattasResponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONArray array= (JSONArray) obj; - - assertTrue(array.size() == 1); - - JSONObject firstElement = (JSONObject) array.get(0); - String jsonName = (String) firstElement.get("name"); - String jsonBoatClass = (String) firstElement.get("boatclass"); - - assertTrue(RegattaImpl.getDefaultName(regattaName, boatClassName).equals(jsonName)); - assertTrue(boatClassName.equals(jsonBoatClass)); - } - -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.UUID; + +import javax.ws.rs.core.Response; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.server.gateway.jaxrs.api.RegattasResource; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class RegattasResourceTest extends AbstractJaxRsApiTest { + private String boatClassName = "49er"; + private String regattaName = "TestRegatta"; + + @Before + public void setUp() { + super.setUp(); + List series = new ArrayList(); + List fleets = new ArrayList(); + List raceColumnNames = new ArrayList(); + fleets.add(new FleetImpl("Fleet1")); + fleets.add(new FleetImpl("Fleet2")); + final Calendar cal = new GregorianCalendar(); + cal.set(2014, 5, 6, 10, 00); + final TimePoint startDate = new MillisecondsTimePoint(cal.getTime()); + cal.set(2014, 5, 8, 16, 00); + final TimePoint endDate = new MillisecondsTimePoint(cal.getTime()); + Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, raceColumnNames, /* trackedRegattaRegistry */null); + series.add(testSeries); + racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, + startDate, endDate, UUID.randomUUID(), series, /*persistent*/ true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); + testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + } + + @Test + public void testGetRegattas() throws Exception { + RegattasResource resource = new RegattasResource(); + RegattasResource spyResource = spyResource(resource); + + Response regattasResponse = spyResource.getRegattas(); + + String jsonString = (String) regattasResponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONArray array= (JSONArray) obj; + + assertTrue(array.size() == 1); + + JSONObject firstElement = (JSONObject) array.get(0); + String jsonName = (String) firstElement.get("name"); + String jsonBoatClass = (String) firstElement.get("boatclass"); + + assertTrue(RegattaImpl.getDefaultName(regattaName, boatClassName).equals(jsonName)); + assertTrue(boatClassName.equals(jsonBoatClass)); + } + +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java index 9141af9f724..564cedf4cd7 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java @@ -66,7 +66,7 @@ public class RaceGroupFactory { Map> seriesToRaceColumns = getSeriesToRaceColumns(leaderboard); Collection seriesWithRows = new ArrayList<>(); for (Series series : getSeriesIterable(leaderboard, seriesToRaceColumns)) { - seriesWithRows.add(new SeriesWithRowsImpl(series.getName(), series.isMedal(), getRows(series, + seriesWithRows.add(new SeriesWithRowsImpl(series.getName(), series.isMedal(), series.isFleetsCanRunInParallel(), getRows(series, seriesToRaceColumns.get(series)))); } return seriesWithRows; @@ -135,7 +135,7 @@ public class RaceGroupFactory { private Series createDefaultSeries(Iterable fleets) { Series defaultSeries; - defaultSeries = new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, false, + defaultSeries = new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, false, true, fleets, Collections. emptyList(), null); return defaultSeries; } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java index f886fb2ad9d..dc5a8edcc94 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java @@ -1,117 +1,117 @@ -package com.sap.sailing.server.replication.test; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; - -import com.sap.sailing.domain.abstractlog.AbstractLog; -import com.sap.sailing.domain.abstractlog.AbstractLogEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; -import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; -import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboard; -import com.sap.sse.common.Color; -import com.sap.sse.common.Util; - -public abstract class AbstractLogReplicationTest, EventT extends AbstractLogEvent, VisitorT> - extends AbstractServerReplicationTest { - protected static final String BOAT_CLASS_NAME_49er = "49er"; - - /** - * Uses a new master that loads the existing regatta and race log to append an event to the race log. This will store it to the DB so that if the original - * master re-loads the race log it should see the new race log event. - * @throws Exception - */ - protected void addEventToDB(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent createRaceStatusEvent, String regattaName, String raceColumnName, String fleetName) throws Exception { - final RacingEventServiceImpl temporaryMaster = createNewMaster(); - Regatta regatta = temporaryMaster.getRegatta(new RegattaName(regattaName+" ("+BOAT_CLASS_NAME_49er+")")); - Series series = regatta.getSeries().iterator().next(); - RaceLog masterLog = series.getRaceColumnByName(raceColumnName).getRaceLog(series.getFleetByName(fleetName)); - masterLog.add(createRaceStatusEvent); - } - - /** - * Validation is done based only on the identifier and type of the {@link RaceLogEvent}s. - */ - protected void addAndValidateEventIds(LogT masterLog, LogT replicaLog, @SuppressWarnings("unchecked") EventT... addedEvents) throws InterruptedException { - // 1. Check state of replica after initial load... - assertEqualsOnId(masterLog, replicaLog); - // 2. ... add all incoming events... - for (EventT event : addedEvents) { - masterLog.add(event); - } - // 3. ... and give replication some time to deliver messages. - Thread.sleep(3000); - assertEqualsOnId(masterLog, replicaLog); - } - - /** - * Equality of the events is done based only on the identifier and type. - */ - protected void assertEqualsOnId(LogT masterLog, LogT replicaLog) { - replicaLog.lockForRead(); - try { - masterLog.lockForRead(); - try { - assertEqualsOnId(masterLog.getRawFixes(), replicaLog.getRawFixes()); - } finally { - masterLog.unlockAfterRead(); - } - } finally { - replicaLog.unlockAfterRead(); - } - } - - /** - * Equality of the events is done based only on the {@link RaceLogEvent}'s identifier and type. - */ - private void assertEqualsOnId(Iterable expectedEvents, Iterable actualEvents) { - List expectedCollection = new ArrayList<>(); - Util.addAll(expectedEvents, expectedCollection); - List actualCollection = new ArrayList<>(); - Util.addAll(actualEvents, actualCollection); - assertEquals(Util.size(expectedEvents), Util.size(actualEvents)); - for (int i = 0; i < expectedCollection.size(); i++) { - assertEquals(expectedCollection.get(i).getClass(), actualCollection.get(i).getClass()); - assertEquals(expectedCollection.get(i).getId(), actualCollection.get(i).getId()); - } - } - - protected Regatta setupRegatta(final String regattaName, String seriesName, String fleetName, String boatClassName) { - LinkedHashMap seriesCreationParameters = new LinkedHashMap<>(); - SeriesCreationParametersDTO creationParametersForDefaultSeries = new SeriesCreationParametersDTO( - Arrays.asList(new FleetDTO[] { new FleetDTO(fleetName, 0, Color.BLACK), }), /* medal */false, /* startsWithZero */ - false, /* firstColumnIsNonDiscardableCarryForward */false, /* discardingThresholds */new int[0], /* hasSplitFleetContiguousScoring */ - false); - seriesCreationParameters.put(seriesName, creationParametersForDefaultSeries); - // 1. Install some race column on master... - RegattaCreationParametersDTO regattaCreationParams = new RegattaCreationParametersDTO(seriesCreationParameters); - AddSpecificRegatta addRegattaOperation = new AddSpecificRegatta(regattaName, boatClassName, - /*startDate*/ null, /*endDate*/ null, /* regatta ID */ UUID.randomUUID(), regattaCreationParams, /* persistent */ true, - new LowPoint(), /* default course area ID */ UUID.randomUUID(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN); - return master.apply(addRegattaOperation); - } - - protected FlexibleLeaderboard setupFlexibleLeaderboard(final String leaderboardName) { - CreateFlexibleLeaderboard createTestLeaderboard = new CreateFlexibleLeaderboard(leaderboardName, leaderboardName, new int[] { 19, 44 }, new LowPoint(), null); - FlexibleLeaderboard masterLeaderboard = master.apply(createTestLeaderboard); - return masterLeaderboard; - } - +package com.sap.sailing.server.replication.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; + +import com.sap.sailing.domain.abstractlog.AbstractLog; +import com.sap.sailing.domain.abstractlog.AbstractLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; +import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; +import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboard; +import com.sap.sse.common.Color; +import com.sap.sse.common.Util; + +public abstract class AbstractLogReplicationTest, EventT extends AbstractLogEvent, VisitorT> + extends AbstractServerReplicationTest { + protected static final String BOAT_CLASS_NAME_49er = "49er"; + + /** + * Uses a new master that loads the existing regatta and race log to append an event to the race log. This will store it to the DB so that if the original + * master re-loads the race log it should see the new race log event. + * @throws Exception + */ + protected void addEventToDB(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent createRaceStatusEvent, String regattaName, String raceColumnName, String fleetName) throws Exception { + final RacingEventServiceImpl temporaryMaster = createNewMaster(); + Regatta regatta = temporaryMaster.getRegatta(new RegattaName(regattaName+" ("+BOAT_CLASS_NAME_49er+")")); + Series series = regatta.getSeries().iterator().next(); + RaceLog masterLog = series.getRaceColumnByName(raceColumnName).getRaceLog(series.getFleetByName(fleetName)); + masterLog.add(createRaceStatusEvent); + } + + /** + * Validation is done based only on the identifier and type of the {@link RaceLogEvent}s. + */ + protected void addAndValidateEventIds(LogT masterLog, LogT replicaLog, @SuppressWarnings("unchecked") EventT... addedEvents) throws InterruptedException { + // 1. Check state of replica after initial load... + assertEqualsOnId(masterLog, replicaLog); + // 2. ... add all incoming events... + for (EventT event : addedEvents) { + masterLog.add(event); + } + // 3. ... and give replication some time to deliver messages. + Thread.sleep(3000); + assertEqualsOnId(masterLog, replicaLog); + } + + /** + * Equality of the events is done based only on the identifier and type. + */ + protected void assertEqualsOnId(LogT masterLog, LogT replicaLog) { + replicaLog.lockForRead(); + try { + masterLog.lockForRead(); + try { + assertEqualsOnId(masterLog.getRawFixes(), replicaLog.getRawFixes()); + } finally { + masterLog.unlockAfterRead(); + } + } finally { + replicaLog.unlockAfterRead(); + } + } + + /** + * Equality of the events is done based only on the {@link RaceLogEvent}'s identifier and type. + */ + private void assertEqualsOnId(Iterable expectedEvents, Iterable actualEvents) { + List expectedCollection = new ArrayList<>(); + Util.addAll(expectedEvents, expectedCollection); + List actualCollection = new ArrayList<>(); + Util.addAll(actualEvents, actualCollection); + assertEquals(Util.size(expectedEvents), Util.size(actualEvents)); + for (int i = 0; i < expectedCollection.size(); i++) { + assertEquals(expectedCollection.get(i).getClass(), actualCollection.get(i).getClass()); + assertEquals(expectedCollection.get(i).getId(), actualCollection.get(i).getId()); + } + } + + protected Regatta setupRegatta(final String regattaName, String seriesName, String fleetName, String boatClassName) { + LinkedHashMap seriesCreationParameters = new LinkedHashMap<>(); + SeriesCreationParametersDTO creationParametersForDefaultSeries = new SeriesCreationParametersDTO( + Arrays.asList(new FleetDTO[] { new FleetDTO(fleetName, 0, Color.BLACK), }), /* medal */false, /* fleetsCanRunInParallel */ true, /* startsWithZero */ + false, /* firstColumnIsNonDiscardableCarryForward */false, /* discardingThresholds */new int[0], /* hasSplitFleetContiguousScoring */ + false); + seriesCreationParameters.put(seriesName, creationParametersForDefaultSeries); + // 1. Install some race column on master... + RegattaCreationParametersDTO regattaCreationParams = new RegattaCreationParametersDTO(seriesCreationParameters); + AddSpecificRegatta addRegattaOperation = new AddSpecificRegatta(regattaName, boatClassName, + /*startDate*/ null, /*endDate*/ null, /* regatta ID */ UUID.randomUUID(), regattaCreationParams, /* persistent */ true, + new LowPoint(), /* default course area ID */ UUID.randomUUID(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN); + return master.apply(addRegattaOperation); + } + + protected FlexibleLeaderboard setupFlexibleLeaderboard(final String leaderboardName) { + CreateFlexibleLeaderboard createTestLeaderboard = new CreateFlexibleLeaderboard(leaderboardName, leaderboardName, new int[] { 19, 44 }, new LowPoint(), null); + FlexibleLeaderboard masterLeaderboard = master.apply(createTestLeaderboard); + return masterLeaderboard; + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java index c9a660ffc08..62ed8b2751e 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java @@ -185,11 +185,11 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String baseEventName = "Kiel Week 2012"; final String boatClassName = "49er"; final List emptyRaceColumnNamesList = Collections.emptyList(); - Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, + Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); - Series finals = new SeriesImpl("Finals", /* isMedal */ false, + Series finals = new SeriesImpl("Finals", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Gold", 1), new FleetImpl("Silver", 2) }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); - Series medal = new SeriesImpl("Medal", /* isMedal */ true, + Series medal = new SeriesImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Medal") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification, finals, medal }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); @@ -224,7 +224,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String baseEventName = "Extreme Sailing Series 2020"; final String boatClassName = "Extreme40"; final List emptyRaceColumnNamesList = Collections.emptyList(); - Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, + Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); @@ -238,10 +238,10 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { assertEquals("Qualification", replicatedQualification.getName()); assertEquals(2, Util.size(replicatedQualification.getFleets())); assertFalse(seriesIter.hasNext()); - Series finals = new SeriesImpl("Finals", /* isMedal */ false, + Series finals = new SeriesImpl("Finals", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Gold", 1) }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); FleetDTO finalsGoldFleet = new FleetDTO("Gold", 1, Color.GRAY); - master.apply(new UpdateSeries(masterRegatta.getRegattaIdentifier(), finals.getName(), finals.getName(), finals.isMedal(), + master.apply(new UpdateSeries(masterRegatta.getRegattaIdentifier(), finals.getName(), finals.getName(), finals.isMedal(), finals.isFleetsCanRunInParallel(), new int[] {}, finals.isStartsWithZeroScore(), finals.isFirstColumnIsNonDiscardableCarryForward(), finals.hasSplitFleetContiguousScoring(), Arrays.asList(new FleetDTO[] { finalsGoldFleet }))); @@ -267,7 +267,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String baseEventName = "Extreme Sailing Series 2021"; final String boatClassName = "Extreme40"; final List emptyRaceColumnNamesList = Collections.emptyList(); - Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, + Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); @@ -279,7 +279,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { Series replicatedQualification = seriesIter.next(); assertEquals("Qualification", replicatedQualification.getName()); master.apply(new UpdateSeries(masterRegatta.getRegattaIdentifier(), qualification.getName(), "Simons Quali", - qualification.isMedal(), + qualification.isMedal(), qualification.isFleetsCanRunInParallel(), new int[] {}, qualification.isStartsWithZeroScore(), qualification.isFirstColumnIsNonDiscardableCarryForward(), qualification.hasSplitFleetContiguousScoring(), Arrays.asList(new FleetDTO[] { }))); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java index 455cd856500..bf6f4c88ca5 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java @@ -1,190 +1,190 @@ -package com.sap.sailing.server.impl; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import org.junit.Test; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.CourseAreaImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.BoatClassMasterdata; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.media.MediaTrack; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.masterdataimport.TopLevelMasterData; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsDurationImpl; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.media.MimeType; - -public class MediaMasterDataExportTest { - - static final String matchingRegattaName = "Matching Regatta"; - static final String missingRegattaName = "Missing Regatta"; - static final String matchingRace1 = "matching race 1"; - static final String matchingRace2 = "matching race 2"; - static final String matchingRace3 = "matching race 3"; - static final String missingRace1 = "missing race 1"; - static final String missingRace2 = "missing race 2"; - static final String missingRace3 = "missing race 3"; - - static final boolean displayGroupsInReverseOrder = true; - static final ThresholdBasedResultDiscardingRule resultDiscardingRule = new ThresholdBasedResultDiscardingRuleImpl(new int[0]); - static final BoatClass boatClass = new BoatClassImpl("boat class name", BoatClassMasterdata._12M); - static final TimePoint startDate = MillisecondsTimePoint.now(); - static final TimePoint endDate = startDate.plus(MillisecondsDurationImpl.ONE_DAY); - static final boolean isMedal = false; - static final Fleet regattaFleet = new FleetImpl("fleet name"); - static final boolean persistent = false; - static final ScoringScheme scoringScheme = new LowPoint(); - static final CourseArea courseArea = new CourseAreaImpl("Course Area", UUID.randomUUID()); - static final Serializable regatteId = "regatta id"; - - @Test - public void testTrackWithAssignedRaceButEmptyLeaderboardGroup() { - TimePoint startTime = null; - Duration duration = null; - MimeType mimeType = MimeType.mp4; - Collection allMediaTracks = Arrays.asList( - new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), - new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), - new MediaTrack("dbId 3", "title 1 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), - new MediaTrack("dbId 4", "title 2 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))) - ); - Collection regattaRaces = Collections.emptyList(); - Collection flexibleRaces = Collections.emptyList(); - TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); - Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); - assertEquals(0, filteredMediaTracks.size()); - } - - @Test - public void testTracksWithAssignedRaceAndMatchingRegattaInLeaderboardGroup() { - TimePoint startTime = null; - Duration duration = null; - MimeType mimeType = MimeType.mp4; - Collection allMediaTracks = Arrays.asList( - new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), - new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), - new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), - new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), - new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) - ); - Collection regattaRaces = Arrays.asList( - new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) - ); - Collection flexibleRaces = Collections.emptyList(); - TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); - Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); - assertEquals(3, filteredMediaTracks.size()); - } - - @Test - public void testTrackWithAssignedRaceAndMatchingFlexibleLeaderboardInLeaderboardGroup() { - TimePoint startTime = null; - Duration duration = null; - MimeType mimeType = MimeType.mp4; - Collection allMediaTracks = Arrays.asList( - new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), - new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), - new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), - new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), - new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) - ); - Collection regattaRaces = Collections.emptyList(); - Collection flexibleRaces = Arrays.asList( - new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) - ); - TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); - Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); - assertEquals(3, filteredMediaTracks.size()); - } - - private TopLevelMasterData createTopLevelMasterData(Collection regattaRaces, Collection flexibleRaces, Collection allMediaTracks) { - Set raceColumnNames = new HashSet<>(); - for (RaceIdentifier regattaAndRaceIdentifier : regattaRaces) { - raceColumnNames.add(regattaAndRaceIdentifier.getRaceName()); - } - Regatta regatta = createTestRegatta("regatta name", raceColumnNames); - RegattaLeaderboard regattaLeaderboard = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); - assignRacesToRegattaLeaderboardColumns(regattaLeaderboard, regattaRaces); - - FlexibleLeaderboard flexibleLeaderboard = new FlexibleLeaderboardImpl("flexible leaderboard", resultDiscardingRule, scoringScheme , courseArea); - for (RaceIdentifier regattaAndRaceIdentifier : flexibleRaces) { - RaceColumn raceColumn = flexibleLeaderboard.addRaceColumn(regattaAndRaceIdentifier.getRaceName(), isMedal); - Fleet defaultFleet = raceColumn.getFleets().iterator().next(); - raceColumn.setRaceIdentifier(defaultFleet, regattaAndRaceIdentifier); - } - - - List leaderboards = Arrays.asList(regattaLeaderboard, flexibleLeaderboard); - LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("name", "description", "displayName", displayGroupsInReverseOrder , leaderboards); - Set groupsToExport = Collections.singleton(leaderboardGroup); - Iterable allEvents = Collections.emptyList(); - Map regattaForRaceIdString = Collections.emptyMap(); - GPSFixStore gpsFixStore = mock(GPSFixStore.class); - boolean exportWind = false; - TopLevelMasterData topLevelMasterData = new TopLevelMasterData(groupsToExport , allEvents , regattaForRaceIdString, allMediaTracks , gpsFixStore , exportWind, new HashMap<>()); - return topLevelMasterData; - } - - public static void assignRacesToRegattaLeaderboardColumns(RegattaLeaderboard leaderboard, Collection raceIdentifiers) { - Iterator regattaRacesIterator = raceIdentifiers.iterator(); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - raceColumn.setRaceIdentifier(regattaFleet, regattaRacesIterator.next()); - } - } - - public static RegattaImpl createTestRegatta(String regattaName, Iterable raceColumnNames) { - Iterable regattaFleets = Collections.singleton(regattaFleet); - TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class); - Series series = new SeriesImpl("series name", isMedal, regattaFleets, raceColumnNames, trackedRegattaRegistry); - Iterable regattaSeries = Collections.singleton(series); - return new RegattaImpl(regattaName, boatClass, startDate, endDate, regattaSeries, persistent, scoringScheme, regatteId , courseArea, OneDesignRankingMetric::new); - } - - - -} +package com.sap.sailing.server.impl; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.BoatClassMasterdata; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.masterdataimport.TopLevelMasterData; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsDurationImpl; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.MimeType; + +public class MediaMasterDataExportTest { + + static final String matchingRegattaName = "Matching Regatta"; + static final String missingRegattaName = "Missing Regatta"; + static final String matchingRace1 = "matching race 1"; + static final String matchingRace2 = "matching race 2"; + static final String matchingRace3 = "matching race 3"; + static final String missingRace1 = "missing race 1"; + static final String missingRace2 = "missing race 2"; + static final String missingRace3 = "missing race 3"; + + static final boolean displayGroupsInReverseOrder = true; + static final ThresholdBasedResultDiscardingRule resultDiscardingRule = new ThresholdBasedResultDiscardingRuleImpl(new int[0]); + static final BoatClass boatClass = new BoatClassImpl("boat class name", BoatClassMasterdata._12M); + static final TimePoint startDate = MillisecondsTimePoint.now(); + static final TimePoint endDate = startDate.plus(MillisecondsDurationImpl.ONE_DAY); + static final boolean isMedal = false; + static final Fleet regattaFleet = new FleetImpl("fleet name"); + static final boolean persistent = false; + static final ScoringScheme scoringScheme = new LowPoint(); + static final CourseArea courseArea = new CourseAreaImpl("Course Area", UUID.randomUUID()); + static final Serializable regatteId = "regatta id"; + + @Test + public void testTrackWithAssignedRaceButEmptyLeaderboardGroup() { + TimePoint startTime = null; + Duration duration = null; + MimeType mimeType = MimeType.mp4; + Collection allMediaTracks = Arrays.asList( + new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), + new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), + new MediaTrack("dbId 3", "title 1 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), + new MediaTrack("dbId 4", "title 2 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))) + ); + Collection regattaRaces = Collections.emptyList(); + Collection flexibleRaces = Collections.emptyList(); + TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); + Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); + assertEquals(0, filteredMediaTracks.size()); + } + + @Test + public void testTracksWithAssignedRaceAndMatchingRegattaInLeaderboardGroup() { + TimePoint startTime = null; + Duration duration = null; + MimeType mimeType = MimeType.mp4; + Collection allMediaTracks = Arrays.asList( + new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), + new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), + new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), + new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), + new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) + ); + Collection regattaRaces = Arrays.asList( + new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) + ); + Collection flexibleRaces = Collections.emptyList(); + TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); + Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); + assertEquals(3, filteredMediaTracks.size()); + } + + @Test + public void testTrackWithAssignedRaceAndMatchingFlexibleLeaderboardInLeaderboardGroup() { + TimePoint startTime = null; + Duration duration = null; + MimeType mimeType = MimeType.mp4; + Collection allMediaTracks = Arrays.asList( + new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), + new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), + new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), + new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), + new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) + ); + Collection regattaRaces = Collections.emptyList(); + Collection flexibleRaces = Arrays.asList( + new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) + ); + TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); + Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); + assertEquals(3, filteredMediaTracks.size()); + } + + private TopLevelMasterData createTopLevelMasterData(Collection regattaRaces, Collection flexibleRaces, Collection allMediaTracks) { + Set raceColumnNames = new HashSet<>(); + for (RaceIdentifier regattaAndRaceIdentifier : regattaRaces) { + raceColumnNames.add(regattaAndRaceIdentifier.getRaceName()); + } + Regatta regatta = createTestRegatta("regatta name", raceColumnNames); + RegattaLeaderboard regattaLeaderboard = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); + assignRacesToRegattaLeaderboardColumns(regattaLeaderboard, regattaRaces); + + FlexibleLeaderboard flexibleLeaderboard = new FlexibleLeaderboardImpl("flexible leaderboard", resultDiscardingRule, scoringScheme , courseArea); + for (RaceIdentifier regattaAndRaceIdentifier : flexibleRaces) { + RaceColumn raceColumn = flexibleLeaderboard.addRaceColumn(regattaAndRaceIdentifier.getRaceName(), isMedal); + Fleet defaultFleet = raceColumn.getFleets().iterator().next(); + raceColumn.setRaceIdentifier(defaultFleet, regattaAndRaceIdentifier); + } + + + List leaderboards = Arrays.asList(regattaLeaderboard, flexibleLeaderboard); + LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("name", "description", "displayName", displayGroupsInReverseOrder , leaderboards); + Set groupsToExport = Collections.singleton(leaderboardGroup); + Iterable allEvents = Collections.emptyList(); + Map regattaForRaceIdString = Collections.emptyMap(); + GPSFixStore gpsFixStore = mock(GPSFixStore.class); + boolean exportWind = false; + TopLevelMasterData topLevelMasterData = new TopLevelMasterData(groupsToExport , allEvents , regattaForRaceIdString, allMediaTracks , gpsFixStore , exportWind, new HashMap<>()); + return topLevelMasterData; + } + + public static void assignRacesToRegattaLeaderboardColumns(RegattaLeaderboard leaderboard, Collection raceIdentifiers) { + Iterator regattaRacesIterator = raceIdentifiers.iterator(); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + raceColumn.setRaceIdentifier(regattaFleet, regattaRacesIterator.next()); + } + } + + public static RegattaImpl createTestRegatta(String regattaName, Iterable raceColumnNames) { + Iterable regattaFleets = Collections.singleton(regattaFleet); + TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class); + Series series = new SeriesImpl("series name", isMedal, /* isFleetsCanRunInParallel */ true, regattaFleets, raceColumnNames, trackedRegattaRegistry); + Iterable regattaSeries = Collections.singleton(series); + return new RegattaImpl(regattaName, boatClass, startDate, endDate, regattaSeries, persistent, scoringScheme, regatteId , courseArea, OneDesignRankingMetric::new); + } + + + +} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index c6da147d75f..ab6c392a302 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -204,7 +204,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl(testFleet1Name); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -467,7 +467,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -626,7 +626,7 @@ public class MasterDataImportTest { List fleets = new ArrayList(); FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta(RegattaImpl.getDefaultName("testRegatta", "29er"), "29er", /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ @@ -789,7 +789,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -949,7 +949,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -1063,7 +1063,7 @@ public class MasterDataImportTest { List fleetsNotToOverride = new ArrayList(); FleetImpl testFleet1NotToOverride = new FleetImpl("testFleet1"); fleetsNotToOverride.add(testFleet1NotToOverride); - seriesNotToOverride.add(new SeriesImpl("testSeries", false, fleetsNotToOverride, emptyRaceColumnNamesList, + seriesNotToOverride.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleetsNotToOverride, emptyRaceColumnNamesList, destService)); Regatta regattaNotToOverride = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ @@ -1151,7 +1151,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -1261,7 +1261,7 @@ public class MasterDataImportTest { List fleetsToOverride = new ArrayList(); FleetImpl testFleet1ToOverride = new FleetImpl("testFleet1"); fleetsToOverride.add(testFleet1ToOverride); - seriesToOverride.add(new SeriesImpl("testSeries", false, fleetsToOverride, emptyRaceColumnNamesList, + seriesToOverride.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleetsToOverride, emptyRaceColumnNamesList, destService)); Regatta regattaToOverride = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ @@ -1452,7 +1452,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( @@ -1587,7 +1587,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -1833,7 +1833,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -2063,8 +2063,8 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl(testFleet1Name); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - seriesOnSource.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); - seriesOnTarget.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + seriesOnSource.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); + seriesOnTarget.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java index 94b5884d201..fa6a3ed3f87 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java @@ -1,322 +1,322 @@ -package com.sap.sailing.server.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.LeaderboardGroupBase; -import com.sap.sailing.domain.base.LeaderboardSearchResult; -import com.sap.sailing.domain.base.LeaderboardSearchResultBase; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Venue; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.CourseAreaImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; -import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; -import com.sap.sailing.domain.test.AbstractTracTracLiveTest; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.impl.EmptyWindStore; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; -import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; -import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.LeaderboardSearchResultJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; -import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; -import com.sap.sailing.server.operationaltransformation.AddRaceDefinition; -import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; -import com.sap.sailing.server.operationaltransformation.CreateEvent; -import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; -import com.sap.sailing.server.operationaltransformation.CreateTrackedRace; -import com.sap.sailing.server.operationaltransformation.RemoveEvent; -import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; -import com.sap.sailing.server.operationaltransformation.RemoveRegatta; -import com.sap.sse.common.Color; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.search.KeywordQuery; -import com.sap.sse.common.search.Result; -import com.sap.sse.shared.media.ImageDescriptor; -import com.sap.sse.shared.media.VideoDescriptor; - -public class SearchServiceTest { - private RacingEventService server; - private Venue kiel; - private Venue flensburg; - private Event pfingstbusch; - private Regatta pfingstbusch29er; - private Competitor hassoPlattner; - private Competitor alexanderRies; - private Competitor antonKoch; - private Competitor tobiasSchadewaldt; - private Competitor philippBuhl; - private Competitor dennisGehrlein; - private Regatta pfingstbusch470; - private Regatta aalRegatta; - private DynamicTrackedRace pfingstbusch29erTrackedR1; - private DynamicTrackedRace pfingstbusch29erTrackedR3; - private Event aalEvent; - private DynamicTrackedRace pfingstbusch470TrackedR1; - private DynamicTrackedRace pfingstbusch470TrackedR2; - private DynamicTrackedRace aalOrcTrackedR1; - private DynamicTrackedRace aalOrcTrackedR2; - - @Before - public void setUp() { - server = new RacingEventServiceImpl(); - List allEvents = new ArrayList<>(); - Util.addAll(server.getAllEvents(), allEvents); - for (Event e : allEvents) { - server.apply(new RemoveEvent(e.getId())); - } - Map allLeaderboards = new HashMap<>(server.getLeaderboards()); - for (String leaderboardName : allLeaderboards.keySet()) { - server.apply(new RemoveLeaderboard(leaderboardName)); - } - server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (29er)"))); - server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (470)"))); - server.apply(new RemoveRegatta(new RegattaName("Aalregatta (ORC)"))); - final Calendar cal = new GregorianCalendar(); - cal.set(2014, 5, 6, 10, 00); - final TimePoint pfingstbuschStartDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 16, 00); - final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime()); - pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */ - "Kiel", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /* sailorsInfoWebsiteURLAsString */ null, - /* images */Collections. emptyList(), /* videos */Collections. emptyList())); - kiel = pfingstbusch.getVenue(); - final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID()); - kiel.addCourseArea(kielAlpha); - final CourseAreaImpl kielBravo = new CourseAreaImpl("Bravo", UUID.randomUUID()); - kiel.addCourseArea(kielBravo); - final LinkedHashMap seriesCreationParams = new LinkedHashMap(); - seriesCreationParams.put("Default", - new SeriesCreationParametersDTO(Collections.singletonList(new FleetDTO("Default", /* order */-1, Color.RED)), - /* medal */false, /* startsWithZero */false, /* firstColumnIsNonDiscardableCarryForward */false, - /* discardingThresholds */null, /* hasSplitFleetContiguousScoring */false)); - pfingstbusch29er = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "29er"), "29er", - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), - new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), kielAlpha.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); - server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R1")); - server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R2")); - server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R3")); - RegattaLeaderboard pfingstbusch29erLeaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch29er.getRegattaIdentifier(), - /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); - pfingstbusch470 = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "470"), "470", - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), kielBravo.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); - server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R1")); - server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R2")); - server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R3")); - RegattaLeaderboard pfingstbusch470Leaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch470.getRegattaIdentifier(), - /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); - cal.set(2014, 5, 7, 10, 00); - final TimePoint aalStartDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 18, 00); - final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime()); - aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */ - "Flensburg", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /*sailorsInfoWebsiteURLAsString */ null, - /* images */Collections. emptyList(), /* videos */Collections. emptyList())); - flensburg = aalEvent.getVenue(); - final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID()); - flensburg.addCourseArea(flensburgStandard); - aalRegatta = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Aalregatta", "ORC"), "ORC", - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), - new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), flensburgStandard.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); - server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R1")); - server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R2")); - RegattaLeaderboard aalRegattaLeaderboard = server.apply(new CreateRegattaLeaderboard(aalRegatta.getRegattaIdentifier(), - /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); - hassoPlattner = AbstractTracTracLiveTest.createCompetitor("Hasso Plattner"); - alexanderRies = AbstractTracTracLiveTest.createCompetitor("Alexander Ries"); - antonKoch = AbstractTracTracLiveTest.createCompetitor("Anton Koch"); - tobiasSchadewaldt = AbstractTracTracLiveTest.createCompetitor("Tobias Schadewaldt"); - philippBuhl = AbstractTracTracLiveTest.createCompetitor("Philipp Buhl"); - dennisGehrlein = AbstractTracTracLiveTest.createCompetitor("Dennis Gehrlein"); - final RaceDefinitionImpl pfingstbusch29erR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), - Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); - final RaceDefinitionImpl pfingstbusch29erR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), - Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); - final RaceDefinitionImpl pfingstbusch29erR3 = new RaceDefinitionImpl("R3", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), - Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); - server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR1)); - server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR2)); - server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR3)); - // track only R1 and R3 - pfingstbusch29erTrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch29erTrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(1)); - pfingstbusch29erLeaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch29erTrackedR1); - pfingstbusch29erTrackedR3 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR3), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch29erLeaderboard.getRaceColumnByName("R3").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R3").getFleetByName("Default"), pfingstbusch29erTrackedR3); - - final RaceDefinitionImpl pfingstbush470R1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), - Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); - final RaceDefinitionImpl pfingstbush470R2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), - Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); - server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R1)); - server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R2)); - // track only R1 and R2 - pfingstbusch470TrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch470TrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(2)); // starts later than 29er - pfingstbusch470Leaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch470TrackedR1); - pfingstbusch470TrackedR2 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch470Leaderboard.getRaceColumnByName("R2").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R2").getFleetByName("Default"), pfingstbusch470TrackedR2); - - final RaceDefinitionImpl aalOrcR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), - Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); - final RaceDefinitionImpl aalOrcR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), - Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); - server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR1)); - server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR2)); - // track only R1 and R2 - aalOrcTrackedR1 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - aalOrcTrackedR1.setStartOfTrackingReceived(aalStartDate); - aalRegattaLeaderboard.getRaceColumnByName("R1").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), aalOrcTrackedR1); - aalOrcTrackedR2 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - aalRegattaLeaderboard.getRaceColumnByName("R2").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R2").getFleetByName("Default"), aalOrcTrackedR2); - } - - @Test - public void testSetup() { - assertNotNull(pfingstbusch29er); - assertNotNull(pfingstbusch29erTrackedR1); - assertNotNull(pfingstbusch29erTrackedR3); - assertNotNull(pfingstbusch470TrackedR1); - assertNotNull(pfingstbusch470TrackedR2); - assertNotNull(aalEvent); - assertNotNull(aalRegatta); - assertNotNull(aalOrcTrackedR1); - assertNotNull(aalOrcTrackedR2); - } - - @Test - public void testSimpleSearchByCompetitorName() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Tobi" }))); - assertEquals(1, Util.size(searchResults.getHits())); - Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); - assertSame(pfingstbusch29er, foundRegatta); - } - - @Test - public void testSimpleSearchByCompetitorName2() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Hasso" }))); - assertEquals(1, Util.size(searchResults.getHits())); - Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); - assertSame(aalRegatta, foundRegatta); - } - - @Test - public void testSimpleSearchByVenueName() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Flensburg" }))); - assertEquals(1, Util.size(searchResults.getHits())); - Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); - assertSame(aalRegatta, foundRegatta); - } - - @Test - public void testSimpleSearchByVenueName2() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Kiel" }))); - assertEquals(2, Util.size(searchResults.getHits())); - final Iterator iterator = searchResults.getHits().iterator(); - final LeaderboardSearchResult firstMatch = iterator.next(); - Regatta firstFoundRegatta = firstMatch.getRegatta(); - assertSame(pfingstbusch29er, firstFoundRegatta); - assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); - final LeaderboardSearchResult secondMatch = iterator.next(); - Regatta secondFoundRegatta = secondMatch.getRegatta(); - assertSame(pfingstbusch470, secondFoundRegatta); - assertSame(pfingstbusch, secondMatch.getEvents().iterator().next()); - } - - @Test - public void testMultipleMatchesSortedCorrectly() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); - assertEquals(2, Util.size(searchResults.getHits())); - final Iterator iter = searchResults.getHits().iterator(); - final LeaderboardSearchResult firstMatch = iter.next(); - Regatta earlierStartRegatta = firstMatch.getRegatta(); - assertSame(pfingstbusch470, earlierStartRegatta); - assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); - final LeaderboardSearchResult secondMatch = iter.next(); - Regatta laterStartRegatta = secondMatch.getRegatta(); - assertSame(aalRegatta, laterStartRegatta); - assertSame(aalEvent, secondMatch.getEvents().iterator().next()); - } - - @Test - public void testSerializeAndDeserializeSearchResult() throws JsonDeserializationException { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); - LeaderboardGroupBaseJsonSerializer leaderboardGroupBaseJsonSerializer = new LeaderboardGroupBaseJsonSerializer(); - LeaderboardSearchResultJsonSerializer serializer = new LeaderboardSearchResultJsonSerializer(new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), - leaderboardGroupBaseJsonSerializer), - leaderboardGroupBaseJsonSerializer); - LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer(); - LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer( - new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), - leaderboardGroupBaseJsonDeserializer), leaderboardGroupBaseJsonDeserializer); - final LeaderboardSearchResult expected = searchResults.getHits().iterator().next(); - LeaderboardSearchResultBase deserialized = deserializer.deserialize(serializer.serialize(expected)); - assertEquals("Pfingstbusch (470)", deserialized.getRegattaName()); - assertEquals(expected.getEvents().iterator().next().getName(), deserialized.getEvents().iterator().next().getName()); - assertEquals(expected.getEvents().iterator().next().getId(), deserialized.getEvents().iterator().next().getId()); - assertEquals(expected.getEvents().iterator().next().getStartDate(), deserialized.getEvents().iterator().next().getStartDate()); - assertEquals(expected.getEvents().iterator().next().getEndDate(), deserialized.getEvents().iterator().next().getEndDate()); - assertEquals(expected.getEvents().iterator().next().getVenue().getName(), deserialized.getEvents().iterator().next().getVenue().getName()); - Iterator expectedLGs = expected.getLeaderboardGroups().iterator(); - Iterator deserializedLGs = deserialized.getLeaderboardGroups().iterator(); - while (expectedLGs.hasNext()) { - assertTrue(deserializedLGs.hasNext()); - LeaderboardGroup expectedLG = expectedLGs.next(); - LeaderboardGroupBase deserializedLG = deserializedLGs.next(); - assertEquals(expectedLG.getId(), deserializedLG.getId()); - assertEquals(expectedLG.getName(), deserializedLG.getName()); - assertEquals(expectedLG.getDescription(), deserializedLG.getDescription()); - } - } -} +package com.sap.sailing.server.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; +import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.test.AbstractTracTracLiveTest; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.LeaderboardSearchResultJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; +import com.sap.sailing.server.operationaltransformation.AddRaceDefinition; +import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; +import com.sap.sailing.server.operationaltransformation.CreateEvent; +import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; +import com.sap.sailing.server.operationaltransformation.CreateTrackedRace; +import com.sap.sailing.server.operationaltransformation.RemoveEvent; +import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; +import com.sap.sailing.server.operationaltransformation.RemoveRegatta; +import com.sap.sse.common.Color; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.common.search.Result; +import com.sap.sse.shared.media.ImageDescriptor; +import com.sap.sse.shared.media.VideoDescriptor; + +public class SearchServiceTest { + private RacingEventService server; + private Venue kiel; + private Venue flensburg; + private Event pfingstbusch; + private Regatta pfingstbusch29er; + private Competitor hassoPlattner; + private Competitor alexanderRies; + private Competitor antonKoch; + private Competitor tobiasSchadewaldt; + private Competitor philippBuhl; + private Competitor dennisGehrlein; + private Regatta pfingstbusch470; + private Regatta aalRegatta; + private DynamicTrackedRace pfingstbusch29erTrackedR1; + private DynamicTrackedRace pfingstbusch29erTrackedR3; + private Event aalEvent; + private DynamicTrackedRace pfingstbusch470TrackedR1; + private DynamicTrackedRace pfingstbusch470TrackedR2; + private DynamicTrackedRace aalOrcTrackedR1; + private DynamicTrackedRace aalOrcTrackedR2; + + @Before + public void setUp() { + server = new RacingEventServiceImpl(); + List allEvents = new ArrayList<>(); + Util.addAll(server.getAllEvents(), allEvents); + for (Event e : allEvents) { + server.apply(new RemoveEvent(e.getId())); + } + Map allLeaderboards = new HashMap<>(server.getLeaderboards()); + for (String leaderboardName : allLeaderboards.keySet()) { + server.apply(new RemoveLeaderboard(leaderboardName)); + } + server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (29er)"))); + server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (470)"))); + server.apply(new RemoveRegatta(new RegattaName("Aalregatta (ORC)"))); + final Calendar cal = new GregorianCalendar(); + cal.set(2014, 5, 6, 10, 00); + final TimePoint pfingstbuschStartDate = new MillisecondsTimePoint(cal.getTime()); + cal.set(2014, 5, 8, 16, 00); + final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime()); + pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */ + "Kiel", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /* sailorsInfoWebsiteURLAsString */ null, + /* images */Collections. emptyList(), /* videos */Collections. emptyList())); + kiel = pfingstbusch.getVenue(); + final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID()); + kiel.addCourseArea(kielAlpha); + final CourseAreaImpl kielBravo = new CourseAreaImpl("Bravo", UUID.randomUUID()); + kiel.addCourseArea(kielBravo); + final LinkedHashMap seriesCreationParams = new LinkedHashMap(); + seriesCreationParams.put("Default", + new SeriesCreationParametersDTO(Collections.singletonList(new FleetDTO("Default", /* order */-1, Color.RED)), + /* medal */false, /* fleetsCanRunInParallel */ true, /* startsWithZero */false, /* firstColumnIsNonDiscardableCarryForward */false, + /* discardingThresholds */null, /* hasSplitFleetContiguousScoring */false)); + pfingstbusch29er = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "29er"), "29er", + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), + new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ + true, new LowPoint(), kielAlpha.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); + server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R1")); + server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R2")); + server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R3")); + RegattaLeaderboard pfingstbusch29erLeaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch29er.getRegattaIdentifier(), + /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); + pfingstbusch470 = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "470"), "470", + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ + true, new LowPoint(), kielBravo.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); + server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R1")); + server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R2")); + server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R3")); + RegattaLeaderboard pfingstbusch470Leaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch470.getRegattaIdentifier(), + /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); + cal.set(2014, 5, 7, 10, 00); + final TimePoint aalStartDate = new MillisecondsTimePoint(cal.getTime()); + cal.set(2014, 5, 8, 18, 00); + final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime()); + aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */ + "Flensburg", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /*sailorsInfoWebsiteURLAsString */ null, + /* images */Collections. emptyList(), /* videos */Collections. emptyList())); + flensburg = aalEvent.getVenue(); + final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID()); + flensburg.addCourseArea(flensburgStandard); + aalRegatta = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Aalregatta", "ORC"), "ORC", + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), + new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ + true, new LowPoint(), flensburgStandard.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); + server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R1")); + server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R2")); + RegattaLeaderboard aalRegattaLeaderboard = server.apply(new CreateRegattaLeaderboard(aalRegatta.getRegattaIdentifier(), + /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); + hassoPlattner = AbstractTracTracLiveTest.createCompetitor("Hasso Plattner"); + alexanderRies = AbstractTracTracLiveTest.createCompetitor("Alexander Ries"); + antonKoch = AbstractTracTracLiveTest.createCompetitor("Anton Koch"); + tobiasSchadewaldt = AbstractTracTracLiveTest.createCompetitor("Tobias Schadewaldt"); + philippBuhl = AbstractTracTracLiveTest.createCompetitor("Philipp Buhl"); + dennisGehrlein = AbstractTracTracLiveTest.createCompetitor("Dennis Gehrlein"); + final RaceDefinitionImpl pfingstbusch29erR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), + Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); + final RaceDefinitionImpl pfingstbusch29erR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), + Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); + final RaceDefinitionImpl pfingstbusch29erR3 = new RaceDefinitionImpl("R3", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), + Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); + server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR1)); + server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR2)); + server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR3)); + // track only R1 and R3 + pfingstbusch29erTrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch29erTrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(1)); + pfingstbusch29erLeaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch29erTrackedR1); + pfingstbusch29erTrackedR3 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR3), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch29erLeaderboard.getRaceColumnByName("R3").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R3").getFleetByName("Default"), pfingstbusch29erTrackedR3); + + final RaceDefinitionImpl pfingstbush470R1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), + Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); + final RaceDefinitionImpl pfingstbush470R2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), + Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); + server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R1)); + server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R2)); + // track only R1 and R2 + pfingstbusch470TrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch470TrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(2)); // starts later than 29er + pfingstbusch470Leaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch470TrackedR1); + pfingstbusch470TrackedR2 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch470Leaderboard.getRaceColumnByName("R2").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R2").getFleetByName("Default"), pfingstbusch470TrackedR2); + + final RaceDefinitionImpl aalOrcR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), + Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); + final RaceDefinitionImpl aalOrcR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), + Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); + server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR1)); + server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR2)); + // track only R1 and R2 + aalOrcTrackedR1 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + aalOrcTrackedR1.setStartOfTrackingReceived(aalStartDate); + aalRegattaLeaderboard.getRaceColumnByName("R1").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), aalOrcTrackedR1); + aalOrcTrackedR2 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + aalRegattaLeaderboard.getRaceColumnByName("R2").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R2").getFleetByName("Default"), aalOrcTrackedR2); + } + + @Test + public void testSetup() { + assertNotNull(pfingstbusch29er); + assertNotNull(pfingstbusch29erTrackedR1); + assertNotNull(pfingstbusch29erTrackedR3); + assertNotNull(pfingstbusch470TrackedR1); + assertNotNull(pfingstbusch470TrackedR2); + assertNotNull(aalEvent); + assertNotNull(aalRegatta); + assertNotNull(aalOrcTrackedR1); + assertNotNull(aalOrcTrackedR2); + } + + @Test + public void testSimpleSearchByCompetitorName() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Tobi" }))); + assertEquals(1, Util.size(searchResults.getHits())); + Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); + assertSame(pfingstbusch29er, foundRegatta); + } + + @Test + public void testSimpleSearchByCompetitorName2() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Hasso" }))); + assertEquals(1, Util.size(searchResults.getHits())); + Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); + assertSame(aalRegatta, foundRegatta); + } + + @Test + public void testSimpleSearchByVenueName() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Flensburg" }))); + assertEquals(1, Util.size(searchResults.getHits())); + Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); + assertSame(aalRegatta, foundRegatta); + } + + @Test + public void testSimpleSearchByVenueName2() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Kiel" }))); + assertEquals(2, Util.size(searchResults.getHits())); + final Iterator iterator = searchResults.getHits().iterator(); + final LeaderboardSearchResult firstMatch = iterator.next(); + Regatta firstFoundRegatta = firstMatch.getRegatta(); + assertSame(pfingstbusch29er, firstFoundRegatta); + assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); + final LeaderboardSearchResult secondMatch = iterator.next(); + Regatta secondFoundRegatta = secondMatch.getRegatta(); + assertSame(pfingstbusch470, secondFoundRegatta); + assertSame(pfingstbusch, secondMatch.getEvents().iterator().next()); + } + + @Test + public void testMultipleMatchesSortedCorrectly() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); + assertEquals(2, Util.size(searchResults.getHits())); + final Iterator iter = searchResults.getHits().iterator(); + final LeaderboardSearchResult firstMatch = iter.next(); + Regatta earlierStartRegatta = firstMatch.getRegatta(); + assertSame(pfingstbusch470, earlierStartRegatta); + assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); + final LeaderboardSearchResult secondMatch = iter.next(); + Regatta laterStartRegatta = secondMatch.getRegatta(); + assertSame(aalRegatta, laterStartRegatta); + assertSame(aalEvent, secondMatch.getEvents().iterator().next()); + } + + @Test + public void testSerializeAndDeserializeSearchResult() throws JsonDeserializationException { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); + LeaderboardGroupBaseJsonSerializer leaderboardGroupBaseJsonSerializer = new LeaderboardGroupBaseJsonSerializer(); + LeaderboardSearchResultJsonSerializer serializer = new LeaderboardSearchResultJsonSerializer(new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), + leaderboardGroupBaseJsonSerializer), + leaderboardGroupBaseJsonSerializer); + LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer(); + LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer( + new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), + leaderboardGroupBaseJsonDeserializer), leaderboardGroupBaseJsonDeserializer); + final LeaderboardSearchResult expected = searchResults.getHits().iterator().next(); + LeaderboardSearchResultBase deserialized = deserializer.deserialize(serializer.serialize(expected)); + assertEquals("Pfingstbusch (470)", deserialized.getRegattaName()); + assertEquals(expected.getEvents().iterator().next().getName(), deserialized.getEvents().iterator().next().getName()); + assertEquals(expected.getEvents().iterator().next().getId(), deserialized.getEvents().iterator().next().getId()); + assertEquals(expected.getEvents().iterator().next().getStartDate(), deserialized.getEvents().iterator().next().getStartDate()); + assertEquals(expected.getEvents().iterator().next().getEndDate(), deserialized.getEvents().iterator().next().getEndDate()); + assertEquals(expected.getEvents().iterator().next().getVenue().getName(), deserialized.getEvents().iterator().next().getVenue().getName()); + Iterator expectedLGs = expected.getLeaderboardGroups().iterator(); + Iterator deserializedLGs = deserialized.getLeaderboardGroups().iterator(); + while (expectedLGs.hasNext()) { + assertTrue(deserializedLGs.hasNext()); + LeaderboardGroup expectedLG = expectedLGs.next(); + LeaderboardGroupBase deserializedLG = deserializedLGs.next(); + assertEquals(expectedLG.getId(), deserializedLG.getId()); + assertEquals(expectedLG.getName(), deserializedLG.getName()); + assertEquals(expectedLG.getDescription(), deserializedLG.getDescription()); + } + } +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java index 9aa44d096da..df2e1f6b302 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java @@ -78,6 +78,10 @@ public class RaceLogReplicator implements RaceColumnListener { public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java index a2b3ad0ec64..1efadc59320 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java @@ -51,6 +51,10 @@ public class RaceLogScoringReplicator implements RaceColumnListener { public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 6541b9a4d8c..1de83e5335d 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1444,7 +1444,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes } result.put( s.getName(), - new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isStartsWithZeroScore(), s + new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isFleetsCanRunInParallel(), s.isStartsWithZeroScore(), s .isFirstColumnIsNonDiscardableCarryForward(), s.getResultDiscardingRule() == null ? null : s.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), s .hasSplitFleetContiguousScoring())); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java index a996da27431..b58bf4d24c5 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java @@ -56,7 +56,7 @@ public class AddSpecificRegatta extends AbstractAddRegattaOperation { List result = new ArrayList(); for (Map.Entry e : seriesNamesWithFleetNamesAndFleetOrderingAndMedalAndStartsWithZeroScoreAndDiscardingThresholds.getSeriesCreationParameters().entrySet()) { final List emptyRaceColumnNamesList = Collections.emptyList(); - Series s = new SeriesImpl(e.getKey(), e.getValue().isMedal(), createFleets(e.getValue().getFleets()), + Series s = new SeriesImpl(e.getKey(), e.getValue().isMedal(), e.getValue().isFleetsCanRunInParallel(), createFleets(e.getValue().getFleets()), emptyRaceColumnNamesList, trackedRegattaRegistry); if (e.getValue().getDiscardingThresholds() != null) { s.setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(e.getValue().getDiscardingThresholds())); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java index b3e5f88ef34..88d9208e9dd 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java @@ -1,90 +1,93 @@ -package com.sap.sailing.server.operationaltransformation; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.RegattaIdentifier; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.RacingEventServiceOperation; - -public class UpdateSeries extends AbstractSeriesOperation { - private static final long serialVersionUID = 6356089749049112710L; - - private final List fleets; - private final boolean isMedal; - private final int[] resultDiscardingThresholds; - private final boolean startsWithZeroScore; - private final boolean firstColumnIsNonDiscardableCarryForward; - private final boolean hasSplitFleetContiguousScoring; - private final boolean seriesNameChanged; - private final String newSeriesName; - - public UpdateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, - boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, - List fleets) { - super(regattaIdentifier, seriesName); - this.seriesNameChanged = !seriesName.equals(newSeriesName); - this.newSeriesName = newSeriesName; - this.isMedal = isMedal; - this.resultDiscardingThresholds = resultDiscardingThresholds; - this.startsWithZeroScore = startsWithZeroScore; - this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; - this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; - this.fleets = fleets; - } - - @Override - public Void internalApplyTo(RacingEventService toState) throws Exception { - Series series = getSeries(toState); - if (series == null) { - series = createSeries(toState); - } - if (seriesNameChanged) { - series.setName(newSeriesName); - } - series.setIsMedal(isMedal); - series.setResultDiscardingRule(resultDiscardingThresholds == null ? - null : new ThresholdBasedResultDiscardingRuleImpl(resultDiscardingThresholds)); - series.setStartsWithZeroScore(startsWithZeroScore); - series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward); - series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoring); - if (series.getRegatta().isPersistent()) { - toState.updateStoredRegatta(series.getRegatta()); - } - return null; - } - - private Series createSeries(RacingEventService toState) { - Regatta regatta = toState.getRegatta(getRegattaIdentifier()); - final List emptyRaceColumnNames = Collections.emptyList(); - List result = new ArrayList(); - for (FleetDTO fleetNameAndOrderingAndColor : fleets) { - Fleet fleet = new FleetImpl(fleetNameAndOrderingAndColor.getName(), fleetNameAndOrderingAndColor.getOrderNo(), fleetNameAndOrderingAndColor.getColor()); - result.add(fleet); - } - regatta.addSeries(new SeriesImpl(getSeriesName(), isMedal, result, emptyRaceColumnNames, (TrackedRegattaRegistry)toState)); - return regatta.getSeriesByName(getSeriesName()); - } - - @Override - public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) { - // TODO Auto-generated method stub - return null; - } - - @Override - public RacingEventServiceOperation transformServerOp(RacingEventServiceOperation clientOp) { - // TODO Auto-generated method stub - return null; - } -} +package com.sap.sailing.server.operationaltransformation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.RacingEventServiceOperation; + +public class UpdateSeries extends AbstractSeriesOperation { + private static final long serialVersionUID = 6356089749049112710L; + + private final List fleets; + private final boolean isMedal; + private final boolean isFleetsCanRunInParallel; + private final int[] resultDiscardingThresholds; + private final boolean startsWithZeroScore; + private final boolean firstColumnIsNonDiscardableCarryForward; + private final boolean hasSplitFleetContiguousScoring; + private final boolean seriesNameChanged; + private final String newSeriesName; + + public UpdateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, boolean isFleetsCanRunInParallel, + int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, + List fleets) { + super(regattaIdentifier, seriesName); + this.seriesNameChanged = !seriesName.equals(newSeriesName); + this.newSeriesName = newSeriesName; + this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + this.resultDiscardingThresholds = resultDiscardingThresholds; + this.startsWithZeroScore = startsWithZeroScore; + this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; + this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; + this.fleets = fleets; + } + + @Override + public Void internalApplyTo(RacingEventService toState) throws Exception { + Series series = getSeries(toState); + if (series == null) { + series = createSeries(toState); + } + if (seriesNameChanged) { + series.setName(newSeriesName); + } + series.setIsMedal(isMedal); + series.setIsFleetsCanRunInParallel(isFleetsCanRunInParallel); + series.setResultDiscardingRule(resultDiscardingThresholds == null ? + null : new ThresholdBasedResultDiscardingRuleImpl(resultDiscardingThresholds)); + series.setStartsWithZeroScore(startsWithZeroScore); + series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward); + series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoring); + if (series.getRegatta().isPersistent()) { + toState.updateStoredRegatta(series.getRegatta()); + } + return null; + } + + private Series createSeries(RacingEventService toState) { + Regatta regatta = toState.getRegatta(getRegattaIdentifier()); + final List emptyRaceColumnNames = Collections.emptyList(); + List result = new ArrayList(); + for (FleetDTO fleetNameAndOrderingAndColor : fleets) { + Fleet fleet = new FleetImpl(fleetNameAndOrderingAndColor.getName(), fleetNameAndOrderingAndColor.getOrderNo(), fleetNameAndOrderingAndColor.getColor()); + result.add(fleet); + } + regatta.addSeries(new SeriesImpl(getSeriesName(), isMedal, isFleetsCanRunInParallel, result, emptyRaceColumnNames, (TrackedRegattaRegistry)toState)); + return regatta.getSeriesByName(getSeriesName()); + } + + @Override + public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RacingEventServiceOperation transformServerOp(RacingEventServiceOperation clientOp) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java index 1e46db1ecf2..f132381a169 100755 --- a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java +++ b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java @@ -143,7 +143,7 @@ public class StructureImporter { for (Series raceType : regattaStructure.getSeries()) { List fleets = getFleets(raceType.getFleets()); setRaceNames(index, raceType, raceType.getFleets()); - series.add(new SeriesImpl(raceType.getSeries(), raceType.isMedal(), fleets, raceType.getRaceNames(), null)); + series.add(new SeriesImpl(raceType.getSeries(), raceType.isMedal(), true, fleets, raceType.getRaceNames(), null)); } } return series; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java index 71683a1d6cc..41be9b03291 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java @@ -87,8 +87,8 @@ public class OfflineDataManager extends DataManager { newEvent.getVenue().addCourseArea(new CourseAreaImpl("Stadium", UUID.randomUUID())); dataStore.addEvent(newEvent); - SeriesWithRows qualifying = new SeriesWithRowsImpl("Qualifying", false, null); - SeriesWithRows medal = new SeriesWithRowsImpl("Medal", true, null); + SeriesWithRows qualifying = new SeriesWithRowsImpl("Qualifying", false, /* isFleetsCanRunInParallel */ true, null); + SeriesWithRows medal = new SeriesWithRowsImpl("Medal", true, /* isFleetsCanRunInParallel */ true, null); RaceGroup raceGroup = new RaceGroupImpl("ESS", /* displayName */ null, new BoatClassImpl("X40", false), null, Arrays.asList(qualifying, medal), new EmptyRegattaConfiguration());