Merge branch 'bug5217'

This commit is contained in:
Axel Uhl committed 2022-10-12 09:52:02 +02:00
commit 9ea955ae2c
24 files changed
+902 -53

No files matched your search

@@ -22,6 +22,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaRegistry;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.base.SailingServerConfiguration;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.impl.DynamicBoat;
import com.sap.sailing.domain.base.impl.DynamicCompetitor;
@@ -35,8 +36,10 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -223,4 +226,12 @@ public interface DomainObjectFactory {
TypeBasedServiceFinder<RaceTrackingConnectivityParametersHandler> getRaceTrackingConnectivityParamsServiceFinder();
Wind loadWind(Document windDocument);
Map<RaceIdentifier, MarkPassingRaceFingerprint> loadFingerprintsForMarkPassingHashes();
/**
* For races that have a {@link MarkPassingRaceFingerprint} stored in the database (see {@link #loadFingerprintsForMarkPassingHashes()})
* a caller can load the corresponding mark passings with this method.
*/
Map<Competitor, Map<Waypoint, MarkPassing>> loadMarkPassings(RaceIdentifier raceIdentifier);
}
@@ -188,5 +188,8 @@ public enum FieldNames {
ORC_CERTIFICATE_TWS_14KT, ORC_CERTIFICATE_TWS_16KT, ORC_CERTIFICATE_TWS_20KT, ORC_CERTIFICATE_R52_PREDICTION, ORC_CERTIFICATE_R60_PREDICTION,
ORC_CERTIFICATE_R75_PREDICTION, ORC_CERTIFICATE_R90_PREDICTION, ORC_CERTIFICATE_R110_PREDICTION, ORC_CERTIFICATE_R120_PREDICTION,
ORC_CERTIFICATE_R135_PREDICTION, ORC_CERTIFICATE_R150_PREDICTION, ORC_CERTIFICATE_BOATCLASS, ORC_CERTIFICATE_LENGTH, ORC_CERTIFICATE_TWA_SPEED_PREDICTIONS,
ORC_CERTIFICATE_CIRCULAR_RANDOM_SPEED_PREDICTIONS, ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS, ORC_OTHER_RACE_IDENTIFIER, ORC_IMPLIED_WIND_SOURCE;
ORC_CERTIFICATE_CIRCULAR_RANDOM_SPEED_PREDICTIONS, ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS, ORC_OTHER_RACE_IDENTIFIER, ORC_IMPLIED_WIND_SOURCE,
// MARKPASSINGS collection
MARK_PASSINGS_FINGERPRINT, MARK_PASSINGS;
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.persistence;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -20,14 +21,18 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.base.SailingServerConfiguration;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.AnniversaryType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
@@ -228,4 +233,14 @@ public interface MongoObjectFactory {
void storeAnniversaryData(ConcurrentHashMap<Integer, Pair<DetailedRaceInfo, AnniversaryType>> knownAnniversaries);
Document storeWind(Wind wind);
/**
* Stores a race's mark passings persistently.
*
* @param raceIdentifier
* identifies the race to which the mark passings belong
* @param fingerprint
* a composite fingerprint of the race in the state at which the mark passings were captured
*/
void storeMarkPassings(RaceIdentifier raceIdentifier, MarkPassingRaceFingerprint fingerprint, Map<Competitor, Map<Waypoint, MarkPassing>> markPassings);
}
@@ -120,6 +120,11 @@ public enum CollectionNames {
/**
* Contains the known anniversaries
*/
ANNIVERSARIES;
ANNIVERSARIES,
/**
* Contains the Hashes for the {@link MarkPassingHashFingerprint}
*/
MARKPASSINGS;
}
@@ -236,6 +236,8 @@ import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.impl.MarkPassingRaceFingerprintImpl;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.FieldNames;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
@@ -247,10 +249,12 @@ import com.sap.sailing.domain.ranking.RankingMetricConstructor;
import com.sap.sailing.domain.ranking.RankingMetricsFactory;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
import com.sap.sailing.server.gateway.deserialization.impl.BoatJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.CompetitorWithBoatRefJsonDeserializer;
@@ -395,6 +399,14 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
return result;
}
/**
* @see #loadRaceIdentifier(Document)
*/
private void addRaceIdentifierToQuery(Document query, RaceIdentifier raceIdentifier) {
query.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
query.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
}
static void ensureIndicesOnWindTracks(MongoCollection<Document> windTracks) {
windTracks.createIndex(new Document(FieldNames.RACE_ID.name(), 1), new IndexOptions().name("windbyrace").background(false)); // for new programmatic access
@@ -3158,5 +3170,70 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
public TypeBasedServiceFinder<RaceTrackingConnectivityParametersHandler> getRaceTrackingConnectivityParamsServiceFinder() {
return raceTrackingConnectivityParamsServiceFinder;
}
@Override
public Map<RaceIdentifier, MarkPassingRaceFingerprint> loadFingerprintsForMarkPassingHashes() {
final MongoCollection<Document> markPassingsCollection = database.getCollection(CollectionNames.MARKPASSINGS.name());
markPassingsCollection.createIndex(new Document()
.append(FieldNames.EVENT_NAME.name(), 1)
.append(FieldNames.RACE_NAME.name(), 1),
new IndexOptions()
.unique(true)
.name("markpassingsbyeventandrace")
.background(false));
final Map<RaceIdentifier, MarkPassingRaceFingerprint> fingerprintHashMap = new HashMap<>();
for (final Document currentDocument : markPassingsCollection.find()) {
final Pair<RaceIdentifier, MarkPassingRaceFingerprint> fingerprint = loadMarkPassingsFingerprint(currentDocument);
if (fingerprint != null && fingerprint.getB() != null) {
fingerprintHashMap.put(fingerprint.getA(), fingerprint.getB());
}
}
return fingerprintHashMap;
}
private Pair<RaceIdentifier, MarkPassingRaceFingerprint> loadMarkPassingsFingerprint(final Document currentDocument) {
final RaceIdentifier raceIdentifier = loadRaceIdentifier(currentDocument);
MarkPassingRaceFingerprint fingerprint;
try {
final JSONObject json = Helpers.toJSONObjectSafe(
new JSONParser().parse(((Document) currentDocument.get(FieldNames.MARK_PASSINGS_FINGERPRINT.name())).toJson()));
fingerprint = new MarkPassingRaceFingerprintImpl(json);
} catch (JsonDeserializationException | ParseException e) {
logger.log(Level.WARNING, "Problem de-serializing mark passings from document; ignoring", e);
fingerprint = null;
}
return new Pair<>(raceIdentifier, fingerprint);
}
@Override
public Map<Competitor, Map<Waypoint, MarkPassing>> loadMarkPassings(RaceIdentifier raceIdentifier) {
final Map<Competitor, Map<Waypoint, MarkPassing>> result;
final Document query = new Document();
addRaceIdentifierToQuery(query, raceIdentifier);
final MongoCollection<Document> markPassingsCollection = database.getCollection(CollectionNames.MARKPASSINGS.name());
final Document doc = markPassingsCollection.find(query).first();
if (doc != null) {
result = new HashMap<>();
final List<Document> markPassingsDoc = doc.getList(FieldNames.MARK_PASSINGS.name(), Document.class);
for (final Document markPassingsForOneCompetitorDoc : markPassingsDoc) {
final Serializable competitorId = markPassingsForOneCompetitorDoc.get(FieldNames.COMPETITOR_ID.name(), Serializable.class);
final Competitor competitor = baseDomainFactory.getExistingCompetitorById(competitorId);
for (final Document markPassingForWaypoint : markPassingsForOneCompetitorDoc.getList(FieldNames.MARK_PASSINGS.name(), Document.class)) {
final Pair<Waypoint, MarkPassing> waypointAndMarkPassing = loadWaypointAndMarkPassing(competitor, markPassingForWaypoint);
result.computeIfAbsent(competitor, c->new HashMap<>()).put(waypointAndMarkPassing.getA(), waypointAndMarkPassing.getB());
}
}
} else {
result = null;
}
return result;
}
private Pair<Waypoint, MarkPassing> loadWaypointAndMarkPassing(Competitor competitor, Document markPassingForWaypoint) {
final Serializable waypointId = markPassingForWaypoint.get(FieldNames.WAYPOINT_ID.name(), Serializable.class);
final Waypoint waypoint = baseDomainFactory.getExistingWaypointById(waypointId);
final TimePoint timePoint = TimePoint.of(markPassingForWaypoint.getLong(FieldNames.TIME_AS_MILLIS.name()));
final MarkPassing markPassing = new MarkPassingImpl(timePoint, waypoint, competitor);
return new Pair<>(waypoint, markPassing);
}
}
@@ -126,10 +126,12 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithOtherTieBreaking
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.persistence.FieldNames;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -1964,5 +1966,34 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
document.append(FieldNames.ORC_CERTIFICATE.name(), createORCCertificateObject(event.getCertificate()));
return storeRegattaLogEvent(regattaLikeIdentifier, document);
}
private List<Document> storeMarkPassings(Map<Competitor, Map<Waypoint, MarkPassing>> markPassings) {
final List<Document> result = new ArrayList<>();
for (final Entry<Competitor, Map<Waypoint, MarkPassing>> e : markPassings.entrySet()) {
final Document competitorMarkPassings = new Document();
competitorMarkPassings.put(FieldNames.COMPETITOR_ID.name(), e.getKey().getId());
final List<Document> markPassingsList = new ArrayList<>();
for (final Entry<Waypoint, MarkPassing> f : e.getValue().entrySet()) {
final Document markPassingDoc = new Document();
markPassingDoc.put(FieldNames.WAYPOINT_ID.name(), f.getKey().getId());
markPassingDoc.put(FieldNames.TIME_AS_MILLIS.name(), f.getValue().getTimePoint().asMillis());
markPassingsList.add(markPassingDoc);
}
competitorMarkPassings.put(FieldNames.MARK_PASSINGS.name(), markPassingsList);
result.add(competitorMarkPassings);
}
return result;
}
public void storeMarkPassings(RaceIdentifier raceIdentification, MarkPassingRaceFingerprint fingerprint, Map<Competitor, Map<Waypoint, MarkPassing>> markPassings) {
MongoCollection<Document> markPassingCollection = database.getCollection(CollectionNames.MARKPASSINGS.name());
JSONObject fingerprintjson = fingerprint.toJson();
final Document result = new Document();
final Document fingerprintDoc = Document.parse(fingerprintjson.toString());
result.put(FieldNames.MARK_PASSINGS_FINGERPRINT.name(), fingerprintDoc);
storeRaceIdentifier(result, raceIdentification);
final List<Document> markPassingsDoc = storeMarkPassings(markPassings);
result.put(FieldNames.MARK_PASSINGS.name(), markPassingsDoc);
markPassingCollection.insertOne(result);
}
}
@@ -70,7 +70,7 @@ public interface SharedDomainFactory<RLR extends RaceLogResolver> extends Compet
*/
Waypoint createWaypoint(ControlPoint controlPoint, PassingInstruction passingInstruction);
Waypoint getExistingWaypointById(Waypoint waypointPrototype);
Waypoint getExistingWaypointById(Serializable waypointId);
/**
* Atomically checks if a waypoint by an equal {@link Waypoint#getId()} as <code>waypoint</code> exists in this domain factory's
@@ -271,16 +271,16 @@ public class SharedDomainFactoryImpl<RLR extends RaceLogResolver> implements Sha
}
@Override
public Waypoint getExistingWaypointById(Waypoint waypointPrototype) {
public Waypoint getExistingWaypointById(Serializable waypointId) {
synchronized (waypointCache) {
expungeStaleWaypointCacheEntries();
Waypoint result = null;
Reference<Waypoint> ref = waypointCache.get(waypointPrototype.getId());
Reference<Waypoint> ref = waypointCache.get(waypointId);
if (ref != null) {
result = ref.get();
if (result == null) {
// waypoint was finalized; remove entry from cache
waypointCache.remove(waypointPrototype.getId());
waypointCache.remove(waypointId);
}
}
return result;
@@ -0,0 +1,179 @@
package com.sap.sailing.domain.test.markpassinghash;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import org.json.simple.JSONObject;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
import com.sap.sailing.domain.test.OnlineTracTracBasedTest;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class MarkPassingRaceFingerprintJsonSerializationTest extends OnlineTracTracBasedTest {
DynamicTrackedRaceImpl trackedRace1;
DynamicTrackedRaceImpl trackedRace2;
MarkPassingCalculator calculator1;
MarkPassingCalculator calculator2;
public MarkPassingRaceFingerprintJsonSerializationTest() throws MalformedURLException, URISyntaxException {
super();
}
@Override
protected String getExpectedEventName() {
return "Academy Tracking 2011";
}
@Before
public void setUp() throws Exception {
super.setUp("event_20110505_SailingTea", // Semifinale
/* raceId */ "01ea3604-02ef-11e1-9efc-406186cbf87c", /* liveUri */ null, /* storedUri */ null,
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACECOURSE,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS, ReceiverType.SENSORDATA });
trackedRace1 = getTrackedRace();
super.setUp();
super.setUp("event_20110505_SailingTea", // Semifinale
/* raceId */ "01ea3604-02ef-11e1-9efc-406186cbf87c", /* liveUri */ null, /* storedUri */ null,
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACECOURSE,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS, ReceiverType.SENSORDATA });
trackedRace2 = getTrackedRace();
calculator1 = new MarkPassingCalculator(trackedRace1, false, false);
calculator2 = new MarkPassingCalculator(trackedRace2, false, false);
}
@Test
public void testJsonSerialization() {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
MarkPassingRaceFingerprint output1 = factory.fromJson(json1);
assertTrue("Original and de-serialized copy are equal", output1.matches(trackedRace1));
assertEquals(fingerprint1, output1);
assertEquals(fingerprint1.hashCode(), output1.hashCode());
}
@Test
public void testJsonSerializationWithChangesInMarkFixes() {
DynamicTrackedRaceImpl testRace = trackedRace2;
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
// Change of the race should result in a different hash
TimePoint epoch = new MillisecondsTimePoint(0l);
TimePoint now = MillisecondsTimePoint.now();
Map<String, Position> markPositions = new HashMap<String, Position>();
markPositions.put("CR Start (1)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("CR Start (2)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("Leeward mark", new DegreePosition(53.562145000000015, 10.009252));
markPositions.put("Luvtonne", new DegreePosition(53.560581899999995, 10.005657));
for (Waypoint w : trackedRace2.getRace().getCourse().getWaypoints()) {
for (Mark mark : w.getMarks()) {
assert markPositions.containsKey(mark.getName());
testRace.getOrCreateTrack(mark).addGPSFix(new GPSFixImpl(markPositions.get(mark.getName()), epoch));
testRace.getOrCreateTrack(mark).addGPSFix(new GPSFixImpl(markPositions.get(mark.getName()), now));
}
}
assertFalse(fingerprint1.matches(trackedRace2));
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(testRace);
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
@Test
public void testWaypointChangePassingInstruction() {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
Waypoint wp = trackedRace2.getRace().getCourse().getFirstWaypoint();
ControlPoint cP = wp.getControlPoint();
WaypointImpl wpNew = new WaypointImpl(cP, PassingInstruction.Gate);
trackedRace2.getRace().getCourse().removeWaypoint(0);
trackedRace2.getRace().getCourse().addWaypoint(0, wpNew);
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(trackedRace2);
assertFalse(fingerprint1.matches(trackedRace2));
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
@Test
public void testControlPointChange() {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
Mark gate1 = new MarkImpl("Gate1");
Mark gate2 = new MarkImpl("Gate2");
ControlPointWithTwoMarks cp = new ControlPointWithTwoMarksImpl(gate1, gate2, "cp", "");
Waypoint wpNew = new WaypointImpl(cp, PassingInstruction.None);
trackedRace2.getRace().getCourse().removeWaypoint(0);
trackedRace2.getRace().getCourse().addWaypoint(0, wpNew);
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(trackedRace2);
assertFalse(fingerprint1.matches(trackedRace2));
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
@Test
public void testCompetitorFixChange() {
DynamicTrackedRaceImpl testRace = trackedRace2;
final DynamicTrackedRaceImpl secureRace = trackedRace2;
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
Competitor firstCompetitor = trackedRace2.getRace().getCompetitors().iterator().next();
secureRace.getTrack(firstCompetitor).lockForRead();
GPSFixMoving firstFix;
try {
firstFix = testRace.getTrack(firstCompetitor).getRawFixes().iterator().next();
} finally {
secureRace.getTrack(firstCompetitor).unlockAfterRead();
}
SpeedWithBearing speed = firstFix.getSpeed();
Position pos = firstFix.getPosition();
TimePoint tp = firstFix.getTimePoint();
DegreePosition degPos = new DegreePosition(pos.getLatDeg() + 0.05, pos.getLngDeg() + 0.05);
GPSFixMoving gpsM = new GPSFixMovingImpl(degPos, tp, speed);
testRace.getTrack(firstCompetitor).add(gpsM, true);
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(testRace);
assertFalse(fingerprint1.matches(testRace));
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
}
@@ -18,6 +18,8 @@ Export-Package: com.sap.sailing.domain.base,
com.sap.sailing.domain.markpassingcalculation,
com.sap.sailing.domain.markpassingcalculation.impl,
com.sap.sailing.domain.markpassingcalculation.splining,
com.sap.sailing.domain.markpassinghash,
com.sap.sailing.domain.markpassinghash.impl;x-friends:="com.sap.sailing.domain.persistence",
com.sap.sailing.domain.masterdataimport,
com.sap.sailing.domain.orc,
com.sap.sailing.domain.orc.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.domain.test,com.sap.sailing.mongodb.test",
@@ -58,6 +58,11 @@ public class MarkPassingCalculator {
private final MarkPassingUpdateListener listener;
private final static ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor();
private final LinkedBlockingQueue<StorePositionUpdateStrategy> queue;
/**
* This CALCULATOR_VERSION variable indicates the Version of the {@link MarkPassingCalculator} and must be changed manually when changing the calculator.
* It should be changed by adding +1;
*/
private final int CALCULATOR_VERSION = 1;
/**
* An "end marker" that can be {@link #enqueueUpdate(StorePositionUpdateStrategy) enqueued} in order to tell the {@link Listen}
@@ -96,6 +101,9 @@ public class MarkPassingCalculator {
*/
private boolean listenerThreadStarted;
/**
* Remember to change the {@link CALCULATOR_VERSION} when changing the MarkPassingCalculator
*/
public MarkPassingCalculator(DynamicTrackedRace race, boolean doListen, boolean waitForInitialMarkPassingCalculation) {
if (doListen) {
queue = new LinkedBlockingQueue<>();
@@ -542,4 +550,8 @@ public class MarkPassingCalculator {
public String toString() {
return getClass().getName()+" for "+race+" with chooser "+chooser;
}
public int getCalculatorVersion() {
return CALCULATOR_VERSION;
}
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.markpassinghash;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* An instance of this class represents a composite fingerprint of those components of a {@link TrackedRace} that are
* relevant for computing the
* {@link TrackedRace#getMarkPassing(com.sap.sailing.domain.base.Competitor, com.sap.sailing.domain.base.Waypoint) mark
* passings} in the {@link MarkPassingCalculator}. It can be {@link #matches(TrackedRace) matched} against a {@link TrackedRace}
* instance to see whether the {@link TrackedRace} will produce a set of {@link MarkPassing}s equal to that of the {@link TrackedRace}
* from which this fingerprint was produced.<p>
*
* To produce a fingerprint from a {@link TrackedRace} or from a JSON representation use {@link MarkPassingRaceFingerprintFactory}.<p>
*
* The {@link #equals(Object)} and {@link #hashCode()} methods are defined based on the contents of this fingerprint.
*
* @author Fabian Kallenbach (i550803)
* @author Axel Uhl (d043530)
*/
public interface MarkPassingRaceFingerprint {
/**
* Returns a {@link JSONObject} of the hash values.
*/
JSONObject toJson();
/**
* Incrementally computes the composite fingerprint of the {@code trackedRace} and compares to this fingerprint
* component by component. The fingerprint components are computed in ascending order of computational complexity,
* trying to fail early / fast.
*
* @return {@code true} if the {@code trackedRace} produces a fingerprint equal to this one if passed to
* {@link MarkPassingRaceFingerprintFactory#createFingerprint(TrackedRace)}
*/
boolean matches(TrackedRace trackedRace);
}
@@ -0,0 +1,25 @@
package com.sap.sailing.domain.markpassinghash;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.markpassinghash.impl.MarkPassingRaceFingerprintFactoryImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* Factory for the creation of a {@link MarkPassingRaceFingerprint}.
*
* @author Fabian Kallenbach (i550803)
*/
public interface MarkPassingRaceFingerprintFactory {
MarkPassingRaceFingerprintFactory INSTANCE = new MarkPassingRaceFingerprintFactoryImpl();
/**
* Creates a {@link MarkPassingRaceFingerprint} out of a given {@link TrackedRace}.
*/
MarkPassingRaceFingerprint createFingerprint(TrackedRace trackedRace);
/**
* Creates a {@link MarkPassingRaceFingerprint} out of a given {@link JSONObject}.
*/
MarkPassingRaceFingerprint fromJson(JSONObject json);
}
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.markpassinghash.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
import com.sap.sailing.domain.tracking.TrackedRace;
public class MarkPassingRaceFingerprintFactoryImpl implements MarkPassingRaceFingerprintFactory {
@Override
public MarkPassingRaceFingerprint createFingerprint(TrackedRace trackedRace) {
return new MarkPassingRaceFingerprintImpl(trackedRace);
}
@Override
public MarkPassingRaceFingerprint fromJson(JSONObject json) {
return new MarkPassingRaceFingerprintImpl(json);
}
}
@@ -0,0 +1,256 @@
package com.sap.sailing.domain.markpassinghash.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
public class MarkPassingRaceFingerprintImpl implements MarkPassingRaceFingerprint {
private final int calculatorVersion;
private final int competitorHash;
private final TimePoint startOfTracking;
private final TimePoint startTimeReceived;
private final TimePoint endOfTracking;
private final TimePoint startTimeFromRaceLog;
private final TimePoint finishTimeFromRaceLog;
private final int waypointsHash;
private final int numberOfGPSFixes;
private final int gpsFixesHash;
private static enum JSON_FIELDS {
COMPETITOR_HASH, START_OF_TRACKING_AS_MILLIS, END_OF_TRACKING_AS_MILLIS, START_TIME_RECEIVED_AS_MILLIS, START_TIME_FROM_RACE_LOG_AS_MILLIS, FINISH_TIME_FROM_RACE_LOG_AS_MILLIS, WAYPOINTS_HASH, NUMBEROFGPSFIXES, GPSFIXES_HASH, RACE_ID, CALCULATOR_VERSION
};
public MarkPassingRaceFingerprintImpl(TrackedRace trackedRace) {
this.calculatorVersion = getCalculatorVersion(trackedRace);
this.competitorHash = calculateHashForCompetitors(trackedRace);
this.startOfTracking = trackedRace.getStartOfTracking();
this.endOfTracking = trackedRace.getEndOfTracking();
this.startTimeReceived = trackedRace.getStartTimeReceived();
final Pair<TimePoint, TimePoint> startAndFinishedTimeFromRaceLogs = trackedRace
.getStartAndFinishedTimeFromRaceLogs();
this.startTimeFromRaceLog = startAndFinishedTimeFromRaceLogs == null ? null
: startAndFinishedTimeFromRaceLogs.getA();
this.finishTimeFromRaceLog = startAndFinishedTimeFromRaceLogs == null ? null
: startAndFinishedTimeFromRaceLogs.getB();
this.waypointsHash = calculateHashForWaypoints(trackedRace);
this.numberOfGPSFixes = calculateHashForNumberOfGPSFixes(trackedRace);
this.gpsFixesHash = calculateHashForGPSFixes(trackedRace);
}
public MarkPassingRaceFingerprintImpl(JSONObject json) {
this.calculatorVersion = ((Number) json.get(JSON_FIELDS.CALCULATOR_VERSION.name())).intValue();
this.competitorHash = ((Number) json.get(JSON_FIELDS.COMPETITOR_HASH.name())).intValue();
this.startOfTracking = TimePoint.of((Long) json.get(JSON_FIELDS.START_OF_TRACKING_AS_MILLIS.name()));
this.endOfTracking = TimePoint.of((Long) json.get(JSON_FIELDS.END_OF_TRACKING_AS_MILLIS.name()));
this.startTimeReceived = TimePoint.of((Long) json.get(JSON_FIELDS.START_TIME_RECEIVED_AS_MILLIS.name()));
this.startTimeFromRaceLog = TimePoint
.of((Long) json.get(JSON_FIELDS.START_TIME_FROM_RACE_LOG_AS_MILLIS.name()));
this.finishTimeFromRaceLog = TimePoint
.of((Long) json.get(JSON_FIELDS.FINISH_TIME_FROM_RACE_LOG_AS_MILLIS.name()));
this.waypointsHash = ((Number) json.get(JSON_FIELDS.WAYPOINTS_HASH.name())).intValue();
this.numberOfGPSFixes = ((Number) json.get(JSON_FIELDS.NUMBEROFGPSFIXES.name())).intValue();
this.gpsFixesHash = ((Number) json.get(JSON_FIELDS.GPSFIXES_HASH.name())).intValue();
}
@Override
public JSONObject toJson() {
JSONObject result = new JSONObject();
result.put(JSON_FIELDS.CALCULATOR_VERSION.name(), calculatorVersion);
result.put(JSON_FIELDS.COMPETITOR_HASH.name(), competitorHash);
result.put(JSON_FIELDS.START_OF_TRACKING_AS_MILLIS.name(),
startOfTracking == null ? null : startOfTracking.asMillis());
result.put(JSON_FIELDS.END_OF_TRACKING_AS_MILLIS.name(),
endOfTracking == null ? null : endOfTracking.asMillis());
result.put(JSON_FIELDS.START_TIME_RECEIVED_AS_MILLIS.name(),
startTimeReceived == null ? null : startTimeReceived.asMillis());
result.put(JSON_FIELDS.START_TIME_FROM_RACE_LOG_AS_MILLIS.name(),
startTimeFromRaceLog == null ? null : startTimeFromRaceLog.asMillis());
result.put(JSON_FIELDS.FINISH_TIME_FROM_RACE_LOG_AS_MILLIS.name(),
finishTimeFromRaceLog == null ? null : finishTimeFromRaceLog.asMillis());
result.put(JSON_FIELDS.WAYPOINTS_HASH.name(), waypointsHash);
result.put(JSON_FIELDS.NUMBEROFGPSFIXES.name(), numberOfGPSFixes);
result.put(JSON_FIELDS.GPSFIXES_HASH.name(), gpsFixesHash);
return result;
}
@Override
public boolean matches(TrackedRace trackedRace) {
final boolean result;
if (!Util.equalsWithNull(calculatorVersion, getCalculatorVersion(trackedRace))) {
result = false;
} else if (!Util.equalsWithNull(startOfTracking, trackedRace.getStartOfTracking())) {
result = false;
} else if (!Util.equalsWithNull(endOfTracking, trackedRace.getEndOfTracking())) {
result = false;
} else if (!Util.equalsWithNull(startTimeReceived, trackedRace.getStartTimeReceived())) {
result = false;
} else {
final Pair<TimePoint, TimePoint> startAndFinishedTimeFromRaceLogs = trackedRace
.getStartAndFinishedTimeFromRaceLogs();
if (!Util.equalsWithNull(startTimeFromRaceLog,
startAndFinishedTimeFromRaceLogs == null ? null : startAndFinishedTimeFromRaceLogs.getA())) {
result = false;
} else if (!Util.equalsWithNull(finishTimeFromRaceLog,
startAndFinishedTimeFromRaceLogs == null ? null : startAndFinishedTimeFromRaceLogs.getB())) {
result = false;
} else if (waypointsHash != calculateHashForWaypoints(trackedRace)) {
result = false;
} else if (competitorHash != calculateHashForCompetitors(trackedRace)) {
result = false;
} else if (numberOfGPSFixes != calculateHashForNumberOfGPSFixes(trackedRace)) {
result = false;
} else if (gpsFixesHash != calculateHashForGPSFixes(trackedRace)) {
result = false;
} else {
result = true;
}
}
return result;
}
private int getCalculatorVersion(TrackedRace trackedRace) {
MarkPassingCalculator calculator = new MarkPassingCalculator((DynamicTrackedRace) trackedRace, false, false);
int result = calculator.getCalculatorVersion();
return result;
}
private int calculateHashForCompetitors(TrackedRace trackedRace) {
int hashForCompetitors = 1023;
for (Competitor c : trackedRace.getRace().getCompetitors()) {
hashForCompetitors = hashForCompetitors ^ c.getId().hashCode();
}
return hashForCompetitors;
}
private int calculateHashForNumberOfGPSFixes(TrackedRace trackedRace) {
int count = 0;
for (Mark m : trackedRace.getMarks()) {
count += trackedRace.getTrack(m).size();
}
for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
count += trackedRace.getTrack(competitor).size();
}
return count;
}
private int calculateHashForGPSFixes(TrackedRace trackedRace) {
int res = 511;
for (Mark m : trackedRace.getMarks()) {
final GPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getTrack(m);
markTrack.lockForRead();
try {
for (GPSFix gf : markTrack.getFixes()) {
res = res ^ gf.getTimePoint().hashCode();
res = res ^ gf.getPosition().hashCode();
}
} finally {
markTrack.unlockAfterRead();
}
}
for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
final GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = trackedRace.getTrack(competitor);
competitorTrack.lockForRead();
try {
for (GPSFixMoving gfm : competitorTrack.getFixes()) {
res = res ^ gfm.getTimePoint().hashCode();
res = res ^ gfm.getPosition().hashCode();
res = res ^ gfm.getSpeed().getBearing().hashCode();
res = res ^ Double.hashCode(gfm.getSpeed().getKnots());
}
} finally {
competitorTrack.unlockAfterRead();
}
}
return res;
}
private int calculateHashForWaypoints(TrackedRace trackedRace) {
Iterable<Waypoint> waypoints = trackedRace.getRace().getCourse().getWaypoints();
int res = 0;
for (Waypoint p : waypoints) {
Iterable<Mark> marks = p.getMarks();
for (Mark m : marks) {
res = res ^ m.getId().hashCode();
}
res = res ^ p.getPassingInstructions().hashCode();
res = (res << 5) - res; // we want to detect changes in order
}
return res;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + calculatorVersion;
result = prime * result + competitorHash;
result = prime * result + ((endOfTracking == null) ? 0 : endOfTracking.hashCode());
result = prime * result + ((finishTimeFromRaceLog == null) ? 0 : finishTimeFromRaceLog.hashCode());
result = prime * result + gpsFixesHash;
result = prime * result + numberOfGPSFixes;
result = prime * result + ((startOfTracking == null) ? 0 : startOfTracking.hashCode());
result = prime * result + ((startTimeFromRaceLog == null) ? 0 : startTimeFromRaceLog.hashCode());
result = prime * result + ((startTimeReceived == null) ? 0 : startTimeReceived.hashCode());
result = prime * result + waypointsHash;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MarkPassingRaceFingerprintImpl other = (MarkPassingRaceFingerprintImpl) obj;
if (calculatorVersion != other.calculatorVersion)
return false;
if (competitorHash != other.competitorHash)
return false;
if (endOfTracking == null) {
if (other.endOfTracking != null)
return false;
} else if (!endOfTracking.equals(other.endOfTracking))
return false;
if (finishTimeFromRaceLog == null) {
if (other.finishTimeFromRaceLog != null)
return false;
} else if (!finishTimeFromRaceLog.equals(other.finishTimeFromRaceLog))
return false;
if (gpsFixesHash != other.gpsFixesHash)
return false;
if (numberOfGPSFixes != other.numberOfGPSFixes)
return false;
if (startOfTracking == null) {
if (other.startOfTracking != null)
return false;
} else if (!startOfTracking.equals(other.startOfTracking))
return false;
if (startTimeFromRaceLog == null) {
if (other.startTimeFromRaceLog != null)
return false;
} else if (!startTimeFromRaceLog.equals(other.startTimeFromRaceLog))
return false;
if (startTimeReceived == null) {
if (other.startTimeReceived != null)
return false;
} else if (!startTimeReceived.equals(other.startTimeReceived))
return false;
if (waypointsHash != other.waypointsHash)
return false;
return true;
}
}
@@ -4,6 +4,13 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.Timed;
/**
* Mark passings have an {@link #equals(Object)} and {@link #hashCode()} implementation based
* on their content, not their identity.
*
* @author Axel Uhl (d043530)
*
*/
public interface MarkPassing extends Timed {
Waypoint getWaypoint();
@@ -1,16 +0,0 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.common.PassingInstruction;
/**
* The Waypoint before an Offset Mark (see {@link PassingInstruction}) contains a MarkPassing for this mark, which does
* not start an own leg but can be interesting for analysis.
*
* @author Nicolas Klose
*
*/
public interface MarkPassingForOffsetWaypoint extends MarkPassing {
MarkPassing getOffsetPassing();
}
@@ -1,7 +1,10 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
@@ -484,6 +487,21 @@ public interface TrackedRace
* {@link MarkPassingCalculator }to block the thread until all calculations will be finished.
*/
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor, boolean waitForLatestUpdates);
/**
* Fetches all mark passings for all competitors from this tracked race.
*
* @see #getMarkPassings(Competitor, boolean)
*/
default Map<Competitor, Map<Waypoint, MarkPassing>> getMarkPassings(boolean waitForLatestUpdates) {
final Map<Competitor, Map<Waypoint, MarkPassing>> result = new HashMap<>();
for (final Competitor competitor : getRace().getCompetitors()) {
for (final MarkPassing markPassing : getMarkPassings(competitor, waitForLatestUpdates)) {
result.computeIfAbsent(competitor, k->new HashMap<>()).put(markPassing.getWaypoint(), markPassing);
}
}
return result;
}
/**
* This obtains the course's read lock before asking for the read lock for the <code>markPassings</code> structure.
@@ -1,28 +0,0 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPassingForOffsetWaypoint;
import com.sap.sse.common.TimePoint;
public class MarkPassingForOffsetImpl extends MarkPassingImpl implements MarkPassingForOffsetWaypoint {
private static final long serialVersionUID = -2168312586388285697L;
private final MarkPassing markpassingforOffset;
public MarkPassingForOffsetImpl(TimePoint timePoint, Waypoint waypoint, Competitor competitor, MarkPassing markPassingforOffset) {
super(timePoint, waypoint, competitor);
this.markpassingforOffset = markPassingforOffset;
}
@Override
public MarkPassing getOffsetPassing() {
return markpassingforOffset;
}
}
@@ -43,4 +43,41 @@ public class MarkPassingImpl implements MarkPassing {
return this;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((competitor == null) ? 0 : competitor.hashCode());
result = prime * result + ((timePoint == null) ? 0 : timePoint.hashCode());
result = prime * result + ((waypoint == null) ? 0 : waypoint.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MarkPassingImpl other = (MarkPassingImpl) obj;
if (competitor == null) {
if (other.competitor != null)
return false;
} else if (!competitor.equals(other.competitor))
return false;
if (timePoint == null) {
if (other.timePoint != null)
return false;
} else if (!timePoint.equals(other.timePoint))
return false;
if (waypoint == null) {
if (other.waypoint != null)
return false;
} else if (!waypoint.equals(other.waypoint))
return false;
return true;
}
}
@@ -120,7 +120,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.MODULE_NAME" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html -startupUrl /gwt/AdminConsole.html -startupUrl /gwt/PairingList.html -startupUrl /gwt/LeaderboardEditing.html -startupUrl /gwt/Leaderboard.html -startupUrl /gwt/Spectator.html -startupUrl /gwt/EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html -startupUrl /gwt/RegattaOverview.html -startupUrl /gwt/DataMining.html -startupUrl /gwt/Simulator.html -startupUrl /gwt/VideoPopup.html -startupUrl /gwt/AutoPlay.html -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.ui.PairingList com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.autoplay.AutoPlay"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -style PRETTY -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html -startupUrl /gwt/AdminConsole.html -startupUrl /gwt/PairingList.html -startupUrl /gwt/LeaderboardEditing.html -startupUrl /gwt/Leaderboard.html -startupUrl /gwt/Spectator.html -startupUrl /gwt/EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html -startupUrl /gwt/RegattaOverview.html -startupUrl /gwt/DataMining.html -startupUrl /gwt/Simulator.html -startupUrl /gwt/VideoPopup.html -startupUrl /gwt/AutoPlay.html -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.ui.PairingList com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.autoplay.AutoPlay"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XX:+UseG1GC -XX:-UseStringDeduplication -Dgwt.watchFileChanges=false -Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false -Xmx4096m"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work"/>
@@ -0,0 +1,78 @@
package com.sap.sailing.mongodb.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import com.mongodb.MongoException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.test.OnlineTracTracBasedTest;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.mongodb.MongoDBConfiguration;
public class MarkPassingRaceFingerprintConversionTest extends OnlineTracTracBasedTest {
DynamicTrackedRaceImpl trackedRace1;
DynamicTrackedRaceImpl trackedRace2;
private final MongoDBConfiguration dbConfiguration;
public MarkPassingRaceFingerprintConversionTest() throws MalformedURLException, URISyntaxException {
super();
dbConfiguration = MongoDBConfiguration.getDefaultTestConfiguration();
}
private MongoClient newMongo() throws UnknownHostException, MongoException {
return MongoClients.create(dbConfiguration.getMongoClientURI());
}
@Override
protected String getExpectedEventName() {
return "Academy Tracking 2011";
}
@Before
public void setUp() throws Exception {
newMongo().getDatabase(dbConfiguration.getDatabaseName()).drop();
super.setUp("event_20110505_SailingTea", // Semifinale
/* raceId */ "01ea3604-02ef-11e1-9efc-406186cbf87c", /* liveUri */ null, /* storedUri */ null,
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACECOURSE,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS, ReceiverType.SENSORDATA });
trackedRace1 = getTrackedRace();
}
@Test
public void testStoringToAndLoadingFromMongo() throws UnknownHostException, MongoException {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint.matches(trackedRace1));
MongoClient myFirstMongo = newMongo();
MongoDatabase firstDatabase = myFirstMongo.getDatabase(dbConfiguration.getDatabaseName());
RaceIdentifier raceIdentifier = trackedRace1.getRaceIdentifier();
final Map<Competitor, Map<Waypoint, MarkPassing>> markPassings = trackedRace1.getMarkPassings(/* waitForLatestUpdates */ true);
new MongoObjectFactoryImpl(firstDatabase).storeMarkPassings(raceIdentifier, fingerprint, markPassings);
DomainObjectFactory dF = PersistenceFactory.INSTANCE.getDomainObjectFactory(dbConfiguration.getService(), getDomainFactory().getBaseDomainFactory());
Map<RaceIdentifier, MarkPassingRaceFingerprint> fingerprintHashMap = dF.loadFingerprintsForMarkPassingHashes();
MarkPassingRaceFingerprint fingerprintAfterDB = fingerprintHashMap.get(trackedRace1.getRaceIdentifier());
assertTrue("Original and de-serialized copy are equal", fingerprintAfterDB.matches(trackedRace1));
final Map<Competitor, Map<Waypoint, MarkPassing>> markPassingsLoaded = dF.loadMarkPassings(trackedRace1.getRaceIdentifier());
assertEquals(markPassings, markPassingsLoaded);
}
}
@@ -0,0 +1,46 @@
package com.sap.sailing.server.interfaces;
import java.util.Map;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A registry to provide access to the {@link MarkPassingRaceFingerprint}s at the different levels of the domain model.
* The following use cases exist:
* <ul>
* <li>Check for a {@link RaceIdentifier} whether mark passings have been stored persistently for the race identified by
* that identifier; the result is a {@link MarkPassingRaceFingerprint} which can may or may not be
* {@link MarkPassingRaceFingerprint#matches(com.sap.sailing.domain.tracking.TrackedRace) matched} by a
* {@link TrackedRace}</li>
* <li>If a matching fingerprint is stored in this registry for a race then {@link #loadMarkPassings(RaceIdentifier)
* loading the mark passings} for that race and {@link DynamicTrackedRace#updateMarkPassings(Competitor, Iterable)
* updating it to the tracked race} is the logical next step.</li>
* <li>When a tracked race has reached a "stable" state, e.g., because it's {@link TrackedRace#getEndOfTracking()
* end-of-tracking} time point has passed or its {@link TrackedRace#getStatus() status} has changed to
* {@link TrackedRaceStatusEnum#FINISHED} then the latest mark passing calculation results can be
* {@link #storeMarkPassings(RaceIdentifier, MarkPassingRaceFingerprint, Map) stored} together with the fingerprint
* computed from the tracked race.</li>
* </ul>
*
* @author Fabian Kallenbach (i550803)
*/
public interface MarkPassingRaceFingerprintRegistry {
void storeMarkPassings(RaceIdentifier raceIdentifier, MarkPassingRaceFingerprint fingerprint, Map<Competitor, Map<Waypoint, MarkPassing>> markPassings);
MarkPassingRaceFingerprint getMarkPassingRaceFingerprint(RaceIdentifier raceIdentifier);
/**
* <b>Precondition</b>: {@code fingerprint}.{@link MarkPassingRaceFingerprint#matches(com.sap.sailing.domain.tracking.TrackedRace) matches}
* {@code (}{@link #getMarkPassingRaceFingerprint(RaceIdentifier) getMarkPassingRaceFingerprint(raceIdentifier)}{@code )}<p>
*
* If a mark passing race fingerprint is known for {@code raceIdentifier} in this registry and the
*/
Map<Competitor, Map<Waypoint, MarkPassing>> loadMarkPassings(RaceIdentifier raceIdentifier);
}
@@ -187,6 +187,7 @@ import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
@@ -255,6 +256,7 @@ import com.sap.sailing.server.impl.preferences.model.CompetitorNotificationPrefe
import com.sap.sailing.server.interfaces.CourseAndMarkConfigurationFactory;
import com.sap.sailing.server.interfaces.DataImportLockWithProgress;
import com.sap.sailing.server.interfaces.KeywordQueryWithOptionalEventQualification;
import com.sap.sailing.server.interfaces.MarkPassingRaceFingerprintRegistry;
import com.sap.sailing.server.interfaces.RacingEventService;
import com.sap.sailing.server.interfaces.RacingEventServiceOperation;
import com.sap.sailing.server.interfaces.SimulationService;
@@ -353,7 +355,8 @@ import com.sap.sse.util.ThreadPoolUtil;
public class RacingEventServiceImpl
extends AbstractReplicableWithObjectInputStream<RacingEventService, RacingEventServiceOperation<?>>
implements RacingEventService, ClearStateTestSupport, RegattaListener, LeaderboardRegistry, Replicator {
implements RacingEventService, ClearStateTestSupport, RegattaListener, LeaderboardRegistry, MarkPassingRaceFingerprintRegistry,
Replicator {
private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
/**
@@ -446,6 +449,8 @@ implements RacingEventService, ClearStateTestSupport, RegattaListener, Leaderboa
private final ConcurrentHashMap<String, Set<LeaderboardGroup>> leaderboardGroupsByName;
private final ConcurrentHashMap<UUID, LeaderboardGroup> leaderboardGroupsByID;
private final ConcurrentHashMap<RaceIdentifier, MarkPassingRaceFingerprint> markPassingRaceFingerprints;
/**
* See {@link #leaderboardsByNameLock}
@@ -879,8 +884,9 @@ implements RacingEventService, ClearStateTestSupport, RegattaListener, Leaderboa
leaderboardGroupsByName = new ConcurrentHashMap<>();
leaderboardGroupsByID = new ConcurrentHashMap<>();
leaderboardGroupsByNameLock = new NamedReentrantReadWriteLock("leaderboardGroupsByName for " + this, /* fair */false);
leaderboardsByName = new ConcurrentHashMap<String, Leaderboard>();
leaderboardsByName = new ConcurrentHashMap<>();
leaderboardsByNameLock = new NamedReentrantReadWriteLock("leaderboardsByName for " + this, /* fair */false);
markPassingRaceFingerprints = new ConcurrentHashMap<>();
courseListeners = new ConcurrentHashMap<>();
persistentRegattasForRaceIDs = new ConcurrentHashMap<>();
simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this);
@@ -911,6 +917,7 @@ implements RacingEventService, ClearStateTestSupport, RegattaListener, Leaderboa
loadMediaLibary();
loadStoredDeviceConfigurations();
loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh();
loadMarkPassingRaceFingerprints();
// Stores all events which run through a data migration
// Remark: must be called after loadLinksFromEventsToLeaderboardGroups(), otherwise would loose the Event -> LeaderboardGroup relation
for (Pair<Event, Boolean> eventAndRequireStoreFlag : loadedEventsWithRequireStoreFlag) {
@@ -932,6 +939,32 @@ implements RacingEventService, ClearStateTestSupport, RegattaListener, Leaderboa
}
}
private void loadMarkPassingRaceFingerprints() {
markPassingRaceFingerprints.putAll(domainObjectFactory.loadFingerprintsForMarkPassingHashes());
}
@Override
public void storeMarkPassings(RaceIdentifier raceIdentifier, MarkPassingRaceFingerprint fingerprint,
Map<Competitor, Map<Waypoint, MarkPassing>> markPassings) {
mongoObjectFactory.storeMarkPassings(raceIdentifier, fingerprint, markPassings);
}
@Override
public MarkPassingRaceFingerprint getMarkPassingRaceFingerprint(RaceIdentifier raceIdentifier) {
return markPassingRaceFingerprints.get(raceIdentifier);
}
@Override
public Map<Competitor, Map<Waypoint, MarkPassing>> loadMarkPassings(RaceIdentifier raceIdentifier) {
final Map<Competitor, Map<Waypoint, MarkPassing>> result;
if (markPassingRaceFingerprints.containsKey(raceIdentifier)) {
result = domainObjectFactory.loadMarkPassings(raceIdentifier);
} else {
result = null;
}
return result;
}
public ClassLoaderRegistry getMasterDataClassLoaders() {
return masterDataClassLoaders;
}