diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayPrintListener.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayPrintListener.java index f2f95e4ef03..780cd2ac811 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayPrintListener.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayPrintListener.java @@ -125,4 +125,9 @@ public class SwissTimingReplayPrintListener implements SwissTimingReplayListener public void eot() { System.out.println("EOT"); } + + @Override + public void progress(double progress) { + System.out.println("Progress: "+progress); + } } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayTestListener.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayTestListener.java index 30d411c5f66..9bc6edd849b 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayTestListener.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayTestListener.java @@ -112,4 +112,8 @@ public class SwissTimingReplayTestListener implements SwissTimingReplayListener eotCount++; } + @Override + public void progress(double progress) { + } + } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayListener.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayListener.java index e5d0810867d..fe124ae09c0 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayListener.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayListener.java @@ -126,5 +126,12 @@ public interface SwissTimingReplayListener { void rankingMark(short marksRank, short marksRankIndex, int marksGap_seconds, int marksRaceTime_seconds); void eot(); + + /** + * If a loading progress can be estimated, a progress ratio between 0.0 and 1.0 is provided as argument. + * + * @param progress 0.0 meaning "just begun," 1.0 meaning "finished." + */ + void progress(double progress); } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayAdapter.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayAdapter.java index 74af4fd831d..2085217a93d 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayAdapter.java @@ -59,4 +59,7 @@ public class SwissTimingReplayAdapter implements SwissTimingReplayListener { @Override public void eot() {} + @Override + public void progress(double progress) {} + } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java index a4bd9a926c5..d64598f07e1 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java @@ -132,7 +132,6 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService { bos.write(buf, 0, read); } new SwissTimingReplayParserImpl().readData(new ByteArrayInputStream(bos.getBuffer(), 0, bos.size()), replayListener); - bos.close(); } catch (Exception e) { throw new RuntimeException(e); diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java index 4730b0c0f84..3af456007c4 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java @@ -44,9 +44,11 @@ import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.TrackedRaceStatus.Status; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import difflib.PatchFailedException; @@ -149,7 +151,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { this.domainFactory = domainFactory; } - public Iterable getTrackedRaces() { + public Iterable getTrackedRaces() { return trackedRacePerRaceID.values(); } @@ -307,6 +309,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), /* raceDefinitionSetToUpdate */ null); + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.LOADING, 0)); TimePoint bestStartTimeKnownSoFar = bestStartTimePerRaceID.get(currentRaceID); if (bestStartTimeKnownSoFar != null) { trackedRace.setStartTimeReceived(bestStartTimeKnownSoFar); @@ -365,4 +368,17 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { } } } + + @Override + public void progress(double progress) { + DynamicTrackedRace trackedRace = trackedRacePerRaceID.get(currentRaceID); + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.LOADING, progress)); + } + + @Override + public void eot() { + for (DynamicTrackedRace trackedRace : getTrackedRaces()) { + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.FINISHED, 1.0)); + } + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 11a1ad09ddd..4d178ca3193 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -38,6 +38,7 @@ import com.sap.sailing.domain.tracking.RaceListener; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; @@ -707,4 +708,20 @@ public class MockedTrackedRace implements DynamicTrackedRace { // TODO Auto-generated method stub } + + @Override + public TrackedRaceStatus getStatus() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setStatus(TrackedRaceStatus newStatus) { + // TODO Auto-generated method stub + } + + @Override + public void waitUntilNotLoading() { + // TODO Auto-generated method stub + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index 0a917d3b892..362f81b5a54 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -30,6 +30,7 @@ import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; @@ -455,12 +456,21 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { @Override public void lockForRead(Iterable markPassings) { // TODO Auto-generated method stub - } @Override public void unlockAfterRead(Iterable markPassings) { // TODO Auto-generated method stub - + } + + @Override + public TrackedRaceStatus getStatus() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void waitUntilNotLoading() { + // TODO Auto-generated method stub } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index 0e5750f0289..8d65707f040 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -43,8 +43,10 @@ import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.TrackedRaceStatus.Status; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.GPSFixImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; @@ -406,22 +408,45 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements @Override public void stopped() { logger.info("stopped TracTrac tracking for "+getRaces()); + for (RaceDefinition race : getRaces()) { + DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(race); + if (trackedRace != null) { + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.FINISHED, 1.0)); + } + } } @Override public void storedDataBegin() { logger.info("Stored data begin for race(s) "+getRaces()); + for (RaceDefinition race : getRaces()) { + DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(race); + if (trackedRace != null) { + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.LOADING, 0)); + } + } } @Override public void storedDataEnd() { logger.info("Stored data end for race(s) "+getRaces()); + for (RaceDefinition race : getRaces()) { + DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(race); + if (trackedRace != null) { + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.TRACKING, 1)); + } + } } @Override public void storedDataProgress(float progress) { logger.info("Stored data progress for race(s) "+getRaces()+": "+progress); - + for (RaceDefinition race : getRaces()) { + DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(race); + if (trackedRace != null) { + trackedRace.setStatus(new TrackedRaceStatusImpl(Status.LOADING, progress)); + } + } } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index c05bc94d910..78dbe9e0b91 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -76,4 +76,6 @@ public interface DynamicTrackedRace extends TrackedRace { * the start leg's direction as a fallback for estimating the wind direction. */ void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind); + + void setStatus(TrackedRaceStatus newStatus); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java index ae182d6c979..1000e607f0c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java @@ -35,4 +35,6 @@ public interface RaceChangeListener { void delayToLiveChanged(long delayToLiveInMillis); void windSourcesToExcludeChanged(Iterable windSourcesToExclude); + + void statusChanged(TrackedRaceStatus newStatus); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 84fb96081d1..35284a13e56 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -450,4 +450,12 @@ public interface TrackedRace extends Serializable { * until the wind loading has completed. */ void waitUntilWindLoadingComplete() throws InterruptedException; + + TrackedRaceStatus getStatus(); + + /** + * If the {@link #getStatus() status} is currently {@link TrackedRaceStatus.Status#LOADING}, blocks until the status changes to any + * other status. + */ + void waitUntilNotLoading(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceStatus.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceStatus.java new file mode 100755 index 00000000000..dd9ab7dd32a --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceStatus.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.tracking; + +import java.io.Serializable; + +/** + * Races undergo a variety of different states in our application. They may be created with a competitor list, maybe not + * having a confirmed course yet and not even an expected start time. Later, the start time may be set. The race starts, + * may be abandoned, restarted, running, finishing, finished, protests, completed, race committee confirmed.

+ * + * When a race is then re-loaded or re-connected while it is running, stored data may start to load, be loading, be done + * loading. Live data may continue to be received, done receiving.

+ * + * The race may be archived (stored persistently in our database), loading from the DB, done loading from the DB. It has + * to be possible to update an archived race in case something changes after it has been archived.

+ * + * @author Axel Uhl (D043530) + * + */ +public interface TrackedRaceStatus extends Serializable { + public static enum Status { PREPARED, LOADING, TRACKING, FINISHED }; + + Status getStatus(); + + /** + * Particularly interesting when the {@link #getStatus() status} is {@link Status#LOADING}. Indicates the progress + * of loading the race's tracking data. + * + * @return a value in the range of 0.0 to 1.0 where 0.0 means no progress yet and 1.0 means loading has completed. + * 1.0 will, however, hardly be seen in state {@link Status#LOADING} because the status will probably + * already have transitioned to {@link Status#TRACKING} or {@link Status#FINISHED}. + */ + double getLoadingProgress(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java index 6d5104ebf68..c0056762cfb 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java @@ -11,10 +11,14 @@ import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; public abstract class AbstractRaceChangeListener implements RaceChangeListener { + @Override + public void statusChanged(TrackedRaceStatus newStatus) {} + @Override public void windSourcesToExcludeChanged(Iterable windSourcesToExclude) { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java index cb91281a000..429976d3526 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java @@ -24,6 +24,7 @@ import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.util.impl.SmartFutureCache; import com.sap.sailing.util.impl.SmartFutureCache.CacheUpdater; import com.sap.sailing.util.impl.SmartFutureCache.UpdateInterval; @@ -323,6 +324,12 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { invalidate(competitor, from); } + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + // no-op; race status change doesn't have an impact on the cross track error; only the loaded fixes for + // competitors and mark do + } + @Override public void markPositionChanged(GPSFix fix, Mark mark) { TimePoint from = owner.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix).getA(); 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 a451f201451..ecbfeab22d5 100644 --- 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 @@ -34,6 +34,7 @@ import com.sap.sailing.domain.tracking.GPSTrackListener; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; @@ -104,6 +105,12 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners } + @Override + public void setStatus(TrackedRaceStatus newStatus) { + super.setStatus(newStatus); + notifyListeners(newStatus); + } + @Override public void recordFix(Mark mark, GPSFix fix) { getOrCreateTrack(mark).addGPSFix(fix); @@ -279,6 +286,21 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements } } + private void notifyListeners(TrackedRaceStatus status) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.statusChanged(status); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListeners(TrackedRaceStatus)", e); + } + } + } + private void notifyListeners(Wind wind, WindSource windSource) { RaceChangeListener[] listeners; synchronized (getListeners()) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index 20fa7167e3a..2b26557fa4b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -33,6 +33,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; @@ -174,7 +175,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl virtualInternalRawFixes = new EstimatedWindFixesAsNavigableSet(trackedRace); weigher = ConfidenceFactory.INSTANCE .createHyperbolicTimeDifferenceWeigher(getMillisecondsOverWhichToAverageWind()); - trackedRace.addListener(this); + trackedRace.addListener(this); // in particular, race status changes will be notified, unblocking waiting computations after LOADING phase this.timePointsWithCachedNullResult = new ArrayListNavigableSet( AbstractTimePoint.TIMEPOINT_COMPARATOR); this.timePointsWithCachedNullResultFastContains = new HashSet(); @@ -397,6 +398,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl public void run() { // no locking required here; the incremental cache refresh protects the inner cache structures from concurrent modifications cacheInvalidationTimer.cancel(); // terminates the timer thread + getTrackedRace().waitUntilNotLoading(); refreshCacheIncrementally(); } }, delayForCacheInvalidationInMilliseconds); @@ -502,6 +504,12 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl TimePoint endOfInvalidation = new MillisecondsTimePoint(fix.getTimePoint().asMillis() + averagingInterval); scheduleCacheRefresh(startOfInvalidation, endOfInvalidation); } + + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + // If the status changes from LOADING to something else, the waitUntilNotLoading call in the scheduler's + // run() method will be unblocked. Therefore no action is required here. + } @Override public void markPassingReceived(Competitor competitor, Map oldMarkPassings, Iterable markPassings) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java index 7f27459e1b8..5e3aab7b1b3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java @@ -28,6 +28,7 @@ import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { @@ -196,6 +197,11 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { clearCaches(); } + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + // no-op; the leg doesn't mind the tracked race's status being updated + } + @Override public void windSourcesToExcludeChanged(Iterable windSourcesToExclude) { clearCaches(); 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 75ab3e3a670..1d6e7fc875c 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 @@ -77,6 +77,8 @@ import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.domain.tracking.TrackedRaceStatus.Status; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; @@ -102,6 +104,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { private final RaceDefinition race; private final TrackedRegatta trackedRegatta; + + private TrackedRaceStatus status; + + private final Object statusNotifier; /** * By default, all wind sources are used, none are excluded. However, e.g., for performance reasons, particular wind @@ -246,6 +252,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { long delayForWindEstimationCacheInvalidation) { super(); locksForMarkPassings = new IdentityHashMap<>(); + this.status = new TrackedRaceStatusImpl(Status.PREPARED, 0.0); + this.statusNotifier = new Object[0]; this.serializationLock = new NamedReentrantReadWriteLock("Serialization lock for tracked race "+race.getName(), /* fair */ true); this.cacheInvalidationTimerLock = new Object(); this.updateCount = 0; @@ -371,6 +379,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { @Override public Triple> computeCacheUpdate(Competitor competitor, EmptyUpdateInterval updateInterval) throws NoWindException { + waitUntilNotLoading(); return computeManeuvers(competitor); } }, /* nameForLocks */ "Maneuver cache for race "+getRace().getName()); @@ -2191,4 +2200,41 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { protected void setDelayToLiveInMillis(long delayToLiveInMillis) { this.delayToLiveInMillis = delayToLiveInMillis; } + + @Override + public TrackedRaceStatus getStatus() { + return status; + } + + /** + * Changes to the {@link #status} variable are synchronized on the {@link #statusNotifier} field. + * @return + */ + protected Object getStatusNotifier() { + return statusNotifier; + } + + protected void setStatus(TrackedRaceStatus newStatus) { + synchronized (getStatusNotifier()) { + this.status = newStatus; + getStatusNotifier().notifyAll(); + } + } + + /** + * Waits on the current ("old") status object which is notified in {@link #setStatus(TrackedRaceStatus)} when the status + * is changed. The change as well as the check synchronize on the old status object. + */ + @Override + public void waitUntilNotLoading() { + synchronized (getStatusNotifier()) { + while (getStatus().getStatus() == Status.LOADING) { + try { + getStatusNotifier().wait(); + } catch (InterruptedException e) { + logger.info("waitUntilNotLoading on tracked race "+this+" interrupted: "+e.getMessage()+". Continuing to wait."); + } + } + } + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceStatusImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceStatusImpl.java new file mode 100755 index 00000000000..f38b3970ee4 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceStatusImpl.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.tracking.impl; + +import com.sap.sailing.domain.tracking.TrackedRaceStatus; + +/** + * Starts out in state {@link Status#PREPARED} with loading progress 0.0. + * + * @author Axel Uhl (D043530) + * + */ +public class TrackedRaceStatusImpl implements TrackedRaceStatus { + private static final long serialVersionUID = 6443697957620826443L; + private final TrackedRaceStatus.Status status; + private final double loadingProgress; + + public TrackedRaceStatusImpl(Status status, double loadingProgress) { + super(); + this.status = status; + this.loadingProgress = loadingProgress; + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public double getLoadingProgress() { + return loadingProgress; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/LockUtil.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/LockUtil.java index 17ccc89e068..8ca75980708 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/LockUtil.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/LockUtil.java @@ -69,7 +69,7 @@ public class LockUtil { } public static void lockForRead(NamedReentrantReadWriteLock lock) { - lock(lock.readLock(), "readLock "+lock.getName(), lock); + lock(lock.readLock(), lock.getReadLockName(), lock); } public static void unlockAfterRead(NamedReentrantReadWriteLock lock) { @@ -77,7 +77,7 @@ public class LockUtil { } public static void lockForWrite(NamedReentrantReadWriteLock lock) { - lock(lock.writeLock(), "writeLock "+lock.getName(), lock); + lock(lock.writeLock(), lock.getWriteLockName(), lock); synchronized (lastTimeWriteLockWasObtained) { lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java index a155f966407..87d97240084 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java @@ -14,6 +14,8 @@ import com.sap.sailing.domain.common.Named; public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implements Named { private static final long serialVersionUID = 2906084982209339774L; private final String name; + private final String readLockName; + private final String writeLockName; private final WriteLockWrapper writeLockWrapper; private final ReadLockWrapper readLockWrapper; private transient List readers; @@ -136,6 +138,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen public NamedReentrantReadWriteLock(String name, boolean fair) { super(fair); this.name = name; + this.readLockName = "readLock "+name; + this.writeLockName = "writeLock "+name; this.writeLockWrapper = new WriteLockWrapper(super.writeLock()); this.readLockWrapper = new ReadLockWrapper(super.readLock()); this.readers = Collections.synchronizedList(new ArrayList()); @@ -173,4 +177,12 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen public String toString() { return "ReentrantReadWriteLock "+getName()+" ("+(isFair()?"fair":"unfair")+")"; } + + protected String getReadLockName() { + return readLockName; + } + + protected String getWriteLockName() { + return writeLockName; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LeaderboardDTOCache.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LeaderboardDTOCache.java index b820918989f..ab0bf067b8b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LeaderboardDTOCache.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LeaderboardDTOCache.java @@ -39,6 +39,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; @@ -103,6 +104,11 @@ public class LeaderboardDTOCache { removeFromCache(leaderboard); } + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + removeFromCache(leaderboard); + } + @Override public void markPositionChanged(GPSFix fix, Mark mark) { removeFromCache(leaderboard); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 0b456897c34..d51db4b1722 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -129,6 +129,7 @@ import com.sap.sailing.domain.tracking.RacesHandle; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; @@ -755,6 +756,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S invalidateCacheAndRemoveThisListenerFromTrackedRace(); } + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + // when the status changes away from LOADING, calculations may start or resume, making it necessary to clear the cache + invalidateCacheAndRemoveThisListenerFromTrackedRace(); + } + @Override public void markPositionChanged(GPSFix fix, Mark mark) { invalidateCacheAndRemoveThisListenerFromTrackedRace(); diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index d758896e662..6d945342daa 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -1,28 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index a63e2232b14..4a0bcd9355b 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -1,28 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index 16b37eb1a76..e0f2c9f07d7 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -16,6 +16,7 @@ + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 4f9db0774d0..047f93a933a 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -85,6 +85,7 @@ import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.RacesHandle; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; @@ -115,6 +116,7 @@ import com.sap.sailing.server.operationaltransformation.TrackRegatta; import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings; import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive; import com.sap.sailing.server.operationaltransformation.UpdateRaceTimes; +import com.sap.sailing.server.operationaltransformation.UpdateTrackedRaceStatus; import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime; import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude; @@ -901,6 +903,11 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen replicate(new RecordCompetitorGPSFix(getRaceIdentifier(), competitor, fix)); } + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus)); + } + @Override public void markPositionChanged(GPSFix fix, Mark mark) { replicate(new RecordMarkGPSFix(getRaceIdentifier(), mark, fix)); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateTrackedRaceStatus.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateTrackedRaceStatus.java new file mode 100755 index 00000000000..c55fd2ebad6 --- /dev/null +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateTrackedRaceStatus.java @@ -0,0 +1,36 @@ +package com.sap.sailing.server.operationaltransformation; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.RacingEventServiceOperation; + +public class UpdateTrackedRaceStatus extends AbstractRaceOperation { + private static final long serialVersionUID = 5847067037829132465L; + private final TrackedRaceStatus newStatus; + + public UpdateTrackedRaceStatus(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceStatus newStatus) { + super(raceIdentifier); + this.newStatus = newStatus; + } + + @Override + public Void internalApplyTo(RacingEventService toState) throws Exception { + DynamicTrackedRace trackedRace = (DynamicTrackedRace) toState.getTrackedRace(getRaceIdentifier()); + trackedRace.setStatus(newStatus); + return null; + } + + @Override + public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RacingEventServiceOperation transformServerOp(RacingEventServiceOperation clientOp) { + // TODO Auto-generated method stub + return null; + } +}