Merge branch 'master' into bug3237

Conflicts:
	java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java
This commit is contained in:
Axel Uhl committed 2015-08-18 16:49:27 +02:00
commit b9ef73b9ad
29 files changed
+478 -85

No files matched your search

@@ -51,6 +51,7 @@ public enum BoatClassMasterdata {
LASER_RADIAL ("Laser Radial", true, 4.19, 1.41, BoatHullType.MONOHULL, false, "LAR", "Laser RAD", "RAD", "Radial"),
LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, false, "Laser", "LSR"),
LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, false, "LSB3", "SB20"),
LAGO_26 ("Lago 26", true, 7.95, 2.50, BoatHullType.MONOHULL, true, "Lago26"),
MELGES_24 ("Melges 24", true, 7.32, 2.50, BoatHullType.MONOHULL, true, "Melges-24", "M24"),
MINI_TRANSAT ("Mini Transat 6.50", true, 6.50, 3.00, BoatHullType.MONOHULL, true, "Mini Transat"),
MUSTO_SKIFF ("Musto Skiff", true, 4.55, 1.35, BoatHullType.MONOHULL, true, "Musto Performance Skiff", "MPS", "Musto"),
@@ -34,14 +34,18 @@ public class MongoRaceLogStoreImpl implements RaceLogStore {
result = raceLogCache.get(identifier);
} else {
result = domainObjectFactory.loadRaceLog(identifier);
MongoRaceLogStoreVisitor listener = new MongoRaceLogStoreVisitor(identifier, mongoObjectFactory);
listeners.put(result, listener);
result.addListener(listener);
addListener(identifier, result);
raceLogCache.put(identifier, result);
}
return result;
}
private void addListener(RaceLogIdentifier identifier, final RaceLog raceLog) {
MongoRaceLogStoreVisitor listener = new MongoRaceLogStoreVisitor(identifier, mongoObjectFactory);
listeners.put(raceLog, listener);
raceLog.addListener(listener);
}
@Override
public void removeRaceLog(RaceLogIdentifier identifier) {
logger.info("Removing race log "+identifier+" from the database");
@@ -57,4 +61,10 @@ public class MongoRaceLogStoreImpl implements RaceLogStore {
}
}
@Override
public void addImportedRaceLog(RaceLog raceLog, RaceLogIdentifier identifier) {
addListener(identifier, raceLog);
raceLogCache.put(identifier, raceLog);
}
}
@@ -27,16 +27,26 @@ public class MongoRegattaLogStoreImpl implements RegattaLogStore {
result = regattaLogCache.get(identifier);
} else {
result = domainObjectFactory.loadRegattaLog(identifier);
MongoRegattaLogStoreVisitor listener = new MongoRegattaLogStoreVisitor(identifier, mongoObjectFactory);
result.addListener(listener);
addListener(identifier, result);
regattaLogCache.put(identifier, result);
}
return result;
}
private void addListener(RegattaLikeIdentifier identifier, final RegattaLog regattaLog) {
MongoRegattaLogStoreVisitor listener = new MongoRegattaLogStoreVisitor(identifier, mongoObjectFactory);
regattaLog.addListener(listener);
}
@Override
public void removeRegattaLog(RegattaLikeIdentifier identifier) {
regattaLogCache.remove(identifier);
mongoObjectFactory.removeRegattaLog(identifier);
}
@Override
public void addImportedRegattaLog(RegattaLog regattaLog, RegattaLikeIdentifier identifier) {
addListener(identifier, regattaLog);
regattaLogCache.put(identifier, regattaLog);
}
}
@@ -191,12 +191,38 @@ public class MongoGPSFixStoreImpl implements MongoGPSFixStore {
loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/);
}
@Override
public void loadCompetitorTrack(DynamicGPSFixTrack<Competitor, GPSFixMoving> track, AbstractLog<?, ?> log,
Competitor competitor, TimePoint start, TimePoint end) throws TransformationException {
List<DeviceMapping<Competitor>> mappings = new DeviceCompetitorMappingFinder<>(log).analyze().get(competitor);
if (mappings != null) {
for (DeviceMapping<Competitor> mapping : mappings) {
final TimePoint from = Util.getLatestOfTimePoints(start, mapping.getTimeRange().from());
final TimePoint to = Util.getEarliestOfTimePoints(end, mapping.getTimeRange().to());
loadTrack(track, mapping.getDevice(), from, to, true /*inclusive*/);
}
}
}
@Override
public void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, DeviceMapping<Mark> mapping)
throws TransformationException, NoCorrespondingServiceRegisteredException {
loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/);
}
@Override
public void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, AbstractLog<?, ?> log, Mark mark,
TimePoint start, TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException {
List<DeviceMapping<Mark>> mappings = new DeviceMarkMappingFinder<>(log).analyze().get(mark);
if (mappings != null) {
for (DeviceMapping<Mark> mapping : mappings) {
final TimePoint from = Util.getLatestOfTimePoints(start, mapping.getTimeRange().from());
final TimePoint to = Util.getEarliestOfTimePoints(end, mapping.getTimeRange().to());
loadTrack(track, mapping.getDevice(), from, to, true /*inclusive*/);
}
}
}
@Override
public void loadTrack(DynamicGPSFixTrack<WithID, ?> track, DeviceMapping<WithID> mapping)
throws NoCorrespondingServiceRegisteredException, TransformationException {
@@ -18,4 +18,5 @@ Import-Package: com.sap.sailing.domain.abstractlog.regatta.events,
com.sap.sailing.domain.persistence.racelog.tracking.impl,
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.server.impl,
com.sap.sse.concurrent,
javax.servlet.http;version="2.5.0"
@@ -79,7 +79,7 @@ public class AbstractGPSFixStoreTest {
mark, 0, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)));
}
protected void testLength(Track<?> track, long expected) {
protected void testNumberOfRawFixes(Track<?> track, long expected) {
track.lockForRead();
assertEquals(expected, size(track.getRawFixes()));
track.unlockAfterRead();
@@ -40,6 +40,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
@@ -49,6 +50,49 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
private Integer previouslyLoading = null;
private int numFixesReceived = 0;
@Test
public void doesRaceLoadOnlyBetweenStartAndEndOfTracking() throws TransformationException,
NoCorrespondingServiceRegisteredException, InterruptedException {
Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
DeviceIdentifier markDevice = new SmartphoneImeiIdentifier("imei2");
Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark),
new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 }));
map(comp, device, 0, 10000);
map(mark, markDevice, 0, 10000);
store.storeFix(device, createFix(100, 10, 20, 30, 40)); // before
store.storeFix(device, createFix(1100, 10, 20, 30, 40)); // in
store.storeFix(device, createFix(2100, 10, 20, 30, 40)); // after
store.storeFix(markDevice, createFix(100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(1100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(2100, 10, 20, 30, 40));
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */
null, /* endDate */null, null, null, "a", null));
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(1000));
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(2000));
trackedRace.attachRaceLog(raceLog);
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(raceLog);
testNumberOfRawFixes(trackedRace.getTrack(comp), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
// now extend the tracking interval of the tracked race and assert that the additional fixes are loaded
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(2500), /* wait for fixes to load */ true);
testNumberOfRawFixes(trackedRace.getTrack(comp), 2);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(0), /* wait for fixes to load */ true);
testNumberOfRawFixes(trackedRace.getTrack(comp), 3);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 3);
}
@Test
public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException,
InterruptedException {
@@ -88,10 +132,10 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
trackedRace.attachRaceLog(raceLog);
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(raceLog);
testLength(trackedRace.getTrack(comp), 10002);
testLength(trackedRace.getTrack(comp2), 1);
testLength(trackedRace.getOrCreateTrack(mark), 1);
testLength(trackedRace.getOrCreateTrack(mark2), 1);
testNumberOfRawFixes(trackedRace.getTrack(comp), 10002);
testNumberOfRawFixes(trackedRace.getTrack(comp2), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark2), 1);
}
@Test
@@ -53,6 +53,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.MaxSpeedCache;
import com.sap.sailing.domain.tracking.impl.TrackImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Timed;
@@ -894,6 +895,55 @@ public class TrackTest {
assertEquals(toReachInOneThirdOfTimeBetweenFixesInMillis.getMeters(), distance3_new.getMeters()-distance2_new.getMeters(), 0.01);
assertEquals(toReachInOneThirdOfTimeBetweenFixesInMillis.getMeters(), distance4_new.getMeters()-distance3_new.getMeters(), 0.01);
}
/**
* Bug 3022 describes a {@link StackOverflowError} that occurs during distance calculations on a {@link GPSFixTrackImpl}.
* This test tries to reproduce this behavior, as a starting point for a fix. The recursion happened while a long-distance
* race was running. Can there be a certain fix insertion pattern that, combined with a certain request pattern,
* produces degenerated cache contents? The race had a live delay of 660s. The trackers were configured to sample every
* 120s and to send every 600s. This could mean that for some 60s the live requests were "ahead" of the trackers.
*/
@Test
public void testDistanceCacheIncrementalFillUpAndRecursion() {
TimePoint start = MillisecondsTimePoint.now();
TimePoint fixTime = start;
TimePoint queryTime = fixTime;
Position p = new DegreePosition(0, 0);
Bearing cog = new DegreeBearingImpl(123);
final Duration queryInterval = Duration.ONE_SECOND;
final Duration readAhead = Duration.ONE_MINUTE;
final Duration samplingInterval = Duration.ONE_SECOND.times(120);
final Duration transmissionInterval = Duration.ONE_MINUTE.times(10);
final Duration raceDuration = Duration.ONE_HOUR.times(48);
final Speed speed = new KnotSpeedImpl(12);
final Distance distancePerSample = speed.travel(fixTime, fixTime.plus(samplingInterval));
final DistanceCache distanceCache = new DistanceCache("test-DistanceCache");
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l) {
private static final long serialVersionUID = -7277196393160609503L;
@Override
protected DistanceCache getDistanceCache() {
return distanceCache;
}
};
while (fixTime.before(start.plus(raceDuration))) {
for (int i=0; i<transmissionInterval.divide(samplingInterval); i++) {
// produce a bulk of fixes
GPSFix fix = new GPSFixImpl(p, fixTime);
track.add(fix);
fixTime = fixTime.plus(samplingInterval);
p = p.translateGreatCircle(cog, distancePerSample);
}
Distance lastDistance = Distance.NULL;
// now query the distance with constant "from" and increasing "to" where "to" moves until 60s after tp
while (queryTime.before(fixTime.plus(readAhead))) {
final Distance d = track.getDistanceTraveled(start, queryTime);
assertTrue(d.compareTo(lastDistance) >= 0);
lastDistance = d;
queryTime = queryTime.plus(queryInterval);
}
}
assertTrue(distanceCache.size() <= DistanceCache.MAX_SIZE);
}
@Test
public void testDistanceCacheAccessForPartialStrip() {
@@ -187,7 +187,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
statisticsDTO.leaderOrWinner = convertToCompetitorDTO(leaderOrWinner);
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(leaderOrWinner);
if (track != null) {
statisticsDTO.averageGPSDataSampleInterval = track.getAverageIntervalBetweenFixes();
statisticsDTO.averageGPSDataSampleInterval = track.getAverageIntervalBetweenRawFixes();
}
}
} catch (NoWindException e) {
@@ -1372,7 +1372,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
if (trackedRace != null) {
Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint);
if (track != null) {
entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenFixes();
entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes();
}
if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) {
long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime();
@@ -14,5 +14,10 @@ public interface RaceLogStore {
void removeRaceLog(RaceLogIdentifier identifier);
void removeListenersAddedByStoreFrom(RaceLog raceLogAvailable);
/**
* Adds the mongo listener to the raceLog and adds raceLog to cache.
*/
void addImportedRaceLog(RaceLog raceLog, RaceLogIdentifier identifier);
}
@@ -23,4 +23,8 @@ public enum EmptyRaceLogStore implements RaceLogStore {
@Override
public void removeRaceLog(RaceLogIdentifier identifier) {
}
@Override
public void addImportedRaceLog(RaceLog raceLog, RaceLogIdentifier identifier) {
}
}
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.WithID;
@@ -64,6 +65,15 @@ public enum EmptyGPSFixStore implements GPSFixStore {
@Override
public void loadTrack(DynamicGPSFixTrack<WithID, ?> track, DeviceMapping<WithID> mapping)
throws NoCorrespondingServiceRegisteredException, TransformationException {
}
@Override
public void loadCompetitorTrack(DynamicGPSFixTrack<Competitor, GPSFixMoving> track, AbstractLog<?, ?> log,
Competitor competitor, TimePoint start, TimePoint end) throws TransformationException {
}
@Override
public void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, AbstractLog<?, ?> log, Mark mark,
TimePoint start, TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException {
}
}
@@ -12,6 +12,7 @@ import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.WithID;
@@ -23,12 +24,40 @@ public interface GPSFixStore {
void loadCompetitorTrack(DynamicGPSFixTrack<Competitor, GPSFixMoving> track, AbstractLog<?, ?> log,
Competitor competitor) throws TransformationException;
/**
* Load all fixes within the start and end time point (inclusive) that correspond to the
* {@link DeviceCompetitorMappingEvent}s found in the {@code raceLog}.
*
* @param start
* if <code>null</code>, the start of the time range for which to load fixes is only constrained by the
* device mapping intervals
* @param end
* if <code>null</code>, the end of the time range for which to load fixes is only constrained by the
* device mapping intervals
*/
void loadCompetitorTrack(DynamicGPSFixTrack<Competitor, GPSFixMoving> track, AbstractLog<?, ?> log,
Competitor competitor, TimePoint start, TimePoint end) throws TransformationException;
/**
* Load all fixes that correspond to the {@link DeviceMarkMappingEvent}s found in the {@code raceLog}.
*/
void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, AbstractLog<?, ?> log, Mark mark) throws TransformationException,
NoCorrespondingServiceRegisteredException;
/**
* Load all fixes within the start and end time point (inclusive) that correspond to the
* {@link DeviceMarkMappingEvent}s found in the {@code raceLog}.
*
* @param start
* if <code>null</code>, the start of the time range for which to load fixes is only constrained by the
* device mapping intervals
* @param end
* if <code>null</code>, the end of the time range for which to load fixes is only constrained by the
* device mapping intervals
*/
void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, AbstractLog<?, ?> log, Mark mark, TimePoint start,
TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException;
/**
* Load all fixes that correspond to the {@code mapping}.
*/
@@ -12,4 +12,9 @@ public interface RegattaLogStore {
* from the cache.
*/
void removeRegattaLog(RegattaLikeIdentifier regattaLikeId);
/**
* Adds the mongo listener to the regatta log and adds regatta log to cache.
*/
void addImportedRegattaLog(RegattaLog regattaLog, RegattaLikeIdentifier identifier);
}
@@ -18,4 +18,8 @@ public enum EmptyRegattaLogStore implements RegattaLogStore {
@Override
public void removeRegattaLog(RegattaLikeIdentifier regattaLikeId) {
}
@Override
public void addImportedRegattaLog(RegattaLog regattaLog, RegattaLikeIdentifier identifier) {
}
}
@@ -2,12 +2,15 @@ package com.sap.sailing.domain.tracking.impl;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.NavigableSet;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.impl.ArrayListNavigableSet;
@@ -68,8 +71,30 @@ import com.sap.sse.util.impl.ArrayListNavigableSet;
* @author Axel Uhl (D043530)
*/
public class DistanceCache {
public static final int MAX_SIZE = 100;
private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>> distanceCache;
/**
* The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
* defined as having been returned by {@link #getEarliestFromAndDistanceAtOrAfterFrom(TimePoint, TimePoint)} or
* having been added by {@link #cache(TimePoint, TimePoint, Distance)}.
* <p>
*
* When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
* the expunging will be admitted, and the entry is removed from the {@link #distanceCache} core structure. Reading
* and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
* map that counts access as "use" has a modifying effect on its internal structures.
* <p>
*
* The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
* alignment of the {@link #distanceCache} structure which has as its outer keys the "to" time point.<p>
*
* Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
* {@link #lock write lock} and therefore will have no contenders.
*/
private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
private final NamedReentrantReadWriteLock lock;
private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
@@ -82,6 +107,37 @@ public class DistanceCache {
public DistanceCache(String nameForLockLogging) {
lock = new NamedReentrantReadWriteLock("lock for DistanceCache for "+nameForLockLogging, /* fair */ true);
this.distanceCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>>(timePointInPairComparator);
this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
/* access-based ordering */ true) {
private static final long serialVersionUID = -6568235517111733193L;
@Override
protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
final boolean expunge = size() > MAX_SIZE;
if (expunge) {
removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
}
return expunge;
}
};
}
public int size() {
return lruCache.size();
}
private void removeCacheEntry(TimePoint from, TimePoint to) {
assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> entryForTo = distanceCache.floor(createDummy(to));
if (entryForTo.getA().equals(to)) {
Pair<TimePoint, Distance> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, Distance>(from, null));
if (entryForFrom.getA().equals(from)) {
entryForTo.getB().remove(entryForFrom);
if (entryForTo.getB().isEmpty()) {
distanceCache.remove(entryForTo);
}
}
}
}
/**
@@ -101,6 +157,12 @@ public class DistanceCache {
result = new Util.Pair<TimePoint, Util.Pair<TimePoint, Distance>>(entryForTo.getA(), fromCeiling);
}
}
// no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using
// the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry
// modifies the internal parts of the data structure which is not thread safe.
synchronized (lruCache) {
lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
}
return result;
} finally {
LockUtil.unlockAfterRead(lock);
@@ -118,6 +180,9 @@ public class DistanceCache {
for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>> i=toRemove.iterator(); i.hasNext(); ) {
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> entryToRemove = i.next();
assert entryToRemove.getA().compareTo(timePoint) >= 0;
for (Pair<TimePoint, Distance> fromAndDistance : entryToRemove.getB()) {
lruCache.remove(new Util.Pair<>(fromAndDistance.getA(), entryToRemove.getA()));
}
i.remove();
}
} finally {
@@ -146,6 +211,7 @@ public class DistanceCache {
distanceCache.add(pairForTo);
}
entryForTo.add(new Util.Pair<TimePoint, Distance>(from, distance));
lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
} finally {
LockUtil.unlockAfterWrite(lock);
}
@@ -143,12 +143,14 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void recordFix(Competitor competitor, GPSFixMoving fix) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
if (track != null) {
if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
logger.finest(""+competitor.getName() + ": " + fix);
if (isFixWithinStartAndEndOfTracking(fix.getTimePoint())) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
if (track != null) {
if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
logger.finest(""+competitor.getName() + ": " + fix);
}
track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners
}
track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners
}
}
@@ -161,12 +163,21 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void recordFix(Mark mark, GPSFix fix) {
if ((getStartOfTracking() == null || getStartOfTracking().compareTo(fix.getTimePoint()) <= 0) &&
(getEndOfTracking() == null || getEndOfTracking().compareTo(fix.getTimePoint()) >= 0)) {
final TimePoint fixTimePoint = fix.getTimePoint();
if (isFixWithinStartAndEndOfTracking(fixTimePoint)) {
getOrCreateTrack(mark).addGPSFix(fix);
}
}
/**
* A time point is considered "in" if it is (inclusively) between {@link #getStartOfTracking()} and {@link #getEndOfTracking()}.
* A <code>null</code> value for one of the two interval demarcations means an open-ended interval.
*/
private boolean isFixWithinStartAndEndOfTracking(final TimePoint fixTimePoint) {
return (getStartOfTracking() == null || getStartOfTracking().compareTo(fixTimePoint) <= 0) &&
(getEndOfTracking() == null || getEndOfTracking().compareTo(fixTimePoint) >= 0);
}
@Override
public void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed) {
this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed;
@@ -613,16 +624,28 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) {
setStartOfTrackingReceived(startOfTrackingReceived, /* waitForGPSFixesToLoad */ false);
}
public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived, final boolean waitForGPSFixesToLoad) {
if (!Util.equalsWithNull(startOfTrackingReceived, getStartOfTracking())) {
super.setStartOfTrackingReceived(startOfTrackingReceived);
super.setStartOfTrackingReceived(startOfTrackingReceived, waitForGPSFixesToLoad);
notifyListenersStartOfTrackingChanged(getStartOfTracking());
}
}
@Override
public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) {
setEndOfTrackingReceived(endOfTrackingReceived, /* waitForGPSFixesToLoad */ false);
}
/**
* Non-interface method, mainly for testing purposes; callers can ask to wait for the loading of fixes in the
* potentially extended tracking interval to finish before returning from this method.
*/
public void setEndOfTrackingReceived(final TimePoint endOfTrackingReceived, final boolean waitForGPSFixesToLoad) {
if (!Util.equalsWithNull(endOfTrackingReceived, getEndOfTracking())) {
super.setEndOfTrackingReceived(endOfTrackingReceived);
super.setEndOfTrackingReceived(endOfTrackingReceived, waitForGPSFixesToLoad);
notifyListenersEndOfTrackingChanged(getEndOfTracking());
}
}
@@ -578,7 +578,9 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
} finally {
unlockAfterRead();
}
getDistanceCache().cache(from, to, result);
if (recursionDepth == 0) {
getDistanceCache().cache(from, to, result);
}
}
return result;
}
@@ -1795,12 +1795,35 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return startTimeReceived;
}
protected void setStartOfTrackingReceived(TimePoint startOfTracking) {
protected void setStartOfTrackingReceived(final TimePoint startOfTracking, final boolean waitForGPSFixesToLoad) {
final TimePoint oldStartOfTracking = this.startOfTrackingReceived;
this.startOfTrackingReceived = startOfTracking;
if (!Util.equalsWithNull(oldStartOfTracking, startOfTracking) &&
(startOfTracking == null || (oldStartOfTracking != null && startOfTracking.before(oldStartOfTracking)))) {
logger.info("Loading fixes after start of tracking for "+getRace()+" has been extended from "+oldStartOfTracking+" to "+startOfTracking);
loadGPSFixesForExtendedTimeRange(startOfTracking, oldStartOfTracking, waitForGPSFixesToLoad);
}
}
protected void setEndOfTrackingReceived(TimePoint endOfTracking) {
private void loadGPSFixesForExtendedTimeRange(final TimePoint start, final TimePoint end, final boolean waitForGPSFixesToLoad) {
for (final RaceLog raceLog : attachedRaceLogs.values()) {
loadFixesForLog(raceLog, /* no need to addLogToMap because log has already been added during attachRaceLog */ null,
/* startOfTimeWindowToLoad */ start, /* endOfTimeWindowToLoad */ end, waitForGPSFixesToLoad);
}
for (final RegattaLog regattaLog : attachedRegattaLogs.values()) {
loadFixesForLog(regattaLog, /* no need to addLogToMap because log has already been added during attachRaceLog */ null,
/* startOfTimeWindowToLoad */ start, /* endOfTimeWindowToLoad */ end, waitForGPSFixesToLoad);
}
}
protected void setEndOfTrackingReceived(final TimePoint endOfTracking, final boolean waitForGPSFixesToLoad) {
final TimePoint oldEndOfTracking = this.endOfTrackingReceived;
this.endOfTrackingReceived = endOfTracking;
if (!Util.equalsWithNull(oldEndOfTracking, endOfTracking) &&
(endOfTracking == null || (oldEndOfTracking != null && endOfTracking.after(oldEndOfTracking)))) {
logger.info("Loading fixes after end of tracking for "+getRace()+" has been extended from "+oldEndOfTracking+" to "+endOfTracking);
loadGPSFixesForExtendedTimeRange(oldEndOfTracking, endOfTracking, waitForGPSFixesToLoad);
}
}
/**
@@ -3135,62 +3158,110 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
/**
* Loads the GPS fixes as per the device registrations in the <code>log</code> into this race. The <code>log</code> is added to the
* <code>addLogToMap</code> data structure while holding this object's monitor, and all waiters on this object are notified when done
* with adding the <code>log</code> to <code>addLogToMap</code>.
* Loads the GPS fixes as per the device registrations in the <code>log</code> into this race. The <code>log</code>
* is added to the <code>addLogToMap</code> data structure while holding <code>this</code> object's monitor, and all
* waiters on <code>this</code> object are notified when done with adding the <code>log</code> to
* <code>addLogToMap</code>.
*/
private <LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT> void loadFixesForLog(
final LogT log, ConcurrentHashMap<Serializable, LogT> addLogToMap) {
final LogT log, ConcurrentHashMap<Serializable, LogT> addLogToMap, final boolean waitForGPSFixesToLoad) {
if (log != null) {
// Use the new log, that possibly contains device mappings, to load GPSFix tracks from the DB
// When this tracked race is to be serialized, wait for the loading from stores to complete.
new Thread("Mongo mark and competitor track loader for tracked race " + getRace().getName() + ", log "
+ log.getId()) {
@Override
public void run() {
LockUtil.lockForRead(getSerializationLock());
LockUtil.lockForWrite(getLoadingFromGPSFixStoreLock());
synchronized (TrackedRaceImpl.this) {
loadingFromGPSFixStore = true; // indicates that the serialization lock is now safely held
addLogToMap.put(log.getId(), log);
TrackedRaceImpl.this.notifyAll();
}
try {
logger.info("Started loading competitor tracks for " + getRace().getName() + " for log " + log.getId());
for (Competitor competitor : race.getCompetitors()) {
try {
gpsFixStore.loadCompetitorTrack(
(DynamicGPSFixTrack<Competitor, GPSFixMoving>) tracks.get(competitor), log,
competitor);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load track for " + competitor, e);
}
}
logger.info("Finished loading competitor tracks for " + getRace().getName());
logger.info("Started loading mark tracks for " + getRace().getName());
for (Mark mark : getMarksFromRaceAndLogs()) {
try {
gpsFixStore.loadMarkTrack((DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark), log, mark);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load track for " + mark, e);
}
}
logger.info("Finished loading mark tracks for " + getRace().getName());
} finally {
synchronized (TrackedRaceImpl.this) {
loadingFromGPSFixStore = false;
TrackedRaceImpl.this.notifyAll();
}
LockUtil.unlockAfterWrite(getLoadingFromGPSFixStoreLock());
LockUtil.unlockAfterRead(getSerializationLock());
}
}
}.start();
final TimePoint startOfTimeWindowToLoad = getStartOfTracking();
final TimePoint endOfTimeWindowToLoad = getEndOfTracking();
loadFixesForLog(log, addLogToMap, startOfTimeWindowToLoad, endOfTimeWindowToLoad, waitForGPSFixesToLoad);
} else {
logger.severe("Got a request to attach log for an empty log!");
}
}
/**
* Loads the GPS fixes as per the device registrations in the <code>log</code> into this race, cropped to the time
* interval specified by <code>startOfTimeWindowToLoad</code> and </code>endOfTimeWindowToLoad</code>. The
* <code>log</code> is added to the <code>addLogToMap</code> data structure (if <code>addLogToMap</code> is not
* <code>null</code>) while holding <code>this</code> object's monitor, and all waiters on <code>this</code> object
* are notified when done with adding the <code>log</code> to <code>addLogToMap</code>.
*
* @param addLogToMap
* the log will be added to this map after the {@link #loadingFromGPSFixStore} flag has been set and only when the
* loading thread has successfully acquired the write lock of {@link #getLoadingFromGPSFixStoreLock()}. When done loading,
* all waiters on <code>this</code> object will be notified. If the map is not <code>null</code>, the key used to enter
* the log is the {@link AbstractLog#getId() log's ID}.
* @param startOfTimeWindowToLoad
* the time to use to crop the beginning of the time range for which to load fixes; if <code>null</code>,
* the start of the time range will be restricted only by the device mapping intervals
* @param endOfTimeWindowToLoad
* the time to use to crop the end of the time range for which to load fixes; if <code>null</code>,
* the end of the time range will be restricted only by the device mapping intervals
*/
private <LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT> void loadFixesForLog(final LogT log,
ConcurrentHashMap<Serializable, LogT> addLogToMap, final TimePoint startOfTimeWindowToLoad,
final TimePoint endOfTimeWindowToLoad, final boolean waitForGPSFixesToLoad) {
Thread t = new Thread("Mongo mark and competitor track loader for tracked race " + getRace().getName() + ", log "
+ log.getId()+" from "+startOfTimeWindowToLoad+" to "+endOfTimeWindowToLoad) {
@Override
public void run() {
LockUtil.lockForRead(getSerializationLock());
LockUtil.lockForWrite(getLoadingFromGPSFixStoreLock());
synchronized (TrackedRaceImpl.this) {
loadingFromGPSFixStore = true; // indicates that the serialization lock is now safely held
if (addLogToMap != null) {
addLogToMap.put(log.getId(), log);
}
TrackedRaceImpl.this.notifyAll();
}
try {
logger.info("Started loading competitor tracks for " + getRace().getName() + " for log " + log.getId());
for (Competitor competitor : race.getCompetitors()) {
try {
gpsFixStore.loadCompetitorTrack(
(DynamicGPSFixTrack<Competitor, GPSFixMoving>) tracks.get(competitor), log,
competitor, startOfTimeWindowToLoad, endOfTimeWindowToLoad);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load track for " + competitor, e);
}
}
logger.info("Finished loading competitor tracks for " + getRace().getName());
logger.info("Started loading mark tracks for " + getRace().getName());
for (Mark mark : getMarksFromRaceAndLogs()) {
try {
gpsFixStore.loadMarkTrack((DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark),
log, mark, startOfTimeWindowToLoad, endOfTimeWindowToLoad);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load track for " + mark, e);
}
}
logger.info("Finished loading mark tracks for " + getRace().getName());
} finally {
synchronized (TrackedRaceImpl.this) {
loadingFromGPSFixStore = false;
TrackedRaceImpl.this.notifyAll();
}
LockUtil.unlockAfterWrite(getLoadingFromGPSFixStoreLock());
LockUtil.unlockAfterRead(getSerializationLock());
logger.info("Thread "+getName()+" done.");
}
}
};
t.start();
if (waitForGPSFixesToLoad) {
try {
t.join();
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Got interrupted while waiting for loading of GPS fixes from log "+log+" to finish", e);
}
}
}
/**
* Tells if currently the race is loading GPS fixes from the {@link GPSFixStore}. Clients may {@link Object#wait()} on <code>this</code>
* object and will be notified whenever a change of this flag's value occurs.
*/
public boolean isLoadingFromGPSFixStore() {
return loadingFromGPSFixStore;
}
/**
* This can trigger fixes to be loaded, if there are {@link DeviceMapping}s in the {@code RaceLog}.
* If multiple race logs are attached, the threads that are spawned to load fixes will do so one
@@ -3198,7 +3269,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
@Override
public void attachRaceLog(final RaceLog raceLog) {
loadFixesForLog(raceLog, attachedRaceLogs);
loadFixesForLog(raceLog, attachedRaceLogs, /* wait for fixes to load */ false);
}
@Override
@@ -3243,7 +3314,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
@Override
public void attachRegattaLog(RegattaLog regattaLog) {
loadFixesForLog(regattaLog, attachedRegattaLogs);
loadFixesForLog(regattaLog, attachedRegattaLogs, /* wait for fixes to load */ false);
}
@Override
@@ -65,6 +65,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.LASER_INT.getDisplayName(), imageResources.LaserIcon());
boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.LaserIcon());
boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserIcon());
boatClassIconsMap.put(BoatClassMasterdata.LAGO_26.getDisplayName(), imageResources.Lago26Icon());
boatClassIconsMap.put(BoatClassMasterdata.MELGES_24.getDisplayName(), imageResources.Melges24Icon());
boatClassIconsMap.put(BoatClassMasterdata.MINI_TRANSAT.getDisplayName(), imageResources.MiniTransatIcon());
boatClassIconsMap.put(BoatClassMasterdata.MUSTO_SKIFF.getDisplayName(), imageResources.MustoSkiffIcon());
@@ -114,7 +114,10 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png")
ImageResource LaserIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/LAGO_26.png")
ImageResource Lago26Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png")
ImageResource Melges24Icon();
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -39,12 +39,15 @@ import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEventFactory;
import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEventFactoryImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
@@ -251,15 +254,14 @@ public class MasterDataImportTest {
raceColumn.setTrackedRace(testFleet1, trackedRace);
// Set log event
RaceLogEventFactory factory = new RaceLogEventFactoryImpl();
TimePoint logTimePoint = new MillisecondsTimePoint(1372489200000L);
TimePoint logTimePoint2 = new MillisecondsTimePoint(1372489201000L);
RaceLogStartTimeEvent logEvent = factory.createStartTimeEvent(logTimePoint, author, 1, logTimePoint);
RaceLogStartTimeEvent logEvent = new RaceLogStartTimeEventImpl(logTimePoint, author, logTimePoint, 1, new ArrayList<Competitor>(), 0, logTimePoint);
raceColumn.getRaceLog(testFleet1).add(logEvent);
Position p = new DegreePosition(3, 3);
Wind wind = new WindImpl(p, logTimePoint2, new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(12)));
RaceLogWindFixEvent windEvent = factory.createWindFixEvent(logTimePoint2, author, UUID.randomUUID(),
new ArrayList<Competitor>(), 2, wind, /* isMagnetic */ false);
RaceLogWindFixEvent windEvent = new RaceLogWindFixEventImpl(logTimePoint2, author, logTimePoint2,
UUID.randomUUID(), new ArrayList<Competitor>(), 2, wind, /* isMagnetic */ false);
raceColumn.getRaceLog(testFleet1).add(windEvent);
storedLogUUIDs.add(logEvent.getId());
storedLogUUIDs.add(windEvent.getId());
@@ -403,6 +405,14 @@ public class MasterDataImportTest {
Assert.assertEquals(wind, ((RaceLogWindFixEvent) raceColumnOnTarget.getRaceLog(fleet1OnTarget)
.getFirstFixAtOrAfter(logTimePoint2)).getWindFix());
// Add new event to check persistence of post-import events (see bug 3230)
TimePoint logTimePoint5 = new MillisecondsTimePoint(1372489310000L);
UUID postImportEventId = UUID.randomUUID();
RaceLogStartTimeEventImpl postImportLogEvent = new RaceLogStartTimeEventImpl(logTimePoint5, author, logTimePoint5, postImportEventId,
new ArrayList<Competitor>(), 3, logTimePoint5);
raceColumnOnTarget.getRaceLog(fleet1OnTarget).add(
postImportLogEvent);
// Check for regatta log event
RegattaLogRegisterCompetitorEvent registerEventOnTarget = (RegattaLogRegisterCompetitorEvent)
regattaOnTarget.getRegattaLog().getFirstFixAtOrAfter(regattaLogTimepoint);
@@ -420,6 +430,9 @@ public class MasterDataImportTest {
RaceLog raceLog2 = raceColumn2.getRaceLog(raceColumn2.getFleetByName(fleet1OnTarget.getName()));
Assert.assertEquals(logEvent.getId(), raceLog2.getFirstRawFixAtOrAfter(logTimePoint).getId());
RaceLogEvent postImportLogEventFromDB = raceLog2.getFirstRawFixAtOrAfter(logTimePoint5);
Assert.assertEquals(postImportEventId, postImportLogEventFromDB.getId());
// Check for persisting of regatta log events
Regatta regattaOnTarget2 = dest2.getRegattaByName(TEST_LEADERBOARD_NAME);
RegattaLogRegisterCompetitorEvent registerEventOnTarget2 = (RegattaLogRegisterCompetitorEvent)
@@ -42,15 +42,18 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.masterdataimport.TopLevelMasterData;
import com.sap.sailing.domain.masterdataimport.WindTrackMasterData;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
@@ -246,8 +249,8 @@ public class ImportMasterDataOperation extends
}
if (leaderboard != null) {
toState.addLeaderboard(leaderboard);
storeRaceLogEvents(leaderboard, toState.getMongoObjectFactory());
storeRegattaLogEvents(leaderboard, toState.getMongoObjectFactory());
storeRaceLogEvents(leaderboard, toState.getMongoObjectFactory(), toState.getDomainObjectFactory());
storeRegattaLogEvents(leaderboard, toState.getMongoObjectFactory(), toState.getDomainObjectFactory());
creationCount.addOneLeaderboard(leaderboard.getName());
relinkTrackedRacesIfPossible(toState, leaderboard);
toState.updateStoredLeaderboard(leaderboard);
@@ -276,7 +279,8 @@ public class ImportMasterDataOperation extends
* in serialized form on the {@link RaceColumn} objects, but the database doesn't yet know about them. This method uses
* a <code>MongoRaceLogStoreVisitor</code> to store all race log events to the database.
*/
private void storeRaceLogEvents(Leaderboard leaderboard, MongoObjectFactory mongoObjectFactory) {
private void storeRaceLogEvents(Leaderboard leaderboard, MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory) {
RaceLogStore mongoRaceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
RaceLog log = raceColumn.getRaceLog(fleet);
@@ -292,6 +296,8 @@ public class ImportMasterDataOperation extends
} finally {
log.unlockAfterRead();
}
// Make sure listener is added to race log
mongoRaceLogStore.addImportedRaceLog(log, identifier);
}
}
}
@@ -303,7 +309,8 @@ public class ImportMasterDataOperation extends
* potentially {@link HasRegattaLike has} an attached RegattaLog, which then must be stored in the database.
* @see #storeRaceLogEvents(Leaderboard, MongoObjectFactory)
*/
private void storeRegattaLogEvents(Leaderboard leaderboard, MongoObjectFactory mongoObjectFactory) {
private void storeRegattaLogEvents(Leaderboard leaderboard, MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory) {
RegattaLogStore regattaLogStore = MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore(mongoObjectFactory, domainObjectFactory);
if (leaderboard instanceof HasRegattaLike) {
IsRegattaLike regattaLike = ((HasRegattaLike) leaderboard).getRegattaLike();
RegattaLog log = regattaLike.getRegattaLog();
@@ -318,6 +325,7 @@ public class ImportMasterDataOperation extends
} finally {
log.unlockAfterRead();
}
regattaLogStore.addImportedRegattaLog(log, identifier);
}
}
@@ -446,9 +446,9 @@ public class LeaderboardData extends ExportAction {
GPSFixTrack<Competitor, GPSFixMoving> gpsFixesForCompetitor = race.getTrack(competitor);
if (gpsFixesForCompetitor != null) {
Duration averageIntervall = gpsFixesForCompetitor.getAverageIntervalBetweenFixes();
if (averageIntervall != null) {
addNamedElementWithValue(competitorRaceDataElement, "average_interval_between_fixes_outliers_removed_as_millis", averageIntervall.asMillis());
Duration averageInterval = gpsFixesForCompetitor.getAverageIntervalBetweenFixes();
if (averageInterval != null) {
addNamedElementWithValue(competitorRaceDataElement, "average_interval_between_fixes_outliers_removed_as_millis", averageInterval.asMillis());
}
Duration averageIntervallRaw = gpsFixesForCompetitor.getAverageIntervalBetweenRawFixes();
if (averageIntervallRaw != null) {
@@ -32,6 +32,7 @@ public interface Duration extends Serializable, Comparable<Duration> {
Duration divide(long divisor);
Duration divide(double divisor);
double divide(Duration duration);
Duration times(long factor);
Duration times(double factor);
@@ -33,6 +33,11 @@ public class MillisecondsDurationImpl implements Duration {
return new MillisecondsDurationImpl((long) (1. / divisor * asMillis()));
}
@Override
public double divide(Duration duration) {
return ((double) asMillis()) / (double) duration.asMillis();
}
@Override
public Duration times(long factor) {
return new MillisecondsDurationImpl(asMillis() * factor);