Merge branch 'master' into swisstiming-2.0

This commit is contained in:
Axel Uhl committed 2014-04-25 14:37:15 +02:00
commit 2442f5b015
16 files changed
+216 -130

No files matched your search

@@ -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
@@ -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,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);
@@ -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>
@@ -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;
}
@@ -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);
@@ -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.");
@@ -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();
@@ -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);
@@ -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();
@@ -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);
@@ -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()) {
@@ -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));
}
@@ -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);