diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java index d01b682e045..9fe5ba23215 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java @@ -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 track, AbstractLog log, + Competitor competitor, TimePoint start, TimePoint end) throws TransformationException { + List> mappings = new DeviceCompetitorMappingFinder<>(log).analyze().get(competitor); + if (mappings != null) { + for (DeviceMapping 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 track, DeviceMapping mapping) throws TransformationException, NoCorrespondingServiceRegisteredException { loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/); } + @Override + public void loadMarkTrack(DynamicGPSFixTrack track, AbstractLog log, Mark mark, + TimePoint start, TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException { + List> mappings = new DeviceMarkMappingFinder<>(log).analyze().get(mark); + if (mappings != null) { + for (DeviceMapping 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 track, DeviceMapping mapping) throws NoCorrespondingServiceRegisteredException, TransformationException { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF index eb7048450f1..3f9cbc1525d 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF @@ -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" diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java index ec95fd9d208..679b6b97455 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java @@ -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(); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java index 75c6b0978f8..501f9f43025 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java @@ -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. 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 diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java index af04e4c82a5..a763c559b66 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java @@ -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 track, DeviceMapping mapping) throws NoCorrespondingServiceRegisteredException, TransformationException { - + } + + @Override + public void loadCompetitorTrack(DynamicGPSFixTrack track, AbstractLog log, + Competitor competitor, TimePoint start, TimePoint end) throws TransformationException { + } + + @Override + public void loadMarkTrack(DynamicGPSFixTrack track, AbstractLog log, Mark mark, + TimePoint start, TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException { } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java index 6db39a883c1..eedda260a79 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java @@ -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 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 null, the start of the time range for which to load fixes is only constrained by the + * device mapping intervals + * @param end + * if null, the end of the time range for which to load fixes is only constrained by the + * device mapping intervals + */ + void loadCompetitorTrack(DynamicGPSFixTrack 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 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 null, the start of the time range for which to load fixes is only constrained by the + * device mapping intervals + * @param end + * if null, the end of the time range for which to load fixes is only constrained by the + * device mapping intervals + */ + void loadMarkTrack(DynamicGPSFixTrack track, AbstractLog log, Mark mark, TimePoint start, + TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException; + /** * Load all fixes that correspond to the {@code mapping}. */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index 52718e33165..879f70c4de1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -143,12 +143,14 @@ DynamicTrackedRace, GPSTrackListener { @Override public void recordFix(Competitor competitor, GPSFixMoving fix) { - DynamicGPSFixTrack 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 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 { @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 null 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 { @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()); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 3a785160eaa..7ed289d11de 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -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 log into this race. The log is added to the - * addLogToMap data structure while holding this object's monitor, and all waiters on this object are notified when done - * with adding the log to addLogToMap. + * Loads the GPS fixes as per the device registrations in the log into this race. The log + * is added to the addLogToMap data structure while holding this object's monitor, and all + * waiters on this object are notified when done with adding the log to + * addLogToMap. */ private , EventT extends AbstractLogEvent, VisitorT> void loadFixesForLog( - final LogT log, ConcurrentHashMap addLogToMap) { + final LogT log, ConcurrentHashMap 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) 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) 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 log into this race, cropped to the time + * interval specified by startOfTimeWindowToLoad and endOfTimeWindowToLoad. The + * log is added to the addLogToMap data structure (if addLogToMap is not + * null) while holding this object's monitor, and all waiters on this object + * are notified when done with adding the log to addLogToMap. + * + * @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 this object will be notified. If the map is not null, 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 null, + * 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 null, + * the end of the time range will be restricted only by the device mapping intervals + */ + private , EventT extends AbstractLogEvent, VisitorT> void loadFixesForLog(final LogT log, + ConcurrentHashMap 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) 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) 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 this + * 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