mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-23 14:08:40 +00:00
Introduced server side shard checking
This commit is contained in:
@@ -17,6 +17,7 @@ Export-Package: com.sap.sailing.domain.common,
|
||||
com.sap.sailing.domain.common.quadtree,
|
||||
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test",
|
||||
com.sap.sailing.domain.common.racelog,
|
||||
com.sap.sailing.domain.common.sharding,
|
||||
com.sap.sailing.domain.common.racelog.tracking,
|
||||
com.sap.sailing.domain.common.racelog.utils,
|
||||
com.sap.sailing.domain.common.scalablevalue.impl,
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.sap.sailing.domain.common.sharding;
|
||||
|
||||
/**
|
||||
* Enum that defines all sharding types known by the system.
|
||||
*
|
||||
* For a sharding to work, we need both client and backend properly identifying a shard.
|
||||
*
|
||||
* While the client has to properly deliver sharding information on each request, the server side
|
||||
* infrastructure must be able to identify the information and properly route the requests.
|
||||
*
|
||||
* The current implementation uses a SUFFIX appended the GWT RPC URLs (allowing path based routing), so that each call to the server
|
||||
* propagates the sharding information through the url.
|
||||
*
|
||||
* A load balancer can be used to properly route each request to the corresponding server node. The first
|
||||
* sharding implemented is the sharding by leaderboard name.
|
||||
*
|
||||
* The suffix appended to the request URL consists of two parts: a prefix that is used to identify the type of shard
|
||||
* and a payload that will be used to identify the shard.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public enum ShardingType {
|
||||
LEADERBOARDNAME("/leaderboard/");
|
||||
|
||||
private String prefix;
|
||||
|
||||
private ShardingType(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public String encodeIfNeeded(String shardingInfo) {
|
||||
if (shardingInfo.startsWith(prefix)) {
|
||||
return shardingInfo;
|
||||
}
|
||||
return new StringBuilder().append(prefix)
|
||||
.append(shardingInfo.replace(' ', '_'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ Export-Package: com.sap.sailing.domain.base,
|
||||
com.sap.sailing.domain.trackimport,
|
||||
com.sap.sailing.domain.tracking,
|
||||
com.sap.sailing.domain.tracking.impl,
|
||||
com.sap.sailing.domain.sharding,
|
||||
com.sap.sailing.domain.windfinder,
|
||||
com.sap.sailing.util,
|
||||
com.sap.sailing.util.impl;x-friends:="com.sap.sailing.server.replication,com.sap.sailing.domain.test"
|
||||
|
||||
+5
@@ -12,7 +12,9 @@ import java.util.logging.Logger;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.sharding.ShardingContext;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
@@ -268,6 +270,7 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
public void run() {
|
||||
assert running;
|
||||
try {
|
||||
ShardingContext.setShardingConstraint(ShardingType.LEADERBOARDNAME, leaderboard.getName());
|
||||
logger.info("Starting " + LiveLeaderboardUpdater.class.getSimpleName() + " thread for leaderboard "
|
||||
+ leaderboard.getName());
|
||||
// interrupt the current thread if not producing a single result within the overall timeout
|
||||
@@ -320,6 +323,8 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
notifyAll();
|
||||
}
|
||||
logger.log(Level.SEVERE, "exception updating live leaderboard "+leaderboard.getName(), e);
|
||||
} finally {
|
||||
ShardingContext.clearShardingConstraint(ShardingType.LEADERBOARDNAME);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -58,6 +58,7 @@ import com.sap.sailing.domain.common.dto.LegEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
|
||||
import com.sap.sailing.domain.common.tracking.BravoFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
@@ -71,6 +72,7 @@ import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
||||
import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
|
||||
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
|
||||
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
||||
import com.sap.sailing.domain.sharding.ShardingContext;
|
||||
import com.sap.sailing.domain.tracking.BravoFixTrack;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
@@ -355,6 +357,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
|
||||
final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory,
|
||||
final boolean fillTotalPointsUncorrected)
|
||||
throws NoWindException {
|
||||
ShardingContext.checkConstraint(ShardingType.LEADERBOARDNAME, getName());
|
||||
long startOfRequestHandling = System.currentTimeMillis();
|
||||
final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
|
||||
final BoatClass boatClass = getBoatClass();
|
||||
|
||||
+166
-124
@@ -52,16 +52,17 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events
|
||||
* received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes
|
||||
* as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful
|
||||
* candidates.
|
||||
* received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard
|
||||
* subscribes as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem
|
||||
* like useful candidates.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardWithCache implements Leaderboard, RaceColumnListener {
|
||||
public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardWithCache
|
||||
implements Leaderboard, RaceColumnListener {
|
||||
private static final long serialVersionUID = 330156778603279333L;
|
||||
|
||||
|
||||
static final Double DOUBLE_0 = new Double(0);
|
||||
|
||||
private final SettableScoreCorrection scoreCorrection;
|
||||
@@ -75,15 +76,15 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
private final Map<Competitor, String> displayNames;
|
||||
|
||||
/**
|
||||
* Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard
|
||||
* with aggregated results of races not tracked / displayed by this leaderboard in detail. The points
|
||||
* provided by this map are considered by {@link #getNetPoints(Competitor, TimePoint)}.
|
||||
* Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard with
|
||||
* aggregated results of races not tracked / displayed by this leaderboard in detail. The points provided by this
|
||||
* map are considered by {@link #getNetPoints(Competitor, TimePoint)}.
|
||||
*/
|
||||
private final Map<Competitor, Double> carriedPoints;
|
||||
|
||||
/**
|
||||
* A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access
|
||||
* is controlled by the {@link #suppressedCompetitorsLock} lock.
|
||||
* A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access is
|
||||
* controlled by the {@link #suppressedCompetitorsLock} lock.
|
||||
*/
|
||||
private final Set<Competitor> suppressedCompetitors;
|
||||
private final NamedReentrantReadWriteLock suppressedCompetitorsLock;
|
||||
@@ -107,8 +108,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
private final Fleet fleet;
|
||||
|
||||
private EntryImpl(Callable<Integer> trackedRankProvider, Double totalPoints,
|
||||
Callable<Double> totalPointsUncorrectedProvider, boolean isTotalPointsCorrected,
|
||||
Double netPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
|
||||
Callable<Double> totalPointsUncorrectedProvider, boolean isTotalPointsCorrected, Double netPoints,
|
||||
MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
|
||||
super();
|
||||
this.trackedRankProvider = trackedRankProvider;
|
||||
this.totalPoints = totalPoints;
|
||||
@@ -119,6 +120,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
this.discarded = discarded;
|
||||
this.fleet = fleet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackedRank() {
|
||||
try {
|
||||
@@ -127,30 +129,37 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTotalPoints() {
|
||||
return totalPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTotalPointsCorrected() {
|
||||
return isTotalPointsCorrected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getNetPoints() {
|
||||
return netPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaxPointsReason getMaxPointsReason() {
|
||||
return maxPointsReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDiscarded() {
|
||||
return discarded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fleet getFleet() {
|
||||
return fleet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTotalPointsUncorrected() {
|
||||
try {
|
||||
@@ -160,7 +169,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed
|
||||
* competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have
|
||||
@@ -171,10 +180,10 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
*
|
||||
* As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors}
|
||||
* competitors list is to determine their number which in turn is only required for high-point scoring systems and
|
||||
* for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes
|
||||
* does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that
|
||||
* need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the
|
||||
* point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be
|
||||
* for computing the default score for penalties. Again, the most frequently used low-point family of scoring
|
||||
* schemes does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those
|
||||
* that need it. Instead of computing it for each call, this interface lets us defer the actual calculation until
|
||||
* the point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be
|
||||
* constructed each time the number shall be computed.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
@@ -183,7 +192,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher {
|
||||
private int numberOfCompetitors = -1;
|
||||
private int numberOfCompetitorsWithoutMaxPointReason = -1;
|
||||
|
||||
|
||||
@Override
|
||||
public int getNumberOfCompetitorsInLeaderboard() {
|
||||
if (numberOfCompetitors == -1) {
|
||||
@@ -191,7 +200,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
return numberOfCompetitors;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getNumberOfCompetitorsWithoutMaxPointReason(RaceColumn column, TimePoint timePoint) {
|
||||
if (numberOfCompetitorsWithoutMaxPointReason == -1) {
|
||||
@@ -215,17 +224,17 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
this.suppressedCompetitors = new HashSet<Competitor>();
|
||||
this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
|
||||
}
|
||||
|
||||
|
||||
protected RaceColumnListeners getRaceColumnListeners() {
|
||||
return raceColumnListeners;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
|
||||
getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
|
||||
super.trackedRaceUnlinked(raceColumn, fleet, trackedRace);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
|
||||
getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
|
||||
@@ -235,10 +244,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
|
||||
getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) {
|
||||
getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel);
|
||||
getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn,
|
||||
newIsFleetsCanRunInParallel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,13 +257,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) {
|
||||
getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward);
|
||||
public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn,
|
||||
boolean firstColumnIsNonDiscardableCarryForward) {
|
||||
getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn,
|
||||
firstColumnIsNonDiscardableCarryForward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
|
||||
getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring);
|
||||
getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn,
|
||||
hasSplitFleetContiguousScoring);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -283,11 +296,13 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
|
||||
@Override
|
||||
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName,
|
||||
displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) {
|
||||
public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule,
|
||||
ResultDiscardingRule newDiscardingRule) {
|
||||
getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule);
|
||||
}
|
||||
|
||||
@@ -304,7 +319,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
protected SettableScoreCorrection createScoreCorrection() {
|
||||
return new ScoreCorrectionImpl(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SettableScoreCorrection getScoreCorrection() {
|
||||
return scoreCorrection;
|
||||
@@ -372,7 +387,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
String oldDisplayName = displayNames.get(competitor);
|
||||
displayNames.put(competitor, displayName);
|
||||
if (!Util.equalsWithNull(oldDisplayName, displayName)) {
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName,
|
||||
displayName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,9 +401,9 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
|
||||
@Override
|
||||
public Double getTotalPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) {
|
||||
return getScoreCorrection().getCorrectedScore(
|
||||
()->getTrackedRank(competitor, raceColumn, timePoint), competitor,
|
||||
raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore();
|
||||
return getScoreCorrection().getCorrectedScore(() -> getTrackedRank(competitor, raceColumn, timePoint),
|
||||
competitor, raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme())
|
||||
.getCorrectedScore();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -399,19 +415,19 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
|
||||
return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint);
|
||||
}
|
||||
|
||||
|
||||
private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn,
|
||||
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
|
||||
.getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
|
||||
raceColumnsToConsider, timePoint);
|
||||
return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can
|
||||
* be specified which is useful when net points are to be computed for more than one column for the same
|
||||
* competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
|
||||
* once and not again for each column (which would lead to quadratic effort).
|
||||
* be specified which is useful when net points are to be computed for more than one column for the same competitor
|
||||
* because then the calculation of discards (which requires looking at all columns) only needs to be done once and
|
||||
* not again for each column (which would lead to quadratic effort).
|
||||
*
|
||||
* @param discardedRaceColumns
|
||||
* expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
|
||||
@@ -420,8 +436,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
*/
|
||||
private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
|
||||
final Set<RaceColumn> discardedRaceColumns) {
|
||||
return !raceColumn.isMedalRace()
|
||||
&& getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
|
||||
return !raceColumn.isMedalRace() && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
|
||||
&& discardedRaceColumns.contains(raceColumn);
|
||||
}
|
||||
|
||||
@@ -431,16 +446,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn,
|
||||
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
|
||||
.getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
|
||||
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable<RaceColumn> raceColumnsToConsider,
|
||||
TimePoint timePoint) {
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
|
||||
raceColumnsToConsider, timePoint);
|
||||
return getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #getNetPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can
|
||||
* be specified which is useful when net points are to be computed for more than one column for the same
|
||||
* Same as {@link #getNetPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race
|
||||
* columns can be specified which is useful when net points are to be computed for more than one column for the same
|
||||
* competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
|
||||
* once and not again for each column (which would lead to quadratic effort).
|
||||
*
|
||||
@@ -450,7 +465,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
* getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
|
||||
*/
|
||||
@Override
|
||||
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set<RaceColumn> discardedRaceColumns) {
|
||||
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
|
||||
Set<RaceColumn> discardedRaceColumns) {
|
||||
Double result;
|
||||
if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) {
|
||||
result = 0.0;
|
||||
@@ -473,11 +489,12 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
@Override
|
||||
public Double getNetPoints(Competitor competitor, final Iterable<RaceColumn> raceColumnsToConsider,
|
||||
TimePoint timePoint) {
|
||||
// when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on
|
||||
// when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race
|
||||
// from there on
|
||||
boolean needToResetScoreUponNextNonEmptyEntry = false;
|
||||
double result = getCarriedPoints(competitor);
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
|
||||
.getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
|
||||
raceColumnsToConsider, timePoint);
|
||||
for (RaceColumn raceColumn : raceColumnsToConsider) {
|
||||
if (raceColumn.isStartsWithZeroScore()) {
|
||||
needToResetScoreUponNextNonEmptyEntry = true;
|
||||
@@ -497,16 +514,19 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
/**
|
||||
* All competitors with non-<code>null</code> total points are added to the result which is then sorted by total points in ascending
|
||||
* order. The fleet, if ordered, is the primary ordering criterion, followed by the total points.
|
||||
* All competitors with non-<code>null</code> total points are added to the result which is then sorted by total
|
||||
* points in ascending order. The fleet, if ordered, is the primary ordering criterion, followed by the total
|
||||
* points.
|
||||
*/
|
||||
@Override
|
||||
public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
|
||||
public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint)
|
||||
throws NoWindException {
|
||||
final Map<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>> totalPointsAndFleet = new HashMap<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>>();
|
||||
for (Competitor competitor : getCompetitors()) {
|
||||
Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint);
|
||||
if (totalPoints != null) {
|
||||
totalPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair<Double, Fleet>(totalPoints, raceColumn.getFleetOfCompetitor(competitor)));
|
||||
totalPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair<Double, Fleet>(totalPoints,
|
||||
raceColumn.getFleetOfCompetitor(competitor)));
|
||||
}
|
||||
}
|
||||
List<Competitor> result = new ArrayList<Competitor>(totalPointsAndFleet.keySet());
|
||||
@@ -518,7 +538,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
comparisonResult = 0;
|
||||
} else {
|
||||
if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) {
|
||||
// only check fleets if there are more than one and the column is not to be contiguously scored even in case
|
||||
// only check fleets if there are more than one and the column is not to be contiguously scored
|
||||
// even in case
|
||||
// of split fleets
|
||||
final Fleet o1Fleet = totalPointsAndFleet.get(o1).getB();
|
||||
final Fleet o2Fleet = totalPointsAndFleet.get(o2).getB();
|
||||
@@ -537,13 +558,14 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// either there are no split fleets or the split isn't relevant for scoring as for ordered fleets
|
||||
// either there are no split fleets or the split isn't relevant for scoring as for ordered
|
||||
// fleets
|
||||
// the scoring runs contiguously from top to bottom
|
||||
comparisonResult = 0;
|
||||
}
|
||||
if (comparisonResult == 0) {
|
||||
comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
|
||||
totalPointsAndFleet.get(o1).getA(), totalPointsAndFleet.get(o2).getA());
|
||||
comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false)
|
||||
.compare(totalPointsAndFleet.get(o1).getA(), totalPointsAndFleet.get(o2).getA());
|
||||
}
|
||||
}
|
||||
return comparisonResult;
|
||||
@@ -559,11 +581,12 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
|
||||
return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* suppressed competitors are removed from the result
|
||||
*/
|
||||
private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
||||
private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider,
|
||||
TimePoint timePoint) {
|
||||
List<Competitor> result = new ArrayList<Competitor>();
|
||||
for (Competitor competitor : getCompetitors()) {
|
||||
result.add(competitor);
|
||||
@@ -571,9 +594,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
||||
return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider);
|
||||
|
||||
protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider,
|
||||
TimePoint timePoint) {
|
||||
return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false,
|
||||
raceColumnsToConsider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -599,15 +624,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
|
||||
public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn,
|
||||
TimePoint timePoint) {
|
||||
TrackedRace trackedRaceForCompetitorInColumn;
|
||||
return getScoringScheme().isValidInNetScore(this, raceColumn, competitor, timePoint) &&
|
||||
(getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) ||
|
||||
((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null &&
|
||||
trackedRaceForCompetitorInColumn.hasStarted(timePoint) &&
|
||||
trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0));
|
||||
return getScoringScheme().isValidInNetScore(this, raceColumn, competitor, timePoint)
|
||||
&& (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint)
|
||||
|| ((trackedRaceForCompetitorInColumn = raceColumn.getTrackedRace(competitor)) != null
|
||||
&& trackedRaceForCompetitorInColumn.hasStarted(timePoint)
|
||||
&& trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addRaceColumnListener(RaceColumnListener listener) {
|
||||
getRaceColumnListeners().addRaceColumnListener(listener);
|
||||
@@ -619,27 +645,31 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException {
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
||||
public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint)
|
||||
throws NoWindException {
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
|
||||
getRaceColumns(), timePoint);
|
||||
return getEntry(competitor, race, timePoint, discardedRaceColumns);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint,
|
||||
Set<RaceColumn> discardedRaceColumns) throws NoWindException {
|
||||
Callable<Integer> trackedRankProvider = ()->getTrackedRank(competitor, race, timePoint);
|
||||
Callable<Integer> trackedRankProvider = () -> getTrackedRank(competitor, race, timePoint);
|
||||
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race,
|
||||
this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
|
||||
final Double correctedScore = correctedResults.getCorrectedScore();
|
||||
return new EntryImpl(trackedRankProvider, correctedScore, ()->correctedResults.getUncorrectedScore(),
|
||||
correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null
|
||||
: Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded,
|
||||
race.getFleetOfCompetitor(competitor));
|
||||
return new EntryImpl(trackedRankProvider, correctedScore, () -> correctedResults.getUncorrectedScore(),
|
||||
correctedResults.isCorrected(),
|
||||
discarded ? DOUBLE_0
|
||||
: correctedScore == null ? null : Double.valueOf(correctedScore * race.getFactor()),
|
||||
correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException {
|
||||
public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint)
|
||||
throws NoWindException {
|
||||
Map<RaceColumn, List<Competitor>> result = new LinkedHashMap<>();
|
||||
List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
|
||||
for (RaceColumn raceColumn : getRaceColumns()) {
|
||||
@@ -663,7 +693,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public Map<Competitor, Double> call() {
|
||||
Map<Competitor, Double> netPointsSumPerCompetitorInColumn = new HashMap<>();
|
||||
for (Competitor competitor : getCompetitors()) {
|
||||
netPointsSumPerCompetitorInColumn.put(competitor, getNetPoints(competitor, finalRaceColumnsToConsider, timePoint));
|
||||
netPointsSumPerCompetitorInColumn.put(competitor,
|
||||
getNetPoints(competitor, finalRaceColumnsToConsider, timePoint));
|
||||
}
|
||||
synchronized (result) {
|
||||
return netPointsSumPerCompetitorInColumn;
|
||||
@@ -686,27 +717,29 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException {
|
||||
public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint)
|
||||
throws NoWindException {
|
||||
Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> result = new HashMap<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry>();
|
||||
Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>();
|
||||
for (final RaceColumn raceColumn : getRaceColumns()) {
|
||||
for (final Competitor competitor : getCompetitors()) {
|
||||
Callable<Integer> trackedRankProvider = ()->getTrackedRank(competitor, raceColumn, timePoint);
|
||||
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
|
||||
this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
Callable<Integer> trackedRankProvider = () -> getTrackedRank(competitor, raceColumn, timePoint);
|
||||
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor,
|
||||
raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
|
||||
if (discardedRacesForCompetitor == null) {
|
||||
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
||||
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
|
||||
getRaceColumns(), timePoint);
|
||||
discardedRaces.put(competitor, discardedRacesForCompetitor);
|
||||
}
|
||||
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
|
||||
final Double correctedScore = correctedResults.getCorrectedScore();
|
||||
Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
|
||||
()->correctedResults.getUncorrectedScore(),
|
||||
correctedResults.isCorrected(),
|
||||
discarded ? DOUBLE_0 : (correctedScore==null?null:
|
||||
Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(),
|
||||
discarded, raceColumn.getFleetOfCompetitor(competitor));
|
||||
() -> correctedResults.getUncorrectedScore(), correctedResults.isCorrected(),
|
||||
discarded ? DOUBLE_0
|
||||
: (correctedScore == null ? null
|
||||
: Double.valueOf((correctedScore * raceColumn.getFactor()))),
|
||||
correctedResults.getMaxPointsReason(), discarded, raceColumn.getFleetOfCompetitor(competitor));
|
||||
result.put(new com.sap.sse.common.Util.Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
|
||||
}
|
||||
}
|
||||
@@ -714,7 +747,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
/**
|
||||
* A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard.
|
||||
* A leaderboard will only accept the addition of a race column if the column's name is unique across the
|
||||
* leaderboard.
|
||||
*/
|
||||
@Override
|
||||
public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) {
|
||||
@@ -731,17 +765,18 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
/**
|
||||
* Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to
|
||||
* the <code>leaderboard</code>} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been
|
||||
* modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard,
|
||||
* <code>null</code> is returned. The time point computed this way is a good choice for normalizing queries for later time
|
||||
* points in an attempt to achieve more cache hits.<p>
|
||||
* modified. If no tracked race is attached and no time-stamped score corrections have been applied to the
|
||||
* leaderboard, <code>null</code> is returned. The time point computed this way is a good choice for normalizing
|
||||
* queries for later time points in an attempt to achieve more cache hits.
|
||||
* <p>
|
||||
*
|
||||
* Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used
|
||||
* to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point
|
||||
* returned by this method but that column's attached tracked race has finished before the time point returned by this method,
|
||||
* the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure.
|
||||
* A different means to determine the possibility of changes that happened to this leaderboard must be used for cache
|
||||
* management. Such a facility has to listen for score correction changes, tracked races being attached or detached and
|
||||
* the column structure changing.
|
||||
* Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be
|
||||
* used to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the
|
||||
* time point returned by this method but that column's attached tracked race has finished before the time point
|
||||
* returned by this method, the result of this method won't change. Still, the contents of the leaderboard will
|
||||
* change by a change in column structure. A different means to determine the possibility of changes that happened
|
||||
* to this leaderboard must be used for cache management. Such a facility has to listen for score correction
|
||||
* changes, tracked races being attached or detached and the column structure changing.
|
||||
*
|
||||
* @see TrackedRace#getTimePointOfNewestEvent()
|
||||
* @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity()
|
||||
@@ -750,19 +785,22 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public TimePoint getTimePointOfLatestModification() {
|
||||
TimePoint result = null;
|
||||
for (TrackedRace trackedRace : getTrackedRaces()) {
|
||||
if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) {
|
||||
if (result == null || (trackedRace.getTimePointOfNewestEvent() != null
|
||||
&& trackedRace.getTimePointOfNewestEvent().after(result))) {
|
||||
result = trackedRace.getTimePointOfNewestEvent();
|
||||
}
|
||||
}
|
||||
TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity();
|
||||
if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) {
|
||||
if (timePointOfLastScoreCorrection != null
|
||||
&& (result == null || timePointOfLastScoreCorrection.after(result))) {
|
||||
result = timePointOfLastScoreCorrection;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) {
|
||||
public com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor,
|
||||
TimePoint timePoint) {
|
||||
com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> result = null;
|
||||
// TODO should we ensure that competitor participated in all race columns?
|
||||
for (TrackedRace trackedRace : getTrackedRaces()) {
|
||||
@@ -771,16 +809,17 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
if (!markPassings.isEmpty()) {
|
||||
TimePoint from = markPassings.first().getTimePoint();
|
||||
TimePoint to;
|
||||
if (timePoint.after(markPassings.last().getTimePoint()) &&
|
||||
markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
|
||||
if (timePoint.after(markPassings.last().getTimePoint()) && markPassings.last()
|
||||
.getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
|
||||
// stop counting when competitor finished the race
|
||||
to = markPassings.last().getTimePoint();
|
||||
} else {
|
||||
to = timePoint;
|
||||
}
|
||||
com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to);
|
||||
if (result == null || result.getB() == null ||
|
||||
(maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) {
|
||||
com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = trackedRace.getTrack(competitor)
|
||||
.getMaximumSpeedOverGround(from, to);
|
||||
if (result == null || result.getB() == null || (maxSpeed != null && maxSpeed.getB() != null
|
||||
&& maxSpeed.getB().compareTo(result.getB()) > 0)) {
|
||||
result = maxSpeed;
|
||||
}
|
||||
}
|
||||
@@ -799,7 +838,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<Competitor> getCompetitors() {
|
||||
final Iterable<Competitor> result;
|
||||
@@ -815,10 +854,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
|
||||
return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors());
|
||||
return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet),
|
||||
getSuppressedCompetitors());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -838,7 +878,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
LockUtil.unlockAfterRead(suppressedCompetitorsLock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSuppressed(Competitor competitor) {
|
||||
LockUtil.lockForRead(suppressedCompetitorsLock);
|
||||
@@ -848,12 +888,12 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
LockUtil.unlockAfterRead(suppressedCompetitorsLock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSuppressed(Competitor competitor, boolean suppressed) {
|
||||
if (competitor == null) {
|
||||
throw new IllegalArgumentException("Cannot change suppression for a null competitor");
|
||||
}
|
||||
if (competitor == null) {
|
||||
throw new IllegalArgumentException("Cannot change suppression for a null competitor");
|
||||
}
|
||||
LockUtil.lockForWrite(suppressedCompetitorsLock);
|
||||
try {
|
||||
if (suppressed) {
|
||||
@@ -874,7 +914,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis);
|
||||
return timePoint;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
|
||||
getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
|
||||
@@ -887,14 +927,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "<No course area defined>") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : "<No scoring scheme set>");
|
||||
return getName() + " "
|
||||
+ (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "<No course area defined>") + " "
|
||||
+ (getScoringScheme() != null ? getScoringScheme().getType().name() : "<No scoring scheme set>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
|
||||
return new NumberOfCompetitorsFetcherImpl();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Pair<RaceColumn, Fleet> getRaceColumnAndFleet(TrackedRace trackedRace) {
|
||||
for (final RaceColumn raceColumn : getRaceColumns()) {
|
||||
@@ -914,8 +956,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
Map<Competitor, Boat> competitorsAndTheirBoats = raceColumn.getAllCompetitorsAndTheirBoats();
|
||||
allBoats.addAll(competitorsAndTheirBoats.values());
|
||||
}
|
||||
return Util.getDominantObject(StreamSupport.stream(allBoats.spliterator(), /* parallel */ false).
|
||||
map(b->b.getBoatClass()).collect(Collectors.toList()));
|
||||
return Util.getDominantObject(StreamSupport.stream(allBoats.spliterator(), /* parallel */ false)
|
||||
.map(b -> b.getBoatClass()).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.sap.sailing.domain.sharding;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
|
||||
|
||||
/**
|
||||
* The sharding context is used to store and verify contraints for a thread.
|
||||
*
|
||||
* This is currently used to verify if a given thread, that is explicitly expected to only
|
||||
* trigger the calculation of a given leaderboard, does trigger the calculation of further/ other
|
||||
* leaderboards.
|
||||
*
|
||||
* It is also used to log warnings if a leaderboard is calculated without predefining the thread constraint.
|
||||
*
|
||||
* The enum {@link ShardingType} lists the known sharding types. Each sharding type defines a prefix that will
|
||||
* be used to identify the sharding type from the request, as implemented in {@link #identifyAndSetShardingConstraint(String)}.
|
||||
*
|
||||
* By calling {@link #setShardingConstraint(ShardingType, String)} it is possible to directly set a thread constraint.
|
||||
* This is useful in background computing code, as in {@link LiveLeaderboardUpdater}
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ShardingContext {
|
||||
private static final Logger logger = Logger.getLogger(ShardingContext.class.getName());
|
||||
|
||||
private static final ConcurrentMap<ShardingType, ThreadLocal<String>> shardingMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Sets the sharding context. The type of sharding will be identified by the prefix.
|
||||
*
|
||||
* @param shardingInfo
|
||||
*/
|
||||
public static ShardingType identifyAndSetShardingConstraint(String shardingInfo) {
|
||||
if (shardingInfo == null || shardingInfo.isEmpty()) {
|
||||
logger.warning("Empty sharding constraint");
|
||||
return null;
|
||||
}
|
||||
ThreadLocal<String> identifiedShardingHolder = null;
|
||||
for (ShardingType shardingType : ShardingType.values()) {
|
||||
if (shardingInfo.startsWith(shardingType.getPrefix())) {
|
||||
identifiedShardingHolder = shardingMap.computeIfAbsent(shardingType, t -> new ThreadLocal<>());
|
||||
checkAndSetShardingInfo(shardingType, shardingInfo, identifiedShardingHolder);
|
||||
return shardingType;
|
||||
}
|
||||
}
|
||||
if (identifiedShardingHolder == null) {
|
||||
logger.warning("Could not identify sharding type for: " + shardingInfo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide sharding constraint for given sharding type.
|
||||
*
|
||||
* @param shardingType
|
||||
* @param shardingInfo
|
||||
*/
|
||||
public static void setShardingConstraint(ShardingType shardingType, String shardingInfo) {
|
||||
final String encodedShardingInfo = shardingType.encodeIfNeeded(shardingInfo);
|
||||
ThreadLocal<String> shardingHolder = shardingMap.computeIfAbsent(shardingType, t -> new ThreadLocal<>());
|
||||
checkAndSetShardingInfo(shardingType, encodedShardingInfo, shardingHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provided sharding information name equals shard constraint.
|
||||
*
|
||||
* If the information is null or empty, we log a warning.
|
||||
* If shard does not have a constraint, we log a warning. If
|
||||
* the information provided does not match information previously stored in shard,
|
||||
* we make a severe log entry.
|
||||
*
|
||||
* @param shardingInfo
|
||||
*/
|
||||
public static void checkConstraint(final ShardingType type, final String shardingInfo) {
|
||||
if (shardingInfo == null || shardingInfo.isEmpty()) {
|
||||
logger.severe("Empty sharding constraint");
|
||||
return;
|
||||
}
|
||||
final ThreadLocal<String> shardingHolder = shardingMap.get(type);
|
||||
if (shardingHolder == null) {
|
||||
logger.log(Level.SEVERE, "No current sharding context set for " + type.name(), new RuntimeException());
|
||||
return;
|
||||
}
|
||||
String currentShardingInfo = shardingHolder.get();
|
||||
if (currentShardingInfo == null || currentShardingInfo.isEmpty()) {
|
||||
logger.warning("No current sharding constraint for " + type.name());
|
||||
return;
|
||||
}
|
||||
final String encodedShardingInfo = type.encodeIfNeeded(shardingInfo);
|
||||
if (!encodedShardingInfo.equals(currentShardingInfo)) {
|
||||
logger.log(Level.SEVERE, "Current sharding constraint vialation for " + type.name() + ". Got "
|
||||
+ shardingInfo + ", shard requires " + currentShardingInfo, new RuntimeException());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearShardingConstraint(ShardingType type) {
|
||||
ThreadLocal<String> shardingHolder = shardingMap.remove(type);
|
||||
if (shardingHolder != null) {
|
||||
shardingHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkAndSetShardingInfo(ShardingType shardingType, String encodedShardingInfo,
|
||||
ThreadLocal<String> shardingHolder) {
|
||||
if (encodedShardingInfo == null || encodedShardingInfo.isEmpty()) {
|
||||
logger.log(Level.SEVERE, "Cannot set empty sharding information for " + shardingType.name(),
|
||||
new RuntimeException());
|
||||
}
|
||||
String currentShardingInfo = shardingHolder.get();
|
||||
if (currentShardingInfo == null) {
|
||||
shardingHolder.set(encodedShardingInfo);
|
||||
} else if (!encodedShardingInfo.equals(currentShardingInfo)) {
|
||||
logger.log(Level.SEVERE, "Switching shard constraint for " + shardingType.name()+". Got <<" + encodedShardingInfo +">>, expeted <<"+ currentShardingInfo+">>", new RuntimeException());
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-6
@@ -1,19 +1,16 @@
|
||||
package com.sap.sailing.gwt.common.communication.routing;
|
||||
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sse.gwt.client.ServiceRoutingProvider;
|
||||
|
||||
/**
|
||||
* Interface used to provide leaderboard specific routing, see {@link ServiceRoutingProvider}
|
||||
*/
|
||||
public interface ProvidesLeaderboardRouting extends ServiceRoutingProvider {
|
||||
String LEADERBOARDNAME_PREFIX ="/leaderboard/";
|
||||
|
||||
|
||||
String getLeaderboardName();
|
||||
|
||||
default String routingSuffixPath() {
|
||||
return new StringBuilder()
|
||||
.append(LEADERBOARDNAME_PREFIX)
|
||||
.append(getLeaderboardName().replace(' ', '_'))
|
||||
.toString();
|
||||
return ShardingType.LEADERBOARDNAME.encodeIfNeeded(getLeaderboardName());
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -1,8 +1,17 @@
|
||||
package com.sap.sailing.gwt.home.server.servlets;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.sharding.ShardingContext;
|
||||
import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory;
|
||||
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
|
||||
import com.sap.sailing.gwt.home.server.SailingDispatchContextImpl;
|
||||
@@ -47,4 +56,19 @@ public class SailingDispatchServlet extends AbstractDispatchServlet<SailingDispa
|
||||
userStoreTracker.getService(), trackedRaceStatisticsCacheTracker.getService(),
|
||||
request.getClientLocaleName(), getThreadLocalRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
|
||||
ShardingType identifiedShardingType = null;
|
||||
try {
|
||||
if (req instanceof HttpServletRequest) {
|
||||
identifiedShardingType = ShardingContext.identifyAndSetShardingConstraint( ((HttpServletRequest) req).getPathInfo());
|
||||
}
|
||||
super.service(req, res);
|
||||
} finally {
|
||||
if (identifiedShardingType != null) {
|
||||
ShardingContext.clearShardingConstraint(identifiedShardingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -53,6 +53,10 @@ import java.util.stream.StreamSupport;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -282,6 +286,7 @@ import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
|
||||
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
|
||||
import com.sap.sailing.domain.common.security.Permission;
|
||||
import com.sap.sailing.domain.common.security.Permission.Mode;
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.common.tracking.BravoFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
@@ -322,6 +327,7 @@ import com.sap.sailing.domain.regattalike.HasRegattaLike;
|
||||
import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
||||
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
||||
import com.sap.sailing.domain.regattalog.RegattaLogStore;
|
||||
import com.sap.sailing.domain.sharding.ShardingContext;
|
||||
import com.sap.sailing.domain.swisstimingadapter.StartList;
|
||||
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter;
|
||||
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory;
|
||||
@@ -7842,4 +7848,19 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
|
||||
ShardingType identifiedShardingType = null;
|
||||
try {
|
||||
if (req instanceof HttpServletRequest) {
|
||||
identifiedShardingType = ShardingContext.identifyAndSetShardingConstraint(((HttpServletRequest) req).getPathInfo());
|
||||
}
|
||||
super.service(req, res);
|
||||
} finally {
|
||||
if (identifiedShardingType != null) {
|
||||
ShardingContext.clearShardingConstraint(identifiedShardingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user