mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-17 01:31:01 +00:00
Merge branch 'master' into swisstiming-2.0
This commit is contained in:
commit
2442f5b015
16 files changed
+216
-130
No files matched your search
+6
-6
@@ -148,17 +148,17 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
InterruptedException {
|
||||
}
|
||||
};
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE);
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE);
|
||||
assertNotNull(leaderboardDTO);
|
||||
assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE)); // assert it's cached
|
||||
assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // assert it's cached
|
||||
leaderboard.getRaceColumnByName(columnName).releaseTrackedRace(defaultFleet); // this should clear the cache
|
||||
LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE);
|
||||
LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE);
|
||||
assertNotSame(leaderboardDTO, leaderboardDTO2);
|
||||
assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again
|
||||
assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again
|
||||
leaderboard.getRaceColumnByName(columnName).setTrackedRace(defaultFleet, testRaces.iterator().next()); // clear cache again; requires listener(s) to still be attached
|
||||
LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE);
|
||||
LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE);
|
||||
assertNotSame(leaderboardDTO2, leaderboardDTO3);
|
||||
assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again
|
||||
assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+11
-10
@@ -32,6 +32,7 @@ import com.sap.sailing.domain.base.impl.MarkImpl;
|
||||
import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
import com.sap.sailing.domain.base.impl.SeriesImpl;
|
||||
import com.sap.sailing.domain.base.impl.WaypointImpl;
|
||||
import com.sap.sailing.domain.common.Duration;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
@@ -600,16 +601,16 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
lastMarkPassingTimesForCompetitors[2].put(c[2], finish);
|
||||
createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes(series.get(1), "Default",
|
||||
new Competitor[][] { f1, f2, f3 }, new TimePoint[] { earlier, now, later }, lastMarkPassingTimesForCompetitors);
|
||||
long totalTimeSailedC0_InRace1 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], earlier.plus(1000));
|
||||
assertEquals(1000l, totalTimeSailedC0_InRace1);
|
||||
long totalTimeSailedC0_InRace2 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], now.plus(1000));
|
||||
assertEquals(now.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace2);
|
||||
long totalTimeSailedC0_InRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], later.plus(1000));
|
||||
assertEquals(later.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace3);
|
||||
long totalTimeSailedC0_AtEndOfRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], finish);
|
||||
assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AtEndOfRace3);
|
||||
long totalTimeSailedC0_AfterRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], finish.plus(1000));
|
||||
assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AfterRace3);
|
||||
Duration totalTimeSailedC0_InRace1 = leaderboard.getTotalTimeSailed(c[0], earlier.plus(1000));
|
||||
assertEquals(1000l, totalTimeSailedC0_InRace1.asMillis());
|
||||
Duration totalTimeSailedC0_InRace2 = leaderboard.getTotalTimeSailed(c[0], now.plus(1000));
|
||||
assertEquals(now.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace2.asMillis());
|
||||
Duration totalTimeSailedC0_InRace3 = leaderboard.getTotalTimeSailed(c[0], later.plus(1000));
|
||||
assertEquals(later.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace3.asMillis());
|
||||
Duration totalTimeSailedC0_AtEndOfRace3 = leaderboard.getTotalTimeSailed(c[0], finish);
|
||||
assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AtEndOfRace3.asMillis());
|
||||
Duration totalTimeSailedC0_AfterRace3 = leaderboard.getTotalTimeSailed(c[0], finish.plus(1000));
|
||||
assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AfterRace3.asMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -71,8 +71,9 @@ public interface Leaderboard extends Named {
|
||||
}
|
||||
|
||||
LeaderboardDTO computeDTO(final TimePoint timePoint,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory)
|
||||
throws NoWindException;
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
||||
final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
DomainFactory baseDomainFactory) throws NoWindException;
|
||||
|
||||
/**
|
||||
* Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this
|
||||
@@ -396,7 +397,7 @@ public interface Leaderboard extends Named {
|
||||
* or the competitor hasn't started sailing a single race at <code>timePoint</code> for any of the tracked
|
||||
* races attached to this leaderboard
|
||||
*/
|
||||
Long getTotalTimeSailedInMilliseconds(Competitor competitor, TimePoint timePoint);
|
||||
Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Computes the distance the <code>competitor</code> has sailed in the tracked races in this leaderboard, starting
|
||||
@@ -452,6 +453,7 @@ public interface Leaderboard extends Named {
|
||||
* point at which to evaluate the leaderboard status
|
||||
* @param namesOfRaceColumnsForWhichToLoadLegDetails
|
||||
* the names of the race columns of which to expand the details in the result
|
||||
* @param addOverallDetails TODO
|
||||
* @param trackedRegattaRegistry
|
||||
* used to determine which of the races are still being tracked and which ones are not
|
||||
* @param baseDomainFactory
|
||||
@@ -459,7 +461,7 @@ public interface Leaderboard extends Named {
|
||||
*/
|
||||
LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
|
||||
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
|
||||
boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
|
||||
InterruptedException, ExecutionException;
|
||||
|
||||
NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher();
|
||||
|
||||
+19
-14
@@ -19,8 +19,7 @@ import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.common.impl.Util.Triple;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
|
||||
import com.sap.sailing.domain.leaderboard.ScoreCorrection;
|
||||
@@ -47,13 +46,15 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
private static final Logger logger = Logger.getLogger(LeaderboardDTOCache.class.getName());
|
||||
|
||||
/**
|
||||
* In live operations, {@link #getLeaderboardByName(Date, Collection, DomainFactory, TrackedRegattaRegistry)} is the application's
|
||||
* In live operations, {@link #getLeaderboardByName(Date, Collection, boolean, DomainFactory, TrackedRegattaRegistry)} is the application's
|
||||
* bottleneck. When two clients ask the same data for the same leaderboard with their
|
||||
* <code>waitForLatestAnalyses</code> parameters set to <code>false</code>, expansion state and (quantized) time
|
||||
* stamp, no two computations should be spawned for the two clients. Instead, if the computation is still running,
|
||||
* all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache.
|
||||
* all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache. The key is
|
||||
* the time point for the query, the names of the races for which details are requested and the flag telling
|
||||
* whether overall details for the leaderboard are requested.
|
||||
*/
|
||||
private final Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> leaderboardCache;
|
||||
private final Map<Triple<TimePoint, Collection<String>, Boolean>, FutureTask<LeaderboardDTO>> leaderboardCache;
|
||||
private final NamedReentrantReadWriteLock leaderboardCacheLock;
|
||||
private int leaderboardByNameCacheHitCount;
|
||||
private int leaderboardByNameCacheMissCount;
|
||||
@@ -83,10 +84,10 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
this.leaderboard = leaderboard;
|
||||
this.waitForLatestAnalyses = waitForLatestAnalyses;
|
||||
// Note: don't use access-based ordering as it turns the get(...) call into a "write" access
|
||||
this.leaderboardCache = new LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>(16, 0.75f) {
|
||||
this.leaderboardCache = new LinkedHashMap<Triple<TimePoint, Collection<String>, Boolean>, FutureTask<LeaderboardDTO>>(16, 0.75f) {
|
||||
private static final long serialVersionUID = 7287916997229815039L;
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> e) {
|
||||
protected boolean removeEldestEntry(Map.Entry<Triple<TimePoint, Collection<String>, Boolean>, FutureTask<LeaderboardDTO>> e) {
|
||||
return size() > 10; // remember 10 LeaderboardDTOs per leaderborad
|
||||
}
|
||||
};
|
||||
@@ -118,10 +119,14 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
* The {@link #waitForLatestAnalyses} field is passed on to
|
||||
* {@link SailingServiceImpl#computeLeaderboardByName(Leaderboard, TimePoint, Collection, boolean)} if a new cache
|
||||
* entry needs to be computed. Caching distinguished between
|
||||
*
|
||||
* @param addOverallDetails
|
||||
* tells whether the columns containing the overall details shall be added
|
||||
*/
|
||||
public LeaderboardDTO getLeaderboardByName(final TimePoint timePoint, final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
final DomainFactory baseDomainFactory, final TrackedRegattaRegistry trackedRegattaRegistry) throws NoWindException, InterruptedException,
|
||||
ExecutionException {
|
||||
public LeaderboardDTO getLeaderboardByName(final TimePoint timePoint,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean addOverallDetails,
|
||||
final DomainFactory baseDomainFactory, final TrackedRegattaRegistry trackedRegattaRegistry)
|
||||
throws NoWindException, InterruptedException, ExecutionException {
|
||||
long startOfRequestHandling = System.currentTimeMillis();
|
||||
final TimePoint adjustedTimePoint;
|
||||
TimePoint timePointOfLastModification = leaderboard.getTimePointOfLatestModification();
|
||||
@@ -131,8 +136,8 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
} else {
|
||||
adjustedTimePoint = timePoint;
|
||||
}
|
||||
Util.Pair<TimePoint, Collection<String>> key = new Util.Pair<TimePoint, Collection<String>>(adjustedTimePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
Triple<TimePoint, Collection<String>, Boolean> key = new Triple<TimePoint, Collection<String>, Boolean>(adjustedTimePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
|
||||
FutureTask<LeaderboardDTO> future = null;
|
||||
boolean cacheHit = false;
|
||||
/*
|
||||
@@ -161,8 +166,8 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
LockUtil.propagateLockSetFrom(callerThread);
|
||||
try {
|
||||
LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, waitForLatestAnalyses,
|
||||
trackedRegattaRegistry, baseDomainFactory);
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
|
||||
waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory);
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unpropagateLockSetFrom(callerThread);
|
||||
|
||||
+50
-19
@@ -71,11 +71,13 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
*/
|
||||
private final Set<String> columnNamesForWhichCurrentLiveLeaderboardHasTheDetails;
|
||||
|
||||
private boolean currentLiveLeaderboardHasOverallDetails;
|
||||
|
||||
private boolean running;
|
||||
|
||||
/**
|
||||
* For each String from <code>namesOfRaceColumnsForWhichToLoadLegDetails</code> passed to
|
||||
* {@link #getLiveLeaderboard(Collection)}, records the validity time point of the last request here when the
|
||||
* {@link #getLiveLeaderboard(Collection, boolean)}, records the validity time point of the last request here when the
|
||||
* {@link #currentLiveLeaderboard} has been updated with results containing this column details. This updater will
|
||||
* stop computing the details for that column if the validity time for the update calculation is more than
|
||||
* {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds after the time point recorded here.
|
||||
@@ -84,7 +86,12 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
private final Map<String, TimePoint> timePointOfLastRequestForColumnDetails;
|
||||
|
||||
/**
|
||||
* As soon as the first {@link #getLiveLeaderboard(Collection)} request has been received, this field tells the
|
||||
* Tells when {@link #updateRequestTimes(Collection, boolean)} was last called with the request for overall details
|
||||
*/
|
||||
private TimePoint timePointOfLastRequestForOverallDetails;
|
||||
|
||||
/**
|
||||
* As soon as the first {@link #getLiveLeaderboard(Collection, boolean)} request has been received, this field tells the
|
||||
* "validity time point" (as opposed to the request time point; so not when the request was received but for which
|
||||
* time point the request was asking the data) for which the last general request asked the leaderboard contents,
|
||||
* regardless the combination of column details requested. This is used to decide when to stop a thread running this
|
||||
@@ -117,14 +124,15 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
return leaderboard;
|
||||
}
|
||||
|
||||
public LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails) throws NoWindException {
|
||||
public LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails) throws NoWindException {
|
||||
LeaderboardDTO result = null;
|
||||
updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
|
||||
ensureRunning();
|
||||
synchronized (this) {
|
||||
if (running
|
||||
&& columnNamesForWhichCurrentLiveLeaderboardHasTheDetails
|
||||
.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)) {
|
||||
.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)
|
||||
&& (!addOverallDetails || currentLiveLeaderboardHasOverallDetails)) {
|
||||
result = currentLiveLeaderboard;
|
||||
if (result != null) {
|
||||
cacheHitCount++;
|
||||
@@ -140,12 +148,13 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
cacheMissCount++;
|
||||
synchronized (this) {
|
||||
while (result == null) {
|
||||
if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)) {
|
||||
if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails) &&
|
||||
(!addOverallDetails || currentLiveLeaderboardHasOverallDetails)) {
|
||||
result = currentLiveLeaderboard;
|
||||
}
|
||||
if (result == null) {
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails);
|
||||
}
|
||||
ensureRunning();
|
||||
try {
|
||||
@@ -153,26 +162,27 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.INFO, "interrupted while waiting for LiveLeaderboardCache update", e);
|
||||
}
|
||||
if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)) {
|
||||
if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails) &&
|
||||
(!addOverallDetails || currentLiveLeaderboardHasOverallDetails)) {
|
||||
result = currentLiveLeaderboard;
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("successfully waited for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
logger.finest("successfully waited for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails);
|
||||
}
|
||||
} else {
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" unsuccessful. Need to try again...");
|
||||
logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails+" unsuccessful. Need to try again...");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" was provided in the meantime");
|
||||
logger.finest("leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails+" was provided in the meantime");
|
||||
}
|
||||
}
|
||||
// now we either have a result (good, we're done), or the thread stopped running (then we need to renew the request)
|
||||
// or the thread still runs but the result may have expired and therefore be null (renew the request)
|
||||
if (result == null) {
|
||||
// need to renew the request
|
||||
updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
|
||||
ensureRunning();
|
||||
}
|
||||
}
|
||||
@@ -205,8 +215,11 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
*
|
||||
* This method assumes that <code>namesOfRaceColumnsForWhichToLoadLegDetails</code> tells the column names for which
|
||||
* <code>result</code> has the details.
|
||||
*
|
||||
* @param addOverallDetails tells whether overall details are requested for the resulting leaderboard; see also
|
||||
* {@link Leaderboard#computeDTO(TimePoint, Collection, boolean, boolean, TrackedRegattaRegistry, DomainFactory)}.
|
||||
*/
|
||||
private synchronized void updateRequestTimes(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails) {
|
||||
private synchronized void updateRequestTimes(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails) {
|
||||
lastRequest = getLeaderboard().getNowMinusDelay();
|
||||
for (String nameOfRaceColumn : namesOfRaceColumnsForWhichToLoadLegDetails) {
|
||||
if (!timePointOfLastRequestForColumnDetails.containsKey(nameOfRaceColumn) ||
|
||||
@@ -214,22 +227,28 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
timePointOfLastRequestForColumnDetails.put(nameOfRaceColumn, lastRequest);
|
||||
}
|
||||
}
|
||||
if (addOverallDetails && (timePointOfLastRequestForOverallDetails == null || lastRequest.after(timePointOfLastRequestForOverallDetails))) {
|
||||
timePointOfLastRequestForOverallDetails = lastRequest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cache contents and notifies all waiters on this object.
|
||||
*/
|
||||
private synchronized void updateCacheContents(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
LeaderboardDTO result) {
|
||||
boolean addOverallDetails, LeaderboardDTO result) {
|
||||
columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.clear();
|
||||
columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.addAll(namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
currentLiveLeaderboard = result;
|
||||
currentLiveLeaderboardHasOverallDetails = addOverallDetails;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps computing the leaderboard for the column names that appear as key in {@link #timePointOfLastRequestForColumnDetails}
|
||||
* until no request has happened
|
||||
* Keeps computing the leaderboard for the column names that appear as key in
|
||||
* {@link #timePointOfLastRequestForColumnDetails} and for the overall details as noted in
|
||||
* {@link #timePointOfLastRequestForOverallDetails} until no request has happened during the
|
||||
* {@link #UPDATE_TIMEOUT_IN_MILLIS timeout period}.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -253,10 +272,11 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
TimePoint timeLastUpdateWasStarted = now;
|
||||
try {
|
||||
final Set<String> namesOfRaceColumnsForWhichToLoadLegDetails = getColumnNamesForWhichToFetchDetails(timePoint);
|
||||
final boolean addOverallDetails = getOverallDetails(timePoint);
|
||||
LeaderboardDTO newCacheValue = leaderboard.computeDTO(timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, /* waitForLatestAnalyses */false,
|
||||
trackedRegattaRegistry, baseDomainFactory);
|
||||
updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, newCacheValue);
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
|
||||
/* waitForLatestAnalyses */false, trackedRegattaRegistry, baseDomainFactory);
|
||||
updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, newCacheValue);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Unable to update cached leaderboard results for leaderboard " + leaderboard.getName() + ": "
|
||||
+ e.getMessage());
|
||||
@@ -290,6 +310,17 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines based on {@link #timePointOfLastRequestForOverallDetails} and the <code>timePoint</code> parameter
|
||||
* if the duration between then is less than {@link #UPDATE_TIMEOUT_IN_MILLIS} in which case overall details are to
|
||||
* be loaded. If {@link #timePointOfLastRequestForOverallDetails} is <code>null</code>, meaning that overall details
|
||||
* have never been requested for the leaderboard before, <code>false</code> is returned immediately.
|
||||
*/
|
||||
private boolean getOverallDetails(TimePoint timePoint) {
|
||||
return timePointOfLastRequestForOverallDetails != null &&
|
||||
!timePoint.after(timePointOfLastRequestForOverallDetails.plus(UPDATE_TIMEOUT_IN_MILLIS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines those column names from {@link #timePointOfLastRequestForColumnDetails}'s keys for which the last
|
||||
* request is less then {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds before <code>timePoint</code>
|
||||
|
||||
+82
-48
@@ -57,7 +57,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
||||
import com.sap.sailing.domain.common.dto.LegEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
@@ -913,6 +912,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
@Override
|
||||
public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException {
|
||||
return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap<TrackedLeg, LegType>());
|
||||
}
|
||||
|
||||
private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map<TrackedLeg, LegType> legTypeCache) throws NoWindException {
|
||||
Duration result = null;
|
||||
// TODO should we ensure that competitor participated in all race columns?
|
||||
outerLoop:
|
||||
@@ -921,14 +924,23 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
trackedRace.getRace().getCourse().lockForRead();
|
||||
try {
|
||||
for (Leg leg : trackedRace.getRace().getCourse().getLegs()) {
|
||||
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
|
||||
if (trackedLeg.hasStartedLeg(timePoint)) {
|
||||
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg);
|
||||
if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) {
|
||||
// find out leg type at the time the competitor started the leg
|
||||
try {
|
||||
LegType trackedLegType = trackedRace.getTrackedLeg(leg).getLegType(
|
||||
trackedLeg.getStartTime());
|
||||
final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg);
|
||||
LegType trackedLegType = legTypeCache.get(trackedLeg);
|
||||
if (trackedLegType == null) {
|
||||
final TimePoint startTime = trackedLegOfCompetitor.getStartTime();
|
||||
TimePoint finishTime = trackedLegOfCompetitor.getFinishTime();
|
||||
if (finishTime == null) {
|
||||
finishTime = timePoint;
|
||||
}
|
||||
trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg
|
||||
legTypeCache.put(trackedLeg, trackedLegType);
|
||||
}
|
||||
if (legType == trackedLegType) {
|
||||
Duration timeSpentOnDownwind = trackedLeg.getTime(timePoint);
|
||||
Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint);
|
||||
if (timeSpentOnDownwind != null) {
|
||||
if (result == null) {
|
||||
result = timeSpentOnDownwind;
|
||||
@@ -959,8 +971,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTotalTimeSailedInMilliseconds(Competitor competitor, TimePoint timePoint) {
|
||||
Long result = null;
|
||||
public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) {
|
||||
Duration result = null;
|
||||
for (TrackedRace trackedRace : getTrackedRaces()) {
|
||||
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
@@ -983,11 +995,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
to = timePoint;
|
||||
}
|
||||
}
|
||||
long timeSpent = to.asMillis() - from.asMillis();
|
||||
Duration timeSpent = from.until(to);
|
||||
if (result == null) {
|
||||
result = timeSpent;
|
||||
} else {
|
||||
result += timeSpent;
|
||||
result=result.plus(timeSpent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1090,12 +1102,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
@Override
|
||||
public LeaderboardDTO computeDTO(final TimePoint timePoint,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory)
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
||||
final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory)
|
||||
throws NoWindException {
|
||||
long startOfRequestHandling = System.currentTimeMillis();
|
||||
LeaderboardDTO result = null;
|
||||
result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
|
||||
final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
|
||||
: this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
|
||||
this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), this
|
||||
.getScoringScheme().isHigherBetter(), new UUIDGenerator());
|
||||
@@ -1105,28 +1116,53 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
|
||||
result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
|
||||
}
|
||||
for (RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
|
||||
// Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
|
||||
// futures to finish:
|
||||
Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
|
||||
this.getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint));
|
||||
for (Fleet fleet : raceColumn.getFleets()) {
|
||||
RegattaAndRaceIdentifier raceIdentifier = null;
|
||||
RaceDTO race = null;
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||
|
||||
final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet);
|
||||
if (trackedRace != null) {
|
||||
raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(),
|
||||
trackedRace.getRace().getName());
|
||||
race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace);
|
||||
}
|
||||
|
||||
// Note: the RaceColumnDTO won't be created by the following addRace call because it has been created
|
||||
// above by the result.createEmptyRaceColumn call
|
||||
result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), fleetDTO,
|
||||
raceColumn.isMedalRace(), raceIdentifier, race);
|
||||
}
|
||||
result.setCompetitorsFromBestToWorst(raceColumnDTO,
|
||||
baseDomainFactory.getCompetitorDTOList(this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
|
||||
FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(new Callable<List<CompetitorDTO>>() {
|
||||
@Override
|
||||
public List<CompetitorDTO> call() throws Exception {
|
||||
return baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint));
|
||||
}
|
||||
});
|
||||
executor.execute(task);
|
||||
competitorsFromBestToWorstTasks.put(raceColumn, task);
|
||||
}
|
||||
// wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data
|
||||
for (Map.Entry<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
|
||||
try {
|
||||
result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
// See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
|
||||
// null values.
|
||||
// If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
|
||||
// this helps robustness from a user's perspective.
|
||||
logger.log(
|
||||
Level.SEVERE,
|
||||
AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
|
||||
+ timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
|
||||
+ "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e);
|
||||
}
|
||||
}
|
||||
result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis());
|
||||
result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>();
|
||||
@@ -1138,8 +1174,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
result.discardThresholds = null;
|
||||
}
|
||||
// competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg
|
||||
// details
|
||||
// are requested only once and pass them into getLeaderboardEntryDTO
|
||||
// details are requested only once and pass them into getLeaderboardEntryDTO
|
||||
final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>();
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
// if details for the column are requested, cache the leg's ranks
|
||||
@@ -1167,7 +1202,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>();
|
||||
row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null;
|
||||
row.totalPoints = this.getTotalPoints(competitor, timePoint);
|
||||
addOverallDetailsToRow(timePoint, competitor, row);
|
||||
if (addOverallDetails) {
|
||||
addOverallDetailsToRow(timePoint, competitor, row);
|
||||
}
|
||||
result.competitors.add(competitorDTO);
|
||||
Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
@@ -1176,17 +1213,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
@Override
|
||||
public LeaderboardEntryDTO call() {
|
||||
try {
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn,
|
||||
timePoint);
|
||||
return getLeaderboardEntryDTO(
|
||||
entry,
|
||||
raceColumn,
|
||||
competitor,
|
||||
timePoint,
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
|
||||
.getName()), waitForLatestAnalyses, legRanksCache,
|
||||
baseDomainFactory);
|
||||
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Exception trying to compute leaderboard entry for competitor "
|
||||
+ competitor.getName() + " in race column " + raceColumn.getName() + ": "
|
||||
@@ -1211,12 +1242,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
// See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
|
||||
// null values.
|
||||
// If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
|
||||
// this helps
|
||||
// robustness from a user's perspective.
|
||||
// this helps robustness from a user's perspective.
|
||||
logger.log(
|
||||
Level.SEVERE,
|
||||
AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
|
||||
+ timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails
|
||||
+ timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
|
||||
+ "): exception during computing leaderboard entry for competitor "
|
||||
+ competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey()
|
||||
+ ". Leaving empty.", e);
|
||||
@@ -1228,8 +1258,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
result.competitorDisplayNames.put(competitorDTO, displayName);
|
||||
}
|
||||
}
|
||||
logger.info("computeLeaderboardByName("+this.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+") took "+
|
||||
(System.currentTimeMillis()-startOfRequestHandling)+"ms");
|
||||
logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", "
|
||||
+ namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took "
|
||||
+ (System.currentTimeMillis() - startOfRequestHandling) + "ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1240,14 +1271,15 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots();
|
||||
row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate();
|
||||
}
|
||||
final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint);
|
||||
Map<TrackedLeg, LegType> legTypeCache = new HashMap<>();
|
||||
final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache);
|
||||
row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds();
|
||||
final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint);
|
||||
final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache);
|
||||
row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds();
|
||||
final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint);
|
||||
final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache);
|
||||
row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds();
|
||||
final Long totalTimeSailedInMilliseconds = this.getTotalTimeSailedInMilliseconds(competitor, timePoint);
|
||||
row.totalTimeSailedInSeconds = totalTimeSailedInMilliseconds==null?null:1./1000.*totalTimeSailedInMilliseconds;
|
||||
final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint);
|
||||
row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds();
|
||||
final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
|
||||
row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
|
||||
}
|
||||
@@ -1623,10 +1655,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
|
||||
private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException {
|
||||
boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException {
|
||||
LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry,
|
||||
baseDomainFactory);
|
||||
return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
|
||||
}
|
||||
|
||||
private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
@@ -1663,8 +1695,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, InterruptedException, ExecutionException {
|
||||
public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
|
||||
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
|
||||
InterruptedException, ExecutionException {
|
||||
LeaderboardDTO result = null;
|
||||
if (timePoint == null) {
|
||||
// date==null means live mode; however, if we're after the end of all races and after all score
|
||||
@@ -1680,7 +1714,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
} else {
|
||||
// don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead:
|
||||
timePoint = null;
|
||||
result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, trackedRegattaRegistry, baseDomainFactory);
|
||||
result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory);
|
||||
}
|
||||
}
|
||||
if (timePoint != null) {
|
||||
@@ -1688,7 +1722,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
// which is OK because the cache is invalidated whenever any of the tracked races attached to the
|
||||
// leaderboard changes.
|
||||
result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
baseDomainFactory, trackedRegattaRegistry);
|
||||
addOverallDetails, baseDomainFactory, trackedRegattaRegistry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+8
-2
@@ -501,7 +501,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
for (MarkPassing markPassing : markPassings) {
|
||||
// try to find corresponding old start mark passing
|
||||
if (oldStartMarkPassing != null
|
||||
&& markPassing.getWaypoint().getName().equals(oldStartMarkPassing.getWaypoint().getName())) {
|
||||
&& markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) {
|
||||
if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null
|
||||
&& markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) {
|
||||
requiresStartTimeUpdate = false;
|
||||
@@ -532,7 +532,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint);
|
||||
LockUtil.lockForWrite(markPassingsLock2);
|
||||
try {
|
||||
// TODO wouldn't we need to remove a previous mark passing of competitor for the same waypoint before re-adding it?
|
||||
// The mark passings of competitor have been removed by the call to clearMarkPassings(competitor) above
|
||||
// from both, the collection that holds the mark passings by waypoint and the one that holds the
|
||||
// mark passings per competitor; so we can simply add here:
|
||||
markPassingsInOrderForWaypoint.add(markPassing);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock2);
|
||||
@@ -574,6 +576,10 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
getRace().getCourse().unlockAfterRead();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all mark passings of <code>competitor</code> from both, the {@link #markPassingsForCompetitor}
|
||||
* and the {@link #markPassingsForWaypoint} collections.
|
||||
*/
|
||||
private void clearMarkPassings(Competitor competitor) {
|
||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings);
|
||||
|
||||
+1
-1
@@ -178,7 +178,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase {
|
||||
private void getLeaderboardByNameAndDeleteColumn(){
|
||||
ArrayList<String> al = new ArrayList<String>();
|
||||
al.add(COLUMN1_NAME);
|
||||
service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* previousLeaderboardId */ null, new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* addOverallDetails */ false, /* previousLeaderboardId */ null, new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
fail("Failed to get leaderboard.");
|
||||
|
||||
+2
-2
@@ -98,7 +98,7 @@ public class TestColumnSwapping {
|
||||
sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
|
||||
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
|
||||
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
|
||||
lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null);
|
||||
lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null);
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
fail(e.getLocalizedMessage());
|
||||
@@ -139,7 +139,7 @@ public class TestColumnSwapping {
|
||||
public void testColumnSwappingFabian() {
|
||||
service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
|
||||
try {
|
||||
leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null);
|
||||
leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ true, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null);
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
|
||||
+7
-6
@@ -17,7 +17,7 @@ import com.sap.sse.gwt.client.player.Timer;
|
||||
* the logic how differential, incremental leaderboard fetching and transmission works. This helps guarantee the
|
||||
* requirement of {@link IncrementalOrFullLeaderboardDTO#getLeaderboardDTO(LeaderboardDTO)} (only pass <code>null</code>
|
||||
* as the previous leaderboard if <code>null</code> was passed as identifier of the previous leaderboard to
|
||||
* {@link SailingServiceAsync#getLeaderboardByName(String, Date, Collection, String, AsyncCallback)} too.
|
||||
* {@link SailingServiceAsync#getLeaderboardByName(String, Date, Collection, boolean, String, AsyncCallback)} too.
|
||||
* <p>
|
||||
*
|
||||
* When a valid {@link IncrementalOrFullLeaderboardDTO} object is received, the
|
||||
@@ -32,20 +32,22 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
|
||||
private final String leaderboardName;
|
||||
private final Date date;
|
||||
private final Collection<String> namesOfRacesForWhichToLoadLegDetails;
|
||||
private final boolean addOverallDetails;
|
||||
private final LeaderboardDTO previousLeaderboard;
|
||||
private final StringMessages stringMessages;
|
||||
private final ErrorReporter errorReporter;
|
||||
private final Timer timerToAdjustOffetIn;
|
||||
|
||||
public GetLeaderboardByNameAction(SailingServiceAsync sailingService, String leaderboardName, Date date,
|
||||
final Collection<String> namesOfRacesForWhichToLoadLegDetails, LeaderboardDTO previousLeaderboard,
|
||||
Timer timerToAdjustOffsetIn, ErrorReporter errorReporter, StringMessages stringMessages) {
|
||||
final Collection<String> namesOfRacesForWhichToLoadLegDetails, boolean addOverallDetails,
|
||||
LeaderboardDTO previousLeaderboard, Timer timerToAdjustOffsetIn, ErrorReporter errorReporter, StringMessages stringMessages) {
|
||||
this.sailingService = sailingService;
|
||||
this.errorReporter = errorReporter;
|
||||
this.stringMessages = stringMessages;
|
||||
this.leaderboardName = leaderboardName;
|
||||
this.date = date;
|
||||
this.namesOfRacesForWhichToLoadLegDetails = namesOfRacesForWhichToLoadLegDetails;
|
||||
this.addOverallDetails = addOverallDetails;
|
||||
this.previousLeaderboard = previousLeaderboard;
|
||||
this.timerToAdjustOffetIn = timerToAdjustOffsetIn;
|
||||
}
|
||||
@@ -53,9 +55,8 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
|
||||
@Override
|
||||
public void execute(final AsyncCallback<LeaderboardDTO> callback) {
|
||||
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
|
||||
sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails,
|
||||
previousLeaderboard==null?null:previousLeaderboard.getId(),
|
||||
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, addOverallDetails,
|
||||
previousLeaderboard==null?null:previousLeaderboard.getId(), new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
callback.onFailure(caught);
|
||||
|
||||
+2
-1
@@ -125,7 +125,8 @@ public interface SailingService extends RemoteService {
|
||||
public List<String> getLeaderboardNames();
|
||||
|
||||
IncrementalOrFullLeaderboardDTO getLeaderboardByName(String leaderboardName, Date date,
|
||||
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, String previousLeaderboardId) throws Exception;
|
||||
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
||||
String previousLeaderboardId) throws Exception;
|
||||
|
||||
List<StrippedLeaderboardDTO> getLeaderboards();
|
||||
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ public interface SailingServiceAsync {
|
||||
|
||||
void getLeaderboardByName(String leaderboardName, Date date,
|
||||
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
String previousLeaderboardId, AsyncCallback<IncrementalOrFullLeaderboardDTO> callback);
|
||||
boolean addOverallDetails, String previousLeaderboardId, AsyncCallback<IncrementalOrFullLeaderboardDTO> callback);
|
||||
|
||||
void getLeaderboardNames(AsyncCallback<List<String>> callback);
|
||||
|
||||
|
||||
+8
-4
@@ -135,7 +135,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
|
||||
/**
|
||||
* The leaderboard name is used to
|
||||
* {@link SailingServiceAsync#getLeaderboardByName(String, java.util.Date, String[], String, com.google.gwt.user.client.rpc.AsyncCallback)
|
||||
* {@link SailingServiceAsync#getLeaderboardByName(String, java.util.Date, String[], boolean, String, com.google.gwt.user.client.rpc.AsyncCallback)
|
||||
* obtain the leaderboard contents} from the server. It may change in case the leaderboard is renamed.
|
||||
*/
|
||||
private String leaderboardName;
|
||||
@@ -791,7 +791,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
getSailingService().getLeaderboardByName(getLeaderboardName(),
|
||||
timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(),
|
||||
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(),
|
||||
previousLeaderboard.getId(), new MarkedAsyncCallback<IncrementalOrFullLeaderboardDTO>(
|
||||
shallAddOverallDetails(), previousLeaderboard.getId(), new MarkedAsyncCallback<IncrementalOrFullLeaderboardDTO>(
|
||||
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
@Override
|
||||
public void onSuccess(IncrementalOrFullLeaderboardDTO result) {
|
||||
@@ -1767,7 +1767,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService,
|
||||
getLeaderboardName(), useNullAsTimePoint() ? null : date,
|
||||
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(),
|
||||
/* previousLeaderboard */ getLeaderboard(), timer, errorReporter, stringMessages);
|
||||
shallAddOverallDetails(), /* previousLeaderboard */ getLeaderboard(), timer, errorReporter, stringMessages);
|
||||
this.asyncActionsExecutor.execute(getLeaderboardByNameAction, LOAD_LEADERBOARD_DATA_CATEGORY,
|
||||
new AsyncCallback<LeaderboardDTO>() {
|
||||
@Override
|
||||
@@ -1820,6 +1820,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
return namesOfExpandedRaces;
|
||||
}
|
||||
|
||||
private boolean shallAddOverallDetails() {
|
||||
return !selectedOverallDetailColumns.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns <code>leaderboard</code> to {@link #leaderboard} and updates the UI accordingly. Also updates the min/max
|
||||
* values on the columns.
|
||||
@@ -2519,7 +2523,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
public boolean hasBusyIndicator() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private Iterable<LeaderboardRowDTO> getSelectedRows() {
|
||||
ArrayList<LeaderboardRowDTO> selectedRows = new ArrayList<LeaderboardRowDTO>();
|
||||
for (LeaderboardRowDTO row : getData().getList()) {
|
||||
|
||||
+9
-8
@@ -557,7 +557,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
* <p>
|
||||
*
|
||||
* Otherwise, the leaderboard is computed synchronously on the fly.
|
||||
*
|
||||
* @param previousLeaderboardId
|
||||
* if <code>null</code> or no leaderboard with that {@link LeaderboardDTO#getId() ID} is known, a
|
||||
* {@link FullLeaderboardDTO} will be computed; otherwise, an {@link IncrementalLeaderboardDTO} will be
|
||||
@@ -565,8 +564,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
*/
|
||||
@Override
|
||||
public IncrementalOrFullLeaderboardDTO getLeaderboardByName(final String leaderboardName, final Date date,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, String previousLeaderboardId)
|
||||
throws NoWindException, InterruptedException, ExecutionException, IllegalArgumentException {
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
||||
String previousLeaderboardId) throws NoWindException, InterruptedException, ExecutionException,
|
||||
IllegalArgumentException {
|
||||
try {
|
||||
long startOfRequestHandling = System.currentTimeMillis();
|
||||
IncrementalOrFullLeaderboardDTO result = null;
|
||||
@@ -579,7 +579,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
timePoint = new MillisecondsTimePoint(date);
|
||||
}
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, getService(), baseDomainFactory);
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, getService(), baseDomainFactory);
|
||||
LeaderboardDTO previousLeaderboardDTO = null;
|
||||
synchronized (leaderboardByNameResultsCacheById) {
|
||||
leaderboardByNameResultsCacheById.put(leaderboardDTO.getId(), leaderboardDTO);
|
||||
@@ -625,9 +625,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
result = incrementalResult;
|
||||
}
|
||||
logger.fine("getLeaderboardByName(" + leaderboardName + ", " + date + ", "
|
||||
+ namesOfRaceColumnsForWhichToLoadLegDetails + ") took "
|
||||
+ (System.currentTimeMillis() - startOfRequestHandling) + "ms; diff cache hits/misses "
|
||||
+ leaderboardDifferenceCacheByIdPairHits+"/"+leaderboardDifferenceCacheByIdPairMisses);
|
||||
+ namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails
|
||||
+ ") took " + (System.currentTimeMillis() - startOfRequestHandling)
|
||||
+ "ms; diff cache hits/misses " + leaderboardDifferenceCacheByIdPairHits + "/"
|
||||
+ leaderboardDifferenceCacheByIdPairMisses);
|
||||
}
|
||||
return result;
|
||||
} catch (NoWindException e) {
|
||||
@@ -2502,7 +2503,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
switch (detailType) {
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, Collections.<String> emptyList(),
|
||||
getService(), baseDomainFactory);
|
||||
/* addOverallDetails */ false, getService(), baseDomainFactory);
|
||||
for (RaceColumnDTO raceColumnDTO : leaderboardDTO.getRaceList()) {
|
||||
result.add(getTotalPointsForRaceColumn(leaderboardDTO, raceColumnDTO));
|
||||
}
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
|
||||
private JSONObject getLeaderboardJson(Leaderboard leaderboard, Triple<TimePoint, ResultStates, Integer> timePointAndResultStateAndMaxCompetitorsCount)
|
||||
throws NoWindException, InterruptedException, ExecutionException {
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePointAndResultStateAndMaxCompetitorsCount.getA(),
|
||||
Collections.<String> emptyList(), getService(), getService().getBaseDomainFactory());
|
||||
Collections.<String> emptyList(), /* addOverallDetails */ false, getService(), getService().getBaseDomainFactory());
|
||||
|
||||
TimePoint resultTimePoint = timePointAndResultStateAndMaxCompetitorsCount.getA();
|
||||
ResultStates resultState = timePointAndResultStateAndMaxCompetitorsCount.getB();
|
||||
|
||||
@@ -427,8 +427,8 @@ public class LeaderboardData extends ExportAction {
|
||||
|
||||
if (leaderboard.getTimePointOfLatestModification() != null) {
|
||||
TimePoint timePointOfLatestModification = leaderboard.getTimePointOfLatestModification();
|
||||
Long totalTimeSailed = leaderboard.getTotalTimeSailedInMilliseconds(competitor, timePointOfLatestModification);
|
||||
addNamedElementWithValue(competitorElement, "total_time_sailed_in_milliseconds", totalTimeSailed);
|
||||
Duration totalTimeSailed = leaderboard.getTotalTimeSailed(competitor, timePointOfLatestModification);
|
||||
addNamedElementWithValue(competitorElement, "total_time_sailed_in_milliseconds", totalTimeSailed.asMillis());
|
||||
addNamedElementWithValue(competitorElement, "total_time_sailed_including_non_finished_races_in_milliseconds", getTotalTimeSailedInMilliseconds(competitor, timePointOfLatestModification, true));
|
||||
Distance totalDistanceSailed = leaderboard.getTotalDistanceTraveled(competitor, timePointOfLatestModification);
|
||||
addNamedElementWithValue(competitorElement, "total_distance_sailed_in_meters", totalDistanceSailed != null ? totalDistanceSailed.getMeters() : 0);
|
||||
@@ -449,7 +449,7 @@ public class LeaderboardData extends ExportAction {
|
||||
addNamedElementWithValue(competitorElement, "overall_score", leaderboard.getTotalPoints(competitor, timePointOfLatestModification));
|
||||
} else {
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
addNamedElementWithValue(competitorElement, "total_time_sailed_in_milliseconds", leaderboard.getTotalTimeSailedInMilliseconds(competitor, now));
|
||||
addNamedElementWithValue(competitorElement, "total_time_sailed_in_milliseconds", leaderboard.getTotalTimeSailed(competitor, now).asMillis());
|
||||
addNamedElementWithValue(competitorElement, "total_time_sailed_including_non_finished_races_in_milliseconds", getTotalTimeSailedInMilliseconds(competitor, now, true));
|
||||
Distance totalDistanceSailed = leaderboard.getTotalDistanceTraveled(competitor, now);
|
||||
addNamedElementWithValue(competitorElement, "total_distance_sailed_in_meters", totalDistanceSailed != null ? totalDistanceSailed.getMeters() : 0);
|
||||
|
||||
Reference in new issue
Block a user