Merge branch 'master' into ubilabs--rcapp--develop

This commit is contained in:
Peter Siegmund
2015-07-29 09:23:55 +02:00
476 changed files with 7168 additions and 3514 deletions
@@ -35,7 +35,7 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class BarbadosResultImportTest {
private static final MillisecondsTimePoint NOW = MillisecondsTimePoint.now();
private static final TimePoint NOW = MillisecondsTimePoint.now();
private static final String SAMPLE_INPUT_NAME_EMPTY_RESULTS = "RESULTS-505Barbados.xlsx";
private static final String SAMPLE_INPUT_NAME_SOME_RESULTS = "RESULTS2.xlsx";
private static final String RESOURCES = "resources/";
@@ -1,6 +1,6 @@
package com.sap.sailing.datamining.test.util;
import com.sap.sse.datamining.AdditionalResultDataBuilder;
import com.sap.sse.datamining.components.AdditionalResultDataBuilder;
import com.sap.sse.datamining.impl.components.AbstractProcessor;
public class NullProcessor<InputType, ResultType> extends AbstractProcessor<InputType, ResultType> {
@@ -20,6 +20,7 @@ LegSailingDomainRetrieverChain=Segel-Dom
GPSFixSailingDomainRetrieverChain=Segel-Domänen GPS-Fix-Beschaffer
MarkPassingSailingDomainRetrieverChain=Segel-Domänen Tonnenrundungs-Beschaffer
RaceResultSailingDomainRetrieverChain=Segel-Domänen Rennergebnis-Beschaffer
RaceOfCompetitorSailingDomainRetrieverChain=Rennen eines Teilnehmers-Beschaffer
LeaderboardGroup=Ranglisten Gruppe
Leaderboard=Rangliste
Leg=Schenkel
@@ -44,4 +45,15 @@ Bft10=schwerer Sturm
Bft11=orkanartiger Sturm
Bft12=Orkan
RelativeScoreInRace=Relative Wertung im Rennen (0 am besten; 1 am schlechtesten)
WindSpeedInBeaufort=Windgeschwindigkeit in Bft.
WindSpeedInBeaufort=Windgeschwindigkeit in Bft.
DistanceAtStart=Distanz zur Startlinie zum Startzeitpunkt
SpeedAtStart=Geschwindigkeit zum Startzeitpunkt
SpeedTenSecondsBeforeStart=Geschwindigkeit 10s vor dem Startzeitpunkt
RankAtFirstMark=Position beim ersten Wegpunkt
NumberOfTacks=Anzahl der Wenden
NumberOfJibes=Anzahl der Halsen
NumberOfPenaltyCircles=Anzahl der Straf-Kreise
RankGainsOrLosses=Positions-Verluste oder -Gewinne
DistanceToStarboardSideAtStart=Distanz zur Steuerbordseite (% der Länge)
NumberOfPodiumFinish=Azahl der Podium-Siege
NumberOfWins=Anzahl der Siege
@@ -22,6 +22,7 @@ LegSailingDomainRetrieverChain=Sailing Domain Leg Retriever
GPSFixSailingDomainRetrieverChain=Sailing Domain GPS Fix Retriever
MarkPassingSailingDomainRetrieverChain=Sailing Domain Mark Passing Retriever
RaceResultSailingDomainRetrieverChain=Sailing Domain Race Result Retriever
RaceOfCompetitorSailingDomainRetrieverChain=Race of Competitor Retriever
LeaderboardGroup=Leaderboard Group
Leaderboard=Leaderboard
Leg=Leg
@@ -44,4 +45,15 @@ Bft10=Storm
Bft11=Violent storm
Bft12=Hurricane
RelativeScoreInRace=Relative score in race (0 best; 1 worst)
WindSpeedInBeaufort=Wind speed in Bft
WindSpeedInBeaufort=Wind speed in Bft
DistanceAtStart=Distance to start at start
SpeedAtStart=Speed at start
SpeedTenSecondsBeforeStart=Speed 10s before start
RankAtFirstMark=Rank at First Mark
NumberOfTacks=Number of Tacks
NumberOfJibes=Number of Jibes
NumberOfPenaltyCircles=Number of Penalty Circles
RankGainsOrLosses=Rank Gains or Losses
DistanceToStarboardSideAtStart=Distance to Starboard Side (% of Length)
NumberOfPodiumFinish=Number of Podium Finishes
NumberOfWins=Number of Wins
@@ -6,16 +6,19 @@ import java.util.Set;
import org.osgi.framework.BundleContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelTrueSumAggregationProcessor;
import com.sap.sailing.domain.common.Speed;
import com.sap.sse.datamining.DataMiningBundleService;
import com.sap.sse.datamining.DataRetrieverChainDefinition;
import com.sap.sse.datamining.DataSourceProvider;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.impl.AbstractDataMiningActivator;
import com.sap.sse.i18n.ResourceBundleStringMessages;
@@ -74,6 +77,7 @@ public class Activator extends AbstractDataMiningActivator implements DataMining
internalClasses.add(HasTrackedLegOfCompetitorContext.class);
internalClasses.add(HasGPSFixContext.class);
internalClasses.add(HasMarkPassingContext.class);
internalClasses.add(HasRaceOfCompetitorContext.class);
return internalClasses;
}
@@ -91,6 +95,13 @@ public class Activator extends AbstractDataMiningActivator implements DataMining
return dataSourceProviders;
}
@Override
public Iterable<AggregationProcessorDefinition<?, ?>> getAggregationProcessorDefinitions() {
HashSet<AggregationProcessorDefinition<?, ?>> aggregators = new HashSet<>();
aggregators.add(ParallelTrueSumAggregationProcessor.getDefinition());
return aggregators;
}
private void initializeDataSourceProviders() {
dataSourceProviders = new HashSet<>();
dataSourceProviders.add(new RacingEventServiceProvider(context));
@@ -6,6 +6,7 @@ import java.util.Collection;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
@@ -15,13 +16,14 @@ import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.MarkPassingRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.datamining.DataRetrieverChainDefinition;
import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
import com.sap.sse.datamining.impl.components.SimpleDataRetrieverChainDefinition;
public class SailingDataRetrievalChainDefinitions {
@@ -50,6 +52,11 @@ public class SailingDataRetrievalChainDefinitions {
trackedRaceRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain");
markPassingRetrieverChainDefinition.endWith(TrackedRaceRetrievalProcessor.class, MarkPassingRetrievalProcessor.class, HasMarkPassingContext.class, "MarkPassing");
dataRetrieverChainDefinitions.add(markPassingRetrieverChainDefinition);
final DataRetrieverChainDefinition<RacingEventService, HasRaceOfCompetitorContext> raceOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>(
trackedRaceRetrieverChainDefinition, HasRaceOfCompetitorContext.class, "RaceOfCompetitorSailingDomainRetrieverChain");
raceOfCompetitorRetrieverChainDefinition.endWith(TrackedRaceRetrievalProcessor.class, RaceOfCompetitorRetrievalProcessor.class, HasRaceOfCompetitorContext.class, "Competitor");
dataRetrieverChainDefinitions.add(raceOfCompetitorRetrieverChainDefinition);
final DataRetrieverChainDefinition<RacingEventService, HasTrackedLegOfCompetitorContext> legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>(
trackedRaceRetrieverChainDefinition, HasTrackedLegOfCompetitorContext.class, "LegSailingDomainRetrieverChain");
@@ -0,0 +1,44 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Speed;
import com.sap.sse.datamining.shared.annotations.Connector;
import com.sap.sse.datamining.shared.annotations.Statistic;
import com.sap.sse.datamining.shared.data.Unit;
public interface HasRaceOfCompetitorContext {
@Connector(scanForStatistics=false)
public HasTrackedRaceContext getTrackedRaceContext();
@Connector(messageKey="Competitor")
public Competitor getCompetitor();
@Statistic(messageKey="DistanceAtStart", resultUnit=Unit.Meters, resultDecimals=0, ordinal=0)
public double getDistanceToStartLineAtStart();
@Statistic(messageKey="DistanceToStarboardSideAtStart", resultDecimals=2, ordinal=1)
public Double getNormalizedDistanceToStarboardSideAtStart();
@Connector(messageKey="SpeedAtStart", ordinal=2)
public Speed getSpeedAtStart();
@Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=3)
public Speed getSpeedTenSecondsBeforeStart();
@Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=4)
public Double getRankAtFirstMark();
@Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=5)
public Double getNumberOfTacks();
@Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=6)
public Double getNumberOfJibes();
@Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=7)
public Double getNumberOfPenaltyCircles();
@Statistic(messageKey="RankGainsOrLosses", resultDecimals=2, ordinal=8)
public Double getRankGainsOrLosses();
}
@@ -16,7 +16,7 @@ public interface HasRaceResultOfCompetitorContext {
/**
* 0 means the competitor won the race, 1 means the competitor ranked last
*/
@Statistic(messageKey="RelativeScoreInRace", ordinal=3, resultDecimals=2)
@Statistic(messageKey="RelativeScoreInRace", ordinal=1, resultDecimals=2)
public double getRelativeRank();
@Dimension(messageKey="WindSpeedInBeaufort")
@@ -24,4 +24,10 @@ public interface HasRaceResultOfCompetitorContext {
@Dimension(messageKey="Regatta")
String getRegattaName();
@Statistic(messageKey="NumberOfPodiumFinish", ordinal=2)
public Boolean isPodiumFinish();
@Statistic(messageKey="NumberOfWins", ordinal=3)
public Boolean isWin();
}
@@ -18,5 +18,8 @@ public interface HasTrackedLegOfCompetitorContext {
@Statistic(messageKey="DistanceTraveled", resultUnit=Unit.Meters, resultDecimals=0, ordinal=0)
public Double getDistanceTraveled();
@Statistic(messageKey="RankGainsOrLosses", resultDecimals=2, ordinal=1)
public Double getRankGainsOrLosses();
}
@@ -0,0 +1,31 @@
package com.sap.sailing.datamining.impl.components;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.impl.data.RaceOfCompetitorWithContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor;
public class RaceOfCompetitorRetrievalProcessor extends AbstractRetrievalProcessor<HasTrackedRaceContext, HasRaceOfCompetitorContext> {
public RaceOfCompetitorRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasRaceOfCompetitorContext, ?>> resultReceivers, int retrievalLevel) {
super(HasTrackedRaceContext.class, HasRaceOfCompetitorContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
protected Iterable<HasRaceOfCompetitorContext> retrieveData(HasTrackedRaceContext element) {
Collection<HasRaceOfCompetitorContext> raceOfCompetitorsWithContext = new ArrayList<>();
for (Competitor competitor : element.getTrackedRace().getRace().getCompetitors()) {
HasRaceOfCompetitorContext raceOfCompetitorWithContext = new RaceOfCompetitorWithContext(element, competitor);
raceOfCompetitorsWithContext.add(raceOfCompetitorWithContext);
}
return raceOfCompetitorsWithContext;
}
}
@@ -0,0 +1,50 @@
package com.sap.sailing.datamining.impl.components.aggregators;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition;
import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataStoringAggregationProcessor;
import com.sap.sse.datamining.shared.GroupKey;
public class ParallelTrueSumAggregationProcessor
extends AbstractParallelGroupedDataStoringAggregationProcessor<Boolean, Double> {
private static final AggregationProcessorDefinition<Boolean, Double> DEFINITION =
new SimpleAggregationProcessorDefinition<>(Boolean.class, Double.class, "Sum", ParallelTrueSumAggregationProcessor.class);
public static AggregationProcessorDefinition<Boolean, Double> getDefinition() {
return DEFINITION;
}
private Map<GroupKey, Double> result;
public ParallelTrueSumAggregationProcessor(ExecutorService executor,
Collection<Processor<Map<GroupKey, Double>, ?>> resultReceivers) {
super(executor, resultReceivers, "Sum");
result = new HashMap<>();
}
@Override
protected void storeElement(GroupedDataEntry<Boolean> element) {
GroupKey key = element.getKey();
if (!result.containsKey(key)) {
result.put(key, 0.0);
}
Double currentAmount = result.get(key);
if (element.getDataEntry()) {
result.put(key, currentAmount + 1);
}
}
@Override
protected Map<GroupKey, Double> aggregateResult() {
return result;
}
}
@@ -0,0 +1,111 @@
package com.sap.sailing.datamining.impl.data;
import java.util.concurrent.TimeUnit;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
private final HasTrackedRaceContext trackedRaceContext;
private final Competitor competitor;
public RaceOfCompetitorWithContext(HasTrackedRaceContext trackedRaceContext, Competitor competitor) {
this.trackedRaceContext = trackedRaceContext;
this.competitor = competitor;
}
@Override
public HasTrackedRaceContext getTrackedRaceContext() {
return trackedRaceContext;
}
private TrackedRace getTrackedRace() {
return getTrackedRaceContext().getTrackedRace();
}
@Override
public Competitor getCompetitor() {
return competitor;
}
@Override
public double getDistanceToStartLineAtStart() {
return getTrackedRace().getDistanceToStartLine(getCompetitor(), 0).getMeters();
}
@Override
public Speed getSpeedAtStart() {
return getTrackedRace().getSpeed(getCompetitor(), 0);
}
@Override
public Speed getSpeedTenSecondsBeforeStart() {
return getTrackedRace().getSpeed(getCompetitor(), TimeUnit.SECONDS.toMillis(10));
}
@Override
public Double getRankAtFirstMark() {
Course course = getTrackedRace().getRace().getCourse();
Waypoint firstMark = course.getFirstLeg().getTo();
Competitor competitor = getCompetitor();
return Double.valueOf(getTrackedRace().getRank(competitor, getTrackedRace().getMarkPassing(competitor, firstMark).getTimePoint()));
}
@Override
public Double getNumberOfTacks() {
return getNumberOf(ManeuverType.TACK);
}
@Override
public Double getNumberOfJibes() {
return getNumberOf(ManeuverType.JIBE);
}
@Override
public Double getNumberOfPenaltyCircles() {
return getNumberOf(ManeuverType.PENALTY_CIRCLE);
}
private Double getNumberOf(ManeuverType maneuverType) {
TrackedRace trackedRace = getTrackedRace();
double number = 0;
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), trackedRace.getEndOfTracking(), false)) {
if (maneuver.getType() == maneuverType) {
number++;
}
}
return number;
}
@Override
public Double getRankGainsOrLosses() {
Double rankAtFirstMark = getRankAtFirstMark();
Double rankAtFinish = getRankAtFinish();
return rankAtFirstMark - rankAtFinish;
}
private Double getRankAtFinish() {
return Double.valueOf(getTrackedRace().getRank(getCompetitor(), getTrackedRace().getEndOfTracking()));
}
@Override
public Double getNormalizedDistanceToStarboardSideAtStart() {
TrackedRace trackedRace = getTrackedRace();
TrackedLegOfCompetitor firstTrackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, trackedRace.getRace().getCourse().getFirstLeg());
TimePoint competitorStartTime = firstTrackedLegOfCompetitor.getStartTime();
Double distance = trackedRace.getDistanceFromStarboardSideOfStartLine(getCompetitor(), competitorStartTime).getMeters();
Double length = trackedRace.getStartLine(competitorStartTime).getLength().getMeters();
return distance / length;
}
}
@@ -52,6 +52,10 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
return leaderboardWithContext;
}
private Leaderboard getLeaderboard() {
return getLeaderboardContext().getLeaderboard();
}
@Override
public Competitor getCompetitor() {
return competitor;
@@ -59,8 +63,8 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
@Override
public double getRelativeRank() {
Leaderboard leaderboard = getLeaderboardContext().getLeaderboard();
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
Leaderboard leaderboard = getLeaderboard();
final TimePoint now = MillisecondsTimePoint.now();
double competitorCount = Util.size(leaderboard.getCompetitors());
double points = leaderboard.getNetPoints(competitor, raceColumn, now);
double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ?
@@ -154,9 +158,35 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
@Override
public String getRegattaName() {
Leaderboard leaderboard = getLeaderboardContext().getLeaderboard();;
Leaderboard leaderboard = getLeaderboard();;
final String result = leaderboard.getName();
return result;
}
@Override
public Boolean isPodiumFinish() {
Leaderboard leaderboard = getLeaderboard();
final TimePoint now = MillisecondsTimePoint.now();
double points = leaderboard.getNetPoints(competitor, raceColumn, now);
if (leaderboard.getScoringScheme().isHigherBetter()) {
double competitorCount = Util.size(leaderboard.getCompetitors());
return points >= (competitorCount - 2.05);
} else {
return points <= 3.05;
}
}
@Override
public Boolean isWin() {
Leaderboard leaderboard = getLeaderboard();
final TimePoint now = MillisecondsTimePoint.now();
double points = leaderboard.getNetPoints(competitor, raceColumn, now);
if (leaderboard.getScoringScheme().isHigherBetter()) {
double competitorCount = Util.size(leaderboard.getCompetitors());
return points >= (competitorCount - 0.05);
} else {
return points <= 1.05;
}
}
}
@@ -4,6 +4,7 @@ import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompetitorContext {
@@ -39,5 +40,13 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
TimePoint timePoint = getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getEndOfTracking();
return getTrackedLegOfCompetitor().getDistanceTraveled(timePoint).getMeters();
}
@Override
public Double getRankGainsOrLosses() {
TrackedRace trackedRace = getTrackedLegContext().getTrackedRaceContext().getTrackedRace();
Double rankAtStart = Double.valueOf(trackedRace.getRank(getCompetitor(), getTrackedLegOfCompetitor().getStartTime()));
Double rankAtFinish = Double.valueOf(trackedRace.getRank(getCompetitor(), getTrackedLegOfCompetitor().getFinishTime()));
return rankAtStart - rankAtFinish;
}
}
@@ -41,7 +41,7 @@ public enum BoatClassMasterdata {
J24 ("J/24", true, 7.32, 2.67, BoatHullType.MONOHULL, true, "J24", "J-24"),
J70 ("J/70", true, 6.93, 2.25, BoatHullType.MONOHULL, true, "J70", "J-70"),
J80 ("J/80", true, 8.0, 2.51, BoatHullType.MONOHULL, true, "J80", "J-80"),
KIELZUGVOGEL ("Kielzugvogel", true, 5.80, 1.88, BoatHullType.MONOHULL, false),
KIELZUGVOGEL ("Kielzugvogel", true, 5.80, 1.88, BoatHullType.MONOHULL, false, "KZV"),
KITE ("Kite", true, 3.35, 1.52, BoatHullType.MONOHULL, false),
LASER_2 ("Laser 2", true, 4.39, 1.42, BoatHullType.MONOHULL, false, "Laser II", "Laser2", "Laser-2", "Laser-II"),
LASER_4_7 ("Laser 4.7", true, 4.20, 1.39, BoatHullType.MONOHULL, false, "L4.7"),
@@ -1,8 +1,6 @@
package com.sap.sailing.domain.common;
public interface LeaderboardNameConstants {
static final String DEFAULT_LEADERBOARD_NAME = "Default Leaderboard";
static final String OVERALL = "Overall";
static final String DEFAULT_FLEET_NAME = "Default";
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.AbstractSpeedWithAbstractBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.AbstractTimePoint;
/**
* A compact representation of a GPS fix which collects all primitive-typed attributes in one object to avoid
@@ -41,7 +42,7 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
private final double latDeg;
private final double lngDeg;
private final TimePoint timePoint;
private final long timePointAsMillis;
/**
* Tells if in the containing {@link DynamicGPSFixTrackImpl} this fix is considered valid. This cache
@@ -77,6 +78,15 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
}
}
private class CompactTimePoint extends AbstractTimePoint implements TimePoint {
private static final long serialVersionUID = -2470922642359937437L;
@Override
public long asMillis() {
return timePointAsMillis;
}
}
private class CompactEstimatedSpeedBearing extends AbstractBearing {
private static final long serialVersionUID = 8549231429037883121L;
@@ -103,7 +113,7 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
public CompactGPSFixImpl(Position position, TimePoint timePoint) {
latDeg = position.getLatDeg();
lngDeg = position.getLngDeg();
this.timePoint = timePoint;
timePointAsMillis = timePoint==null?-1:timePoint.asMillis();
}
public CompactGPSFixImpl(GPSFix gpsFix) {
@@ -122,7 +132,7 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
@Override
public TimePoint getTimePoint() {
return timePoint;
return new CompactTimePoint();
}
@Override
@@ -0,0 +1,28 @@
package com.sap.sailing.domain.common.tracking.impl;
/**
* Used to assemble a compatible JSON string by themselves, using the constants from here.The field identified by this
* name tells the fully-qualified class name from which the value of the field identified by {@link #FIELD_ID} has been
* created. When deserializing, the ID needs to be re-constructed as an instance of that type.
*/
public class CompetitorJsonConstants {
public static final String FIELD_ID = "id";
public static final String FIELD_NAME = "name";
public static final String FIELD_SAIL_ID = "sailID";
public static final String FIELD_NATIONALITY = "nationality";
public static final String FIELD_COUNTRY_CODE = "countryCode";
public static final String FIELD_BOAT_CLASS_NAME = "boatClassName";
public static final String FIELD_COLOR = "color";
public static final String FIELD_FLAG_IMAGE = "flagImage";
public static final String FIELD_ID_TYPE = "idtype";
public static final String FIELD_NATIONALITY_ISO2 = "nationalityISO2";
public static final String FIELD_NATIONALITY_ISO3 = "nationalityISO3";
public static final String FIELD_TEAM = "team";
public static final String FIELD_BOAT = "boat";
public static final String FIELD_DISPLAY_COLOR = "displayColor";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_FLAG_IMAGE_URI = "flagImageUri";
public static final String FIELD_TIME_ON_TIME_FACTOR = "timeOnTimeFactor";
public static final String FIELD_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE = "timeOnDistanceAllowanceInSecondsPerNauticalMile";
}
@@ -1,4 +1,4 @@
package com.sap.sailing.server.gateway.serialization.impl;
package com.sap.sailing.domain.common.tracking.impl;
/**
* So far only holds the constants required to assemble a JSON object properly. The challenge for this serializer is
@@ -15,7 +15,8 @@ Require-Bundle: org.json.simple;bundle-version="1.1.0",
com.sap.sailing.domain.shared.android,
com.sap.sailing.domain,
com.sap.sailing.declination,
com.sap.sse.common
com.sap.sse.common,
com.sap.sse
Import-Package: javax.ws.rs;version="1.1.1",
javax.ws.rs.core;version="1.1.1",
javax.ws.rs.ext;version="1.1.1",
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory;
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
import com.sap.sailing.domain.tracking.WindTrackerFactory;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
/**
* Maintains data about a default {@link Client} that represents this application when interacting with the Igtimi
@@ -43,7 +44,7 @@ public class Activator implements BundleActivator {
private static final String CLIENT_REDIRECT_URI_PROPERTY_NAME = "igtimi.client.redirecturi";
private final Future<IgtimiConnectionFactoryImpl> connectionFactory;
private final Future<IgtimiWindTrackerFactory> windTrackerFactory;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
logger.info(getClass().getName()+" constructor");
@@ -97,7 +97,7 @@ public class DependentStartTimeFinderTest {
assertEquals(cAfterB, result.getStartTimeDiff());
assertEquals(expectedDependingOnRaces, result.getRacesDependingOn());
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, author, now, "12", null, 12, new MillisecondsTimePoint(5000)));
finder = new StartTimeFinder(raceLogResolver, raceLogB);
@@ -156,7 +156,7 @@ public class DependentStartTimeFinderTest {
assertEquals(aAfterC, finder.analyze().getStartTimeDiff());
// Check that all resolve correctly after changing some element in cycle
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
raceLogB.add(new RaceLogStartTimeEventImpl(now, author, now, "12", null, 12, new MillisecondsTimePoint(5000)));
// now A -> C -> B
@@ -102,7 +102,7 @@ public class DependentRaceStateTest {
raceLogB.add(new RaceLogDependentStartTimeEventImpl(nowMock, author, nowMock, "12", null, 12,
new SimpleRaceLogIdentifierImpl("A", "", ""), new MillisecondsDurationImpl(5000)));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, author, now, "12", null, 12, new MillisecondsTimePoint(5000)));
verify(listenerC, times(3)).onStartTimeChanged(stateC);
@@ -124,7 +124,7 @@ public class DependentRaceStateTest {
bTime[0] = state.getStartTime();
}
});
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, author, now, "12", null, 12, now));
assertEquals(now.plus(delta), bTime[0]);
}
@@ -137,7 +137,7 @@ public class DependentRaceStateTest {
raceLogB.add(new RaceLogDependentStartTimeEventImpl(nowMock, author, nowMock, "12", null, 12,
new SimpleRaceLogIdentifierImpl("A", "", ""), new MillisecondsDurationImpl(5000)));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, author, now, "12", null, 12, new MillisecondsTimePoint(5000)));
raceLogB.add(new RaceLogStartTimeEventImpl(now, author, now, "12", null, 12, new MillisecondsTimePoint(20000)));
@@ -432,7 +432,7 @@ public class RaceLogTest {
RaceLog raceLog = new RaceLogImpl("RaceLogTest", "test-identifier");
UUID client1Id = UUID.randomUUID();
UUID client2Id = UUID.randomUUID();
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
RaceLogStartTimeEvent startTimeEvent1 = RaceLogEventFactory.INSTANCE.createStartTimeEvent(now, author, 1, now.plus(1));
Iterable<RaceLogEvent> empty = raceLog.add(startTimeEvent1, client1Id);
assertTrue(Util.isEmpty(empty));
@@ -145,6 +145,12 @@ public class NoAddingRaceLogWrapper implements RaceLog {
return innerRaceLog.getFixesIterator(startingAt, inclusive);
}
@Override
public Iterator<RaceLogEvent> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive,
TimePoint endingAt, boolean endingAtInclusive) {
return innerRaceLog.getFixesIterator(startingAt, startingAtInclusive, endingAt, endingAtInclusive);
}
@Override
public Iterator<RaceLogEvent> getRawFixesIterator(TimePoint startingAt, boolean inclusive) {
return innerRaceLog.getRawFixesIterator(startingAt, inclusive);
@@ -126,7 +126,23 @@ public interface Track<FixType extends Timed> extends Serializable {
* will be thrown in case the caller has failed to do so.
*/
Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
/**
* Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
* <code>inclusive</code> is <code>true</code>) and that ends at the <code>endingAt</code> time point or just before
* in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the smoothened fixes (see
* also {@link #getFixes()}, without any smoothening or dampening applied.
*
* Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an
* exception will be thrown in case the caller has failed to do so.
*
* @param startingAt
* if <code>null</code>, starts with the first fix available
* @param endingAt
* if <code>null</code>., ends with the last fix available
*/
Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
/**
* Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
* <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
@@ -259,6 +259,22 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
return result;
}
@Override
public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt,
boolean endingAtInclusive) {
assertReadLock();
NavigableSet<FixType> set = getInternalFixes();
if (startingAt != null && endingAt != null) {
set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive);
} else if (endingAt != null) {
set = set.headSet(getDummyFix(endingAt), endingAtInclusive);
} else if (startingAt != null) {
set = set.tailSet(getDummyFix(startingAt), startingAtInclusive);
}
Iterator<FixType> result = set.iterator();
return result;
}
@Override
public Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
assertReadLock();
@@ -336,10 +336,9 @@ public class DomainFactoryImpl implements DomainFactory {
Regatta regatta = raceIDToRegattaCache.get(raceID);
if (regatta != null) {
Set<RaceDefinition> toRemove = new HashSet<RaceDefinition>();
for (RaceDefinition race : regatta.getAllRaces()) {
if (race.getName().equals(raceID)) {
toRemove.add(race);
}
RaceDefinition race = regatta.getRaceByName(raceID);
if (race != null) {
toRemove.add(race);
}
for (RaceDefinition raceToRemove : toRemove) {
regatta.removeRace(raceToRemove);
@@ -13,12 +13,13 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardDTOCalculationReuseCacheTest {
@Test
public void testNullWindCaching() {
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(now);
final Competitor competitor = mock(Competitor.class);
@SuppressWarnings("unchecked")
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
@@ -43,7 +44,7 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
public void leaderboardWithOneRaceTest() throws URISyntaxException, NoWindException, IOException, InterruptedException, SubscriberInitializationException, CreateModelException {
leaderboard = new FlexibleLeaderboardImpl("Kiel Week 2011 505s", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }),
new LowPoint(), null);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
loadRace("event_20110609_KielerWoch-505_Race_2.txt", "event_20110609_KielerWoch-505_Race_2.mtb"); // 505 Race 2
Competitor kevin = getCompetitorByName("Kevin");
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */false);
@@ -120,7 +120,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
leaderboard.addRaceColumn(columnName, /* medalRace */ true);
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false);
final Set<String> emptySet = Collections.emptySet();
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
final TrackedRegattaRegistry trackedRegattaRegistry = new TrackedRegattaRegistry() {
@Override
public DynamicTrackedRegatta getOrCreateTrackedRegatta(Regatta regatta) {
@@ -23,7 +23,7 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LineAnalysisTest extends TrackBasedTest {
private MillisecondsTimePoint now;
private TimePoint now;
private DynamicTrackedRace trackedRace;
@Before
@@ -34,6 +34,7 @@ import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
@@ -49,6 +50,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
* was <code>null</code> when {@link RaceColumn} was linked to {@link TrackedRace}.
*
* @author Alexander Ries (D062114)
* @author Axel Uhl (D043530)
*
*/
public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest {
@@ -69,7 +71,7 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest
private final String RACECOLUMN_FLEXIBLELEADERBOARD = "TestFlexibleLeaderboardRaceColumn";
@Test
public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumn() {
public void testRaceExecutionOrderProviderAttachDetachWithRaceColumn() {
trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(),
MillisecondsTimePoint.now());
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
@@ -215,4 +217,63 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest
/* startDate */null, /* endDate */null, seriesSet, false, scoringScheme, UUID.randomUUID(), null,
OneDesignRankingMetric::new);
}
/**
* See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race"
* definition's transitive closure may become cyclic, and a race may be considered its own
* direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)}
* implementation which traverses a race's predecessors recursively.
*/
@Test
public void testCyclicPreviousRacesSequence() {
createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true);
final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null);
final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
final RaceColumnInSeries r3 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
final RaceColumnInSeries r4 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE);
final TimePoint startOfSecondRace = endOfFirstRace.plus(Duration.ONE_MINUTE);
final TimePoint endOfSecondRace = startOfSecondRace.plus(Duration.ONE_MINUTE);
final TimePoint startOfThirdRace = endOfSecondRace.plus(Duration.ONE_MINUTE);
final TimePoint endOfThirdRace = startOfThirdRace.plus(Duration.ONE_MINUTE);
final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace);
final TrackedRace tr2 = createTrackedRace("SecondRace", startOfSecondRace, endOfSecondRace);
final TrackedRace tr3 = createTrackedRace("ThirdRace", startOfThirdRace, endOfThirdRace);
r1.setTrackedRace(fleet, tr3);
r2.setTrackedRace(fleet, tr1);
r3.setTrackedRace(fleet, tr2);
r4.setTrackedRace(fleet, tr3); // produce a cycle because now transitively tr1 has itself as a predecessor
assertTrue(tr3.takesWindFix(new WindImpl(new DegreePosition(12, 13),
startOfThirdRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)),
new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185)))));
}
/**
* See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race"
* definition's transitive closure may become cyclic, and a race may be considered its own
* direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)}
* implementation which traverses a race's predecessors recursively.
*/
@Test
public void testCyclicPreviousRacesSequenceUsingSingleRace() {
createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true);
final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null);
final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE);
final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace);
r1.setTrackedRace(fleet, tr1);
r2.setTrackedRace(fleet, tr1);
assertTrue(tr1.takesWindFix(new WindImpl(new DegreePosition(12, 13),
startOfFirstRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)),
new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185)))));
}
private DynamicTrackedRace createTrackedRace(final String name, final TimePoint startOfRace, final TimePoint endOfRace) {
DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections.<Competitor> emptyList(), startOfRace);
trackedRace.setStartOfTrackingReceived(startOfRace);
trackedRace.setEndOfTrackingReceived(endOfRace);
return trackedRace;
}
}
@@ -3,7 +3,9 @@ package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.HashSet;
import java.util.Random;
@@ -11,12 +13,15 @@ import java.util.Set;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.CacheUpdater;
@@ -24,6 +29,8 @@ import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
import com.sap.sse.util.SmartFutureCache.UpdateInterval;
public class SmartFutureCacheTest {
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000);
@Test
public void testPerformanceOfGetAndCall() {
SmartFutureCache<String, String, EmptyUpdateInterval> sfc = new SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval>(
@@ -42,6 +49,34 @@ public class SmartFutureCacheTest {
}
System.out.println("testPerformanceOfGetAndCall took "+(System.currentTimeMillis()-start)+"ms");
}
@Test
public void testExceptionInComputeCacheUpdate() {
final boolean[] throwException = new boolean[1];
throwException[0] = true;
SmartFutureCache<String, String, EmptyUpdateInterval> sfc = new SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval>(
new SmartFutureCache.AbstractCacheUpdater<String, String, SmartFutureCache.EmptyUpdateInterval>() {
@Override
public String computeCacheUpdate(String key, EmptyUpdateInterval updateInterval) {
if (throwException[0]) {
throw new NullPointerException("Humba");
} else {
return "Humba";
}
}
}, "SmartFutureCacheTest.testExceptionInComputeCacheUpdate");
sfc.triggerUpdate("humba", /* update interval */ null);
try {
// during the first call, expecting exception
sfc.get("humba", /* waitForLatest */ true);
fail("Expected RuntimeException because computeCacheUpdate threw one");
} catch (RuntimeException expected) {
assertSame(ExecutionException.class, expected.getCause().getClass());
}
throwException[0] = false;
sfc.triggerUpdate("humba", /* update interval */ null);
assertEquals("Humba", sfc.get("humba", /* waitForLatest */ true));
}
@Test
public void testSuspendAndResume() {
@@ -34,7 +34,7 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class StarbordSideOfStartLineRecognitionTest {
private MillisecondsTimePoint now;
private TimePoint now;
@Before
public void setUp() {
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -56,7 +57,7 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace {
@Test
public void testStarboardTack() throws NoWindException {
DynamicGPSFixTrack<Competitor, GPSFixMoving> hassosTrack = getTrackedRace().getTrack(competitor);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
hassosTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), now,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(45))));
assertEquals(Tack.PORT, getTrackedRace().getTack(competitor, now));
@@ -65,7 +66,7 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace {
@Test
public void testPortTack() throws NoWindException {
DynamicGPSFixTrack<Competitor, GPSFixMoving> hassosTrack = getTrackedRace().getTrack(competitor);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
hassosTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), now,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(270))));
assertEquals(Tack.STARBOARD, getTrackedRace().getTack(competitor, now));
@@ -74,7 +75,7 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace {
@Test
public void testStarboardTackForZeroDifference() throws NoWindException {
DynamicGPSFixTrack<Competitor, GPSFixMoving> hassosTrack = getTrackedRace().getTrack(competitor);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
hassosTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), now,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(180))));
assertEquals(Tack.PORT, getTrackedRace().getTack(competitor, now));
@@ -0,0 +1,81 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.junit.Test;
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
import com.sap.sailing.domain.tractracadapter.impl.TracTracRaceTrackerImpl;
/**
* See bug 3174: the addition of the "random" element in the params_url around summer 2015 has killed our
* comparison of {@link TracTracRaceTracker#getID()} objects. Although two URLs differ in this "random" parameter,
* they still refer to the same race.<p>
*
* This test asserts that two TracTrac race tracker IDs differing only in the "random" URL parameter are still considered
* equal.
*
* @author Axel Uhl (d043530)
*
*/
public class TracTracIgnoreRandomInParamsURLTest {
@Test
public void testEqualRaceTrackerIDsThatOnlyDifferInRandomParam() throws MalformedURLException, URISyntaxException {
Object id1 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=769909699"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
Object id2 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=1794448899"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
assertEquals(id1, id2);
}
@Test
public void testEqualRaceTrackerIDsThatOnlyDifferInRandomParamAndNotLastParameter() throws MalformedURLException, URISyntaxException {
Object id1 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=769909699&a=b"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
Object id2 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=1794448899&a=b"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
assertEquals(id1, id2);
}
@Test
public void testEqualRaceTrackerIDsThatOnlyDifferInRandomParamWithFragment() throws MalformedURLException, URISyntaxException {
Object id1 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=769909699&a=b#HumbaHumba"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
Object id2 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=1794448899&a=b#HumbaHumba"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
assertEquals(id1, id2);
}
@Test
public void testEqualRaceTrackerIDsThatOnlyDifferInRandomParamAndFragment() throws MalformedURLException, URISyntaxException {
Object id1 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=769909699&a=b#HumbaTrala"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
Object id2 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=1794448899&a=b#HumbaHumba"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
assertFalse(id1.equals(id2));
}
@Test
public void testEqualRaceTrackerIDsThatDifferInRaceIDAndRandomParam() throws MalformedURLException, URISyntaxException {
Object id1 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0132-0cec-60a44ce903c3&random=769909699"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
Object id2 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0133-0cec-60a44ce903c3&random=1794448899"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
assertFalse(id1.equals(id2));
}
@Test
public void testEqualRaceTrackerIDsThatDifferInLiveURI() throws MalformedURLException, URISyntaxException {
Object id1 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0132-0cec-60a44ce903c3&random=769909699"),
new URI("tcp://event.tractrac.com:4400"), new URI("tcp://event.tractrac.com:4401"));
Object id2 = TracTracRaceTrackerImpl.createID(new URL("http://event.tractrac.com/events/event_20150707_erEuropean/clientparams.php?event=event_20150707_erEuropean&race=777b1420-07c1-0132-0cec-60a44ce903c3&random=769909699"),
new URI("tcp://event.tractrac.com:4412"), new URI("tcp://event.tractrac.com:4413"));
assertFalse(id1.equals(id2));
}
}
@@ -758,7 +758,7 @@ public class TrackTest {
Bearing bearing = new DegreeBearingImpl(123);
Speed speed = new KnotSpeedImpl(7);
Position p = new DegreePosition(0, 0);
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
TimePoint start = now;
final int steps = 10;
TimePoint next = null;
@@ -858,7 +858,7 @@ public class TrackTest {
Bearing bearing = new DegreeBearingImpl(123);
Speed speed = new KnotSpeedImpl(7);
Position p = new DegreePosition(0, 0);
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
TimePoint start = now;
final int steps = 10;
TimePoint next = null;
@@ -916,7 +916,7 @@ public class TrackTest {
Bearing bearing = new DegreeBearingImpl(123);
Speed speed = new KnotSpeedImpl(7);
Position p = new DegreePosition(0, 0);
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
TimePoint start = now;
final int steps = 10;
TimePoint next = null;
@@ -67,7 +67,7 @@ public class TrackedRaceCenterTest {
@Test
public void testSimpleAverage() {
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
Position mark1Pos = new DegreePosition(10, 10);
mark1Track.add(new GPSFixImpl(mark1Pos, now));
Position mark2Pos = new DegreePosition(20, 10);
@@ -85,7 +85,7 @@ public class TrackedRaceCenterTest {
DynamicGPSFixTrackImpl<Mark> mark3Track = new DynamicGPSFixTrackImpl<Mark>(mark3, /* millisecondsOverWhichToAverage */ 10);
when(trackedRace.getOrCreateTrack(mark3)).thenReturn(mark3Track);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
Position mark1Pos = new DegreePosition(0, 0);
mark1Track.add(new GPSFixImpl(mark1Pos, now));
Position mark2Pos = new DegreePosition(0, 10);
@@ -100,7 +100,7 @@ public class WindEstimationLockingUnderLoadTest {
} else {
double directionInDegrees = 360*Math.random();
double confidence = Math.random();
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
result = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(49, 8), now, new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(directionInDegrees))),
confidence, now, /* useSpeed */ true);
}
@@ -137,7 +137,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testCombinedWindTrack() throws NoWindException {
initRace(4, new int[] { 1, 1, 2, 2 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 315);
setBearingForCompetitor(competitors.get(1), now, 45); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(2), now, 135);
@@ -253,7 +253,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testWindEstimationForSimpleTracks() throws NoWindException {
initRace(2, new int[] { 1, 1 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 50);
Wind estimatedWindDirection = getTrackedRace().getEstimatedWindDirection(now);
@@ -263,7 +263,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testWindEstimationForSimpleTracksWithOneAncientFixToBeSuppressedByLowConfidence() throws NoWindException {
initRace(3, new int[] { 1, 1, 1 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 50);
setBearingForCompetitor(competitors.get(2), new MillisecondsTimePoint(0), 100); // this shouldn't disturb the estimation because it's too old
@@ -275,7 +275,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
public void testWindEstimationForSimpleTracksWithOneFixNearMarkPassingToBeSuppressedByLowConfidence() throws NoWindException {
TimePoint markPassingTimePoint = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime());
initRace(3, new int[] { 1, 1, 1 }, markPassingTimePoint);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 50);
setBearingForCompetitor(competitors.get(2), markPassingTimePoint, 100); // this shouldn't disturb the estimation because it's too old
@@ -286,7 +286,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testWindEstimationForTwoBoatsOnSameTack() throws NoWindException {
initRace(2, new int[] { 1, 1 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 330); // on the same tack, should give no read-out
Wind nullWind = getTrackedRace().getEstimatedWindDirection(now);
@@ -298,7 +298,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testWindEstimationForFourBoatsOnSameTack() throws NoWindException {
initRace(4, new int[] { 1, 1, 2, 2 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 330); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(2), now, 135);
@@ -312,7 +312,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testWindEstimationForFourBoatsWithUpwindOnSameTack() throws NoWindException {
initRace(4, new int[] { 1, 1, 2, 2 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 330); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(2), now, 135);
@@ -324,7 +324,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
@Test
public void testWindEstimationForFourBoats() throws NoWindException {
initRace(4, new int[] { 1, 1, 2, 2 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 315);
setBearingForCompetitor(competitors.get(1), now, 45); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(2), now, 135);
@@ -1,15 +1,6 @@
package com.sap.sailing.domain.tractracadapter;
import java.net.URI;
import java.net.URL;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sse.common.Util;
public interface TracTracRaceTracker extends RaceTracker {
/**
* returns the paramURL, liveURI and storedURI for the TracTrac connection maintained by this tracker
*/
Util.Triple<URL, URI, URI> getID();
}
@@ -16,6 +16,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -122,7 +123,7 @@ public class DomainFactoryImpl implements DomainFactory {
* Maps from the TracTrac race UUIDs to the domain model's {@link RaceDefinition} objects that represent the race
* identified by that UUID
*/
private final Map<UUID, RaceDefinition> raceCache = new HashMap<>();
private final ConcurrentHashMap<UUID, RaceDefinition> raceCache = new ConcurrentHashMap<>();
private final MetadataParser metadataParser;
@@ -328,11 +329,11 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public RaceDefinition getAndWaitForRaceDefinition(UUID raceId, long timeoutInMilliseconds) {
long start = System.currentTimeMillis();
synchronized (raceCache) {
RaceDefinition result = raceCache.get(raceId);
boolean interrupted = false;
while ((timeoutInMilliseconds == -1 || System.currentTimeMillis()-start < timeoutInMilliseconds) && !interrupted && result == null) {
try {
RaceDefinition result = raceCache.get(raceId);
boolean interrupted = false;
while ((timeoutInMilliseconds == -1 || System.currentTimeMillis()-start < timeoutInMilliseconds) && !interrupted && result == null) {
try {
synchronized (raceCache) {
if (timeoutInMilliseconds == -1) {
raceCache.wait();
} else {
@@ -341,13 +342,13 @@ public class DomainFactoryImpl implements DomainFactory {
raceCache.wait(timeToWait);
}
}
result = raceCache.get(raceId);
} catch (InterruptedException e) {
interrupted = true;
}
result = raceCache.get(raceId);
} catch (InterruptedException e) {
interrupted = true;
}
return result;
}
return result;
}
@Override
@@ -84,7 +84,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
}
@Override
public com.sap.sse.common.Util.Triple<URL, URI, URI> getTrackerID() {
public Object getTrackerID() {
return TracTracRaceTrackerImpl.createID(paramURL, liveURI, storedURI);
}
@@ -12,7 +12,9 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -194,12 +196,12 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
private final Set<RaceDefinition> races;
private final DynamicTrackedRegatta trackedRegatta;
private TrackedRaceStatus lastStatus;
private HashMap<Util.Triple<URL, URI, URI>, Util.Pair<Integer, Float>> lastProgressPerID;
private Map<Object, Util.Pair<Integer, Float>> lastProgressPerID;
/**
* paramURL, liveURI and storedURI for TracTrac connection
* paramURL, liveURI and storedURI for TracTrac connection, voided of "random" part
*/
private final Util.Triple<URL, URI, URI> urls;
private final Object id;
/**
* Tells if this tracker was created with a valid live URI. If not, the tracker will stop and unregister itself
@@ -315,12 +317,12 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
this.trackedRegattaRegistry = trackedRegattaRegistry;
this.tractracRace = tractracRace;
this.tractracEvent = tractracRace.getEvent();
urls = createID(paramURL, liveURI, storedURI);
this.id = createID(paramURL, liveURI, storedURI);
isLiveTracking = liveURI != null;
this.races = new HashSet<RaceDefinition>();
this.gpsFixStore = gpsFixStore;
this.domainFactory = domainFactory;
this.lastProgressPerID = new HashMap<Util.Triple<URL, URI, URI>, Util.Pair<Integer, Float>>();
this.lastProgressPerID = new HashMap<>();
if (simulateWithStartTimeNow) {
simulator = new Simulator(windStore);
// don't write the transformed wind fixes into the DB again... see also bug 1974
@@ -461,13 +463,48 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
return trackedRegatta;
}
static Util.Triple<URL, URI, URI> createID(URL paramURL, URI liveURI, URI storedURI) {
return new Util.Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
public static Object createID(URL paramURL, URI liveURI, URI storedURI) {
URL paramURLStrippedOfRandomParam;
if (paramURL == null) {
paramURLStrippedOfRandomParam = null;
} else {
final String query = paramURL.getQuery();
if (query == null) {
paramURLStrippedOfRandomParam = paramURL;
} else {
final StringJoiner stringJoiner = new StringJoiner("&", "?", "");
stringJoiner.setEmptyValue("");
String[] queryParams = query.split("&");
for (String queryParam : queryParams) {
String[] nameValue = queryParam.split("=");
if (!"random".equalsIgnoreCase(nameValue[0])) {
final StringBuilder param = new StringBuilder();
param.append(nameValue[0]);
if (nameValue.length > 1) {
param.append('=');
param.append(nameValue[1]);
}
stringJoiner.add(param.toString());
}
}
try {
paramURLStrippedOfRandomParam = new URL(paramURL.getProtocol(), paramURL.getHost(), paramURL.getPort(),
paramURL.getPath()+stringJoiner.toString()+(paramURL.getRef() == null || paramURL.getRef().isEmpty() ?
"" : ("#"+paramURL.getRef())));
} catch (MalformedURLException e) {
// this is pretty strange as we only removed one parameter; log and continue with original URL as a default
logger.log(Level.SEVERE, "Error trying to strip the \"random\" parameter from the TracTrac params_url "+
paramURL, e);
paramURLStrippedOfRandomParam = paramURL;
}
}
}
return new Util.Triple<URL, URI, URI>(paramURLStrippedOfRandomParam, liveURI, storedURI);
}
@Override
public Util.Triple<URL, URI, URI> getID() {
return urls;
public Object getID() {
return id;
}
@Override
@@ -6,11 +6,14 @@ import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
@@ -77,7 +80,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
private static final Logger logger = Logger.getLogger(RegattaImpl.class.getName());
private static final long serialVersionUID = 6509564189552478869L;
private Set<RaceDefinition> races;
private ConcurrentHashMap<String, RaceDefinition> races;
private final BoatClass boatClass;
private transient Set<RegattaListener> regattaListeners;
private List<? extends Series> series;
@@ -178,7 +181,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
this.useStartTimeInference = useStartTimeInference;
this.id = id;
this.raceLogStore = raceLogStore;
races = new HashSet<RaceDefinition>();
races = new ConcurrentHashMap<>();
regattaListeners = new HashSet<RegattaListener>();
raceColumnListeners = new RaceColumnListeners();
this.boatClass = boatClass;
@@ -256,7 +259,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
MasterDataImportInformation masterDataImportInformation = ongoingMasterDataImportInformation.get();
if (masterDataImportInformation != null) {
raceLogStore = masterDataImportInformation.getRaceLogStore();
races = new HashSet<RaceDefinition>();
races = new ConcurrentHashMap<>();
} else {
raceLogStore = EmptyRaceLogStore.INSTANCE;
}
@@ -309,9 +312,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
@Override
public Iterable<RaceDefinition> getAllRaces() {
synchronized (races) {
return new ArrayList<RaceDefinition>(races);
}
return races.values();
}
@Override
@@ -326,12 +327,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
@Override
public RaceDefinition getRaceByName(String raceName) {
for (RaceDefinition r : getAllRaces()) {
if (r.getName().equals(raceName)) {
return r;
}
}
return null;
return races.get(raceName);
}
@Override
@@ -340,9 +336,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
if (getBoatClass() != null && race.getBoatClass() != getBoatClass()) {
throw new IllegalArgumentException("Boat class "+race.getBoatClass()+" doesn't match regatta's boat class "+getBoatClass());
}
synchronized (races) {
races.add(race);
}
races.put(race.getName(), race);
synchronized (regattaListeners) {
for (RegattaListener l : regattaListeners) {
l.raceAdded(this, race);
@@ -352,10 +346,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
@Override
public void removeRace(RaceDefinition race) {
synchronized (races) {
logger.info("Removing race "+race.getName()+" from regatta "+getName()+" ("+hashCode()+")");
races.remove(race);
}
logger.info("Removing race "+race.getName()+" from regatta "+getName()+" ("+hashCode()+")");
races.remove(race.getName());
synchronized (regattaListeners) {
for (RegattaListener l : regattaListeners) {
l.raceRemoved(this, race);
@@ -678,15 +670,27 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
final Iterable<? extends Series> mySeries = getSeries();
if (mySeries != null) {
for (Series currentSeries : mySeries) {
if (currentSeries.getFleets() != null) {
for (Fleet fleet : currentSeries.getFleets()) {
if (currentSeries.getRaceColumns() != null) {
result.put(fleet, currentSeries.getRaceColumns());
boolean concurrentlyModified = false;
do {
try {
for (Series currentSeries : mySeries) {
if (currentSeries.getFleets() != null) {
for (Fleet fleet : currentSeries.getFleets()) {
if (currentSeries.getRaceColumns() != null) {
result.put(fleet, currentSeries.getRaceColumns());
}
}
}
}
} catch (ConcurrentModificationException e) {
// getSeries() returns a live collection, and Series.getRaceColumns() does so, too.
// In the unlikely event of a modification is applied to either of these structures while iterating, an exception
// will be thrown. We catch and log it here and try again.
logger.log(Level.INFO,
"Got a ConcurrentModificationException while trying to update the RaceExecutionOrderCache", e);
concurrentlyModified = true;
}
}
} while (concurrentlyModified);
}
return result;
}
@@ -74,7 +74,7 @@ public class LeaderboardDTOCache implements LeaderboardCache {
private static final Executor computeLeadearboardByNameExecutor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1));
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
private final LeaderboardCacheManager leaderboardCacheManager;
@@ -279,7 +279,7 @@ public class LiveLeaderboardUpdater implements Runnable {
+ leaderboard.getName());
// interrupt the current thread if not producing a single result within the overall timeout
while (true) {
MillisecondsTimePoint now = MillisecondsTimePoint.now();
TimePoint now = MillisecondsTimePoint.now();
final Long delayToLiveInMillis = getLeaderboard().getDelayToLiveInMillis();
TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis);
synchronized (this) {
@@ -341,7 +341,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1));
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
}
@Override
@@ -1148,7 +1148,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public TimePoint getNowMinusDelay() {
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TimePoint now = MillisecondsTimePoint.now();
final Long delayToLiveInMillis = getDelayToLiveInMillis();
TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis);
return timePoint;
@@ -145,7 +145,7 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
FlexibleRaceColumn column = getRaceColumnByName(name);
if (column != null) {
final String msg = "Trying to create race column with duplicate name "+name+" in leaderboard +"+getName();
final String msg = "Trying to create race column with duplicate name "+name+" in leaderboard "+getName();
logger.severe(msg);
} else {
column = createRaceColumn(name, medalRace);
@@ -54,7 +54,7 @@ public class MarkPassingCalculator {
.getRuntime().availableProcessors() - 1, 3),
/* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors() - 1, 3),
/* keepAliveTime */60, TimeUnit.SECONDS,
/* workQueue */new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1));
/* workQueue */new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1, /* daemon */ true));
private boolean suspended = false;
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.confidence.Weigher;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.tracking.GPSFix;
@@ -151,4 +152,19 @@ public interface GPSFixTrack<ItemType, FixType extends GPSFix> extends Track<Fix
*/
Iterator<Position> getEstimatedPositions(Iterable<Timed> timeds, boolean extrapolate);
/**
* When a {@link TrackedRace} moves into state {@link TrackedRaceStatusEnum#LOADING}, it shall call
* this method on all its tracks to allow them to skip validity cache updates which, when done at massive
* scale, are too expensive because they keep invalidating neighbors' validity and need some time to
* find those neighbors. When leading state LOADING, {@link #resumeValidityCaching()} must be called.
*/
void suspendValidityCaching();
/**
* When a {@link TrackedRace} moves out of state {@link TrackedRaceStatusEnum#LOADING}, it shall call
* this method on all its tracks to allow them to invalidate all validity caching so far in order to
* have everything re-calculated when needed.
*/
void resumeValidityCaching();
}
@@ -302,6 +302,10 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
return true;
}
default boolean takesWindFixRecursively(Wind wind, Set<TrackedRace> visited) {
return true;
}
/**
* Same as {@link #getWind(Position, TimePoint, Set) getWind(p, at, Collections.emptyList())}
*/
@@ -859,4 +863,5 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
default RaceLogResolver getRaceLogResolver() {
return null;
}
}
@@ -474,10 +474,13 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
}
public void suspend() {
owner.removeListener(this);
cachePerCompetitor.suspend();
}
public void resume() {
owner.addListener(this);
invalidate();
cachePerCompetitor.resume();
}
}
@@ -560,11 +560,6 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
}
}
@Override
public Iterable<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
return (NavigableSet<MarkPassing>) super.getMarkPassingsInOrder(waypoint);
}
@Override
public void lockForRead(Iterable<MarkPassing> markPassings) {
getRace().getCourse().lockForRead();
@@ -26,6 +26,7 @@ import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster;
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
@@ -40,6 +41,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.WithValidityCache;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Timed;
@@ -58,7 +60,12 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
private final GPSTrackListeners<ItemType, FixType> listeners;
/**
* When the owning {@link TrackedRace} is still {@link TrackedRaceStatusEnum#LOADING loading}, validity cache updates are
* suspended.
*/
private boolean validityCachingSuspended;
private static class GPSTrackListeners<I, F extends GPSFix> implements Serializable {
private static final long serialVersionUID = -7117842092078781722L;
private Set<GPSTrackListener<I, F>> listeners;
@@ -186,6 +193,25 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
this.listeners = new GPSTrackListeners<ItemType, FixType>();
this.distanceCache = new DistanceCache(trackedItem==null?"null":trackedItem.toString());
this.maxSpeedCache = createMaxSpeedCache();
this.validityCachingSuspended = false;
}
@Override
public void suspendValidityCaching() {
validityCachingSuspended = true;
}
@Override
public void resumeValidityCaching() {
lockForWrite();
try {
this.validityCachingSuspended = false;
for (FixType fix : getRawFixes()) {
fix.invalidateCache();
}
} finally {
unlockAfterWrite();
}
}
protected MaxSpeedCache<ItemType, FixType> createMaxSpeedCache() {
@@ -958,7 +984,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
* of the <code>gpsFix</code> "upwards." However, if the adjacent earlier fixes have changed their validity by the addition
* of <code>gpsFix</code>, the distance cache must be invalidated starting with the first fix whose validity changed.
*/
protected void invalidateValidityAndEstimatedSpeedAndDistanceCaches(FixType gpsFix) {
private void invalidateValidityAndEstimatedSpeedAndDistanceCaches(FixType gpsFix) {
assertWriteLock();
TimePoint distanceCacheInvalidationStart = gpsFix.getTimePoint();
// see also bug 968: cache entries for intervals ending after the last fix need to be removed because they are
@@ -1058,7 +1084,9 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
try {
firstFixInTrack = getRawFixes().isEmpty();
result = addWithoutLocking(fix);
invalidateValidityAndEstimatedSpeedAndDistanceCaches(fix);
if (!validityCachingSuspended) {
invalidateValidityAndEstimatedSpeedAndDistanceCaches(fix);
}
} finally {
unlockAfterWrite();
}
@@ -105,7 +105,7 @@ public class ShortTimeWindCache {
*/
private void ensureTimerIsRunning() {
if (timer == null) {
timer = new Timer(getClass().getSimpleName()+" for "+trackedRace.getRace().getName());
timer = new Timer(getClass().getSimpleName()+" for "+trackedRace.getRace().getName(), /* isDaemon */ true);
timer.scheduleAtFixedRate(new CacheInvalidator(), /* delay */ preserveHowManyMilliseconds, preserveHowManyMilliseconds);
}
}
@@ -590,6 +590,17 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
*/
private class CacheInvalidationRaceChangeListener extends AbstractRaceChangeListener implements Serializable {
private static final long serialVersionUID = -6623310087193133466L;
private boolean suspended;
public CacheInvalidationRaceChangeListener() {
if (getTrackedRace().getStatus().getStatus() == TrackedRaceStatusEnum.LOADING) {
suspended = true;
clearCache();
} else {
suspended = false;
}
}
@Override
protected void defaultAction() {
@@ -598,7 +609,9 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
@Override
public void windDataReceived(Wind wind, WindSource windSource) {
invalidateForNewWind(wind, windSource);
if (!suspended) {
invalidateForNewWind(wind, windSource);
}
}
@Override
@@ -654,48 +667,61 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
@Override
public void windDataRemoved(Wind wind, WindSource windSource) {
invalidateForNewWind(wind, windSource);
if (!suspended) {
invalidateForNewWind(wind, windSource);
}
}
@Override
public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
long averagingInterval = getTrackedRace().getMillisecondsOverWhichToAverageSpeed();
WindWithConfidence<TimePoint> startOfInvalidation = getDummyFixWithConfidence(new MillisecondsTimePoint(fix
.getTimePoint().asMillis() - averagingInterval));
TimePoint endOfInvalidation = new MillisecondsTimePoint(fix.getTimePoint().asMillis() + averagingInterval);
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
if (!suspended) {
long averagingInterval = getTrackedRace().getMillisecondsOverWhichToAverageSpeed();
WindWithConfidence<TimePoint> startOfInvalidation = getDummyFixWithConfidence(new MillisecondsTimePoint(fix
.getTimePoint().asMillis() - averagingInterval));
TimePoint endOfInvalidation = new MillisecondsTimePoint(fix.getTimePoint().asMillis() + averagingInterval);
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
}
}
@Override
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING) {
if (newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) {
suspended = false;
}
} else if (newStatus.getStatus() == TrackedRaceStatusEnum.LOADING) {
suspended = true;
clearCache();
}
// This virtual wind track's cache can cope with an empty cache after the LOADING phase and populates the
// cache
// upon request. Invalidation happens also during the LOADING phase, preserving the cache's invariant.
// cache upon request. Invalidation during the LOADING phase happens by clearing the entire cache.
}
@Override
public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
Iterable<MarkPassing> markPassings) {
long averagingInterval = getTrackedRace().getMillisecondsOverWhichToAverageSpeed();
WindWithConfidence<TimePoint> startOfInvalidation;
TimePoint endOfInvalidation;
for (MarkPassing markPassing : markPassings) {
MarkPassing oldMarkPassing = oldMarkPassings.get(markPassing.getWaypoint());
if (oldMarkPassing != markPassing) {
if (oldMarkPassing == null) {
startOfInvalidation = getDummyFixWithConfidence(new MillisecondsTimePoint(markPassing
.getTimePoint().asMillis() - averagingInterval));
endOfInvalidation = new MillisecondsTimePoint(markPassing.getTimePoint().asMillis()
+ averagingInterval);
} else {
TimePoint[] interval = new TimePoint[] { oldMarkPassing.getTimePoint(),
markPassing.getTimePoint() };
Arrays.sort(interval);
startOfInvalidation = getDummyFixWithConfidence(new MillisecondsTimePoint(
interval[0].asMillis() - averagingInterval));
endOfInvalidation = new MillisecondsTimePoint(interval[1].asMillis() + averagingInterval);
if (!suspended) {
long averagingInterval = getTrackedRace().getMillisecondsOverWhichToAverageSpeed();
WindWithConfidence<TimePoint> startOfInvalidation;
TimePoint endOfInvalidation;
for (MarkPassing markPassing : markPassings) {
MarkPassing oldMarkPassing = oldMarkPassings.get(markPassing.getWaypoint());
if (oldMarkPassing != markPassing) {
if (oldMarkPassing == null) {
startOfInvalidation = getDummyFixWithConfidence(new MillisecondsTimePoint(markPassing
.getTimePoint().asMillis() - averagingInterval));
endOfInvalidation = new MillisecondsTimePoint(markPassing.getTimePoint().asMillis()
+ averagingInterval);
} else {
TimePoint[] interval = new TimePoint[] { oldMarkPassing.getTimePoint(),
markPassing.getTimePoint() };
Arrays.sort(interval);
startOfInvalidation = getDummyFixWithConfidence(new MillisecondsTimePoint(
interval[0].asMillis() - averagingInterval));
endOfInvalidation = new MillisecondsTimePoint(interval[1].asMillis() + averagingInterval);
}
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
}
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
}
}
}
@@ -703,13 +729,15 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
@Override
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
assert fix != null && fix.getTimePoint() != null;
// A mark position change can mean a leg type change. The interval over which the wind estimation is
// affected
// depends on how the GPS track computes the estimated mark position. Ask it:
TimeRange interval = getTrackedRace().getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix);
WindWithConfidence<TimePoint> startOfInvalidation = getDummyFixWithConfidence(interval.from());
TimePoint endOfInvalidation = interval.to();
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
if (!suspended) {
// A mark position change can mean a leg type change. The interval over which the wind estimation is
// affected
// depends on how the GPS track computes the estimated mark position. Ask it:
TimeRange interval = getTrackedRace().getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix);
WindWithConfidence<TimePoint> startOfInvalidation = getDummyFixWithConfidence(interval.from());
TimePoint endOfInvalidation = interval.to();
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
}
}
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
@@ -17,6 +18,7 @@ import java.util.logging.Logger;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.base.SpeedWithConfidence;
@@ -77,6 +79,16 @@ public class TrackedLegImpl implements TrackedLeg {
competitorTracksOrderedByRank = new ConcurrentHashMap<>();
}
private void writeObject(ObjectOutputStream oos) throws IOException {
final Course course = trackedRace.getRace().getCourse();
course.lockForRead();
try {
oos.defaultWriteObject();
} finally {
course.unlockAfterRead();
}
}
@Override
public Leg getLeg() {
return leg;
@@ -273,6 +273,17 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
private transient Timer cacheInvalidationTimer;
private transient Object cacheInvalidationTimerLock;
/**
* handled by {@link #suspendAllCachesNotUpdatingWhileLoading()} and {@link #resumeAllCachesNotUpdatingWhileLoading()}.
*/
private boolean cachesSuspended;
/**
* Whether during {@link #cachesSuspended suspended caches mode} the maneuver re-calculation was triggered; will lead
* to triggering the maneuver re-calculation when caches are {@link #resumeAllCachesNotUpdatingWhileLoading() resumed}.
*/
private boolean triggerManeuverCacheInvalidationForAllCompetitors;
/**
* Keys are the {@link RaceLog#getId() IDs} of the race logs that are stored as values.
@@ -605,11 +616,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
crossTrackErrorCache = new CrossTrackErrorCache(this);
crossTrackErrorCache.invalidate();
maneuverCache = createManeuverCache();
triggerManeuverCacheRecalculationForAllCompetitors();
logger.info("Deserialized race " + getRace().getName());
// considering the unlikely possibility that the course and this tracked race's internal structures
// may be inconsistent, e.g., due to non-atomic serialization of course and tracked race; see bug 2223
adjustStructureToCourse();
triggerManeuverCacheRecalculationForAllCompetitors();
logger.info("Deserialized race " + getRace().getName());
}
/**
@@ -625,6 +636,10 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
private void adjustStructureToCourse() throws PatchFailedException {
final TrackedRaceAsWaypointList trackedRaceAsWaypointList = new TrackedRaceAsWaypointList(this);
Patch<Waypoint> diff = DiffUtils.diff(trackedRaceAsWaypointList, getRace().getCourse().getWaypoints());
if (!diff.isEmpty()) {
logger.warning("Found inconsistency between race's course ("+getRace().getCourse()+
") and TrackedRace's structures in "+this+"; fixing");
}
diff.applyToInPlace(trackedRaceAsWaypointList);
}
@@ -684,7 +699,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
@Override
public Iterable<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
public NavigableSet<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
return getMarkPassingsInOrderAsNavigableSet(waypoint);
}
@@ -798,12 +813,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
TimePoint passingTime = null;
final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint();
if (lastWaypoint != null) {
Iterable<MarkPassing> markPassingsInOrder = getMarkPassingsInOrder(lastWaypoint);
NavigableSet<MarkPassing> markPassingsInOrder = getMarkPassingsInOrder(lastWaypoint);
if (markPassingsInOrder != null) {
lockForRead(markPassingsInOrder);
try {
for (MarkPassing passingFinishLine : markPassingsInOrder) {
passingTime = passingFinishLine.getTimePoint();
final MarkPassing last = markPassingsInOrder.isEmpty() ? null : markPassingsInOrder.last();
if (last != null) {
passingTime = last.getTimePoint();
}
} finally {
unlockAfterRead(markPassingsInOrder);
@@ -1525,6 +1541,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
@Override
public boolean takesWindFix(Wind wind) {
final Set<TrackedRace> visited = new HashSet<>();
visited.add(this);
return takesWindFixRecursively(wind, visited);
}
/**
* @param visited
* used to avoid endless recursion if cyclic predecessor relations are delivered by a
* {@link RaceExecutionOrderProvider}
*/
@Override
public boolean takesWindFixRecursively(Wind wind, Set<TrackedRace> visited) {
final boolean result;
final TimePoint earliestStartTimePoint = Util.getEarliestOfTimePoints(getStartOfRace(), getStartOfTracking());
final TimePoint latestEndTimePoint = Util.getLatestOfTimePoints(getEndOfRace(), getEndOfTracking());
@@ -1546,7 +1574,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// the fix is in the critical interval between EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS and
// TIME_BEFORE_START_TO_TRACK_WIND_MILLIS before the earliestStartTimePoint; the fix shall only be accepted
// if no previous race exists that accepts it
result = noPreviousRaceTakesWind(wind);
result = noPreviousRaceTakesWind(wind, visited);
}
}
} else {
@@ -1558,10 +1586,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return result;
}
private boolean noPreviousRaceTakesWind(Wind wind) {
private boolean noPreviousRaceTakesWind(Wind wind, Set<TrackedRace> visited) {
final boolean result;
Set<TrackedRace> previousRacesInExecutionOrder = getPreviousRacesFromAttachedRaceExecutionOrderProviders();
if (previousRacesInExecutionOrder == null || !previousRacesInExecutionOrder.stream().filter(tr -> tr.takesWindFix(wind) == true).findAny().isPresent()) {
if (previousRacesInExecutionOrder == null || !previousRacesInExecutionOrder.stream().filter(tr ->
visited.add(tr) && tr.takesWindFixRecursively(wind, visited)).findAny().isPresent()) {
result = true;
} else {
result = false;
@@ -2332,18 +2361,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
protected void triggerManeuverCacheRecalculationForAllCompetitors() {
final List<Competitor> shuffledCompetitors = new ArrayList<>();
for (Competitor competitor : (getRace().getCompetitors())) {
shuffledCompetitors.add(competitor);
}
Collections.shuffle(shuffledCompetitors);
for (Competitor competitor : shuffledCompetitors) {
triggerManeuverCacheRecalculation(competitor);
if (cachesSuspended) {
triggerManeuverCacheInvalidationForAllCompetitors = true;
} else {
final List<Competitor> shuffledCompetitors = new ArrayList<>();
for (Competitor competitor : (getRace().getCompetitors())) {
shuffledCompetitors.add(competitor);
}
Collections.shuffle(shuffledCompetitors);
for (Competitor competitor : shuffledCompetitors) {
triggerManeuverCacheRecalculation(competitor);
}
}
}
protected void triggerManeuverCacheRecalculation(final Competitor competitor) {
maneuverCache.triggerUpdate(competitor, /* updateInterval */null);
if (cachesSuspended) {
triggerManeuverCacheInvalidationForAllCompetitors = true;
} else {
maneuverCache.triggerUpdate(competitor, /* updateInterval */null);
}
}
private com.sap.sse.common.Util.Triple<TimePoint, TimePoint, List<Maneuver>> computeManeuvers(Competitor competitor) throws NoWindException {
@@ -3018,6 +3055,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
private void suspendAllCachesNotUpdatingWhileLoading() {
cachesSuspended = true;
for (GPSFixTrack<Competitor, GPSFixMoving> competitorTrack : tracks.values()) {
competitorTrack.suspendValidityCaching();
}
for (GPSFixTrack<Mark, GPSFix> markTrack : markTracks.values()) {
markTrack.suspendValidityCaching();
}
if (markPassingCalculator != null) {
markPassingCalculator.suspend();
}
@@ -3026,10 +3070,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
private void resumeAllCachesNotUpdatingWhileLoading() {
cachesSuspended = false;
for (GPSFixTrack<Competitor, GPSFixMoving> competitorTrack : tracks.values()) {
competitorTrack.resumeValidityCaching();
}
for (GPSFixTrack<Mark, GPSFix> markTrack : markTracks.values()) {
markTrack.resumeValidityCaching();
}
if (markPassingCalculator != null) {
markPassingCalculator.resume();
}
crossTrackErrorCache.resume();
if (triggerManeuverCacheInvalidationForAllCompetitors) {
triggerManeuverCacheRecalculationForAllCompetitors();
}
maneuverCache.resume();
}
@@ -3365,10 +3419,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
final TrackedLeg legDeterminingDirection = getLegDeterminingDirectionInWhichToPassWaypoint(waypoint);
final Mark portMarkWhileApproachingLine = marksAndPositions.getPortMarkWhileApproachingLine();
final Mark starboardMarkWhileApproachingLine = marksAndPositions.getStarboardMarkWhileApproachingLine();
final Position portMarkPositionWhileApproachingLine = marksAndPositions
.getPortMarkPositionWhileApproachingLine();
final Position starboardMarkPositionWhileApproachingLine = marksAndPositions
.getStarboardMarkPositionWhileApproachingLine();
final Position portMarkPositionWhileApproachingLine = marksAndPositions.getPortMarkPositionWhileApproachingLine();
final Position starboardMarkPositionWhileApproachingLine = marksAndPositions.getStarboardMarkPositionWhileApproachingLine();
final Bearing differenceToCombinedWind;
final NauticalSide advantageousSideWhileApproachingLine;
final Distance distanceAdvantage;
@@ -3425,14 +3477,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
private LineMarksWithPositions getLineMarksAndPositions(TimePoint timePoint, Waypoint waypoint) {
final LineMarksWithPositions result;
List<Position> markPositions = new ArrayList<Position>();
List<Position> markPositions = new ArrayList<>();
int numberOfMarks = 0;
boolean allMarksHavePositions = true;
if (waypoint != null) {
for (Mark lineMark : waypoint.getMarks()) {
numberOfMarks++;
final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(timePoint, /* extrapolate */
false);
final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(timePoint, /* extrapolate */ false);
if (estimatedMarkPosition != null) {
markPositions.add(estimatedMarkPosition);
} else {
@@ -3482,12 +3533,10 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
private TrackedLeg getLegDeterminingDirectionInWhichToPassWaypoint(Waypoint waypoint) {
final TrackedLeg legDeterminingDirection;
{
final int indexOfWaypoint2 = getRace().getCourse().getIndexOfWaypoint(waypoint);
final boolean isStartLine2 = indexOfWaypoint2 == 0;
legDeterminingDirection = getTrackedLeg(getRace().getCourse().getLegs().get(isStartLine2 ? 0
: indexOfWaypoint2 - 1));
}
final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(waypoint);
final boolean isStartLine = indexOfWaypoint == 0;
legDeterminingDirection = getTrackedLeg(getRace().getCourse().getLegs().get(isStartLine ? 0
: indexOfWaypoint - 1));
return legDeterminingDirection;
}
@@ -110,4 +110,11 @@
version="0.0.0"
unpack="false"/>
<plugin
id="lz4-java"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>
-2
View File
@@ -28,9 +28,7 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
<import plugin="com.google.gwt.osgi" version="2.7.0" match="greaterOrEqual"/>
<import plugin="org.json.simple" version="1.1.0" match="greaterOrEqual"/>
<import plugin="org.moxieapps.gwt.highcharts" version="1.6.0" match="greaterOrEqual"/>
<import plugin="org.apache.geronimo.specs.geronimo-jms_1.1_spec"/>
<import plugin="com.rabbitmq.client"/>
<import plugin="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec"/>
</requires>
<plugin
@@ -17,7 +17,6 @@ Require-Bundle: com.sap.sailing.server,
com.sap.sse.filestorage
Bundle-ClassPath: .
Import-Package: com.sap.sailing.domain.racelogtracking.impl,
javax.jms;version="1.1.0",
javax.servlet;version="2.6.0",
javax.servlet.http;version="2.6.0",
junit.framework;version="4.8.1",
@@ -11,6 +11,6 @@
</listAttribute>
<stringAttribute key="projectName" value="com.sap.sailing.gwt.ui"/>
<booleanAttribute key="showLaunchOutput" value="false"/>
<stringAttribute key="url" value="http://127.0.0.1:8888/gwt/AdminConsole.html"/>
<stringAttribute key="url" value="http://127.0.0.1:8888/gwt/Home.html"/>
<stringAttribute key="urlQueryParams" value=""/>
</launchConfiguration>
@@ -51,8 +51,7 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.news
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Bundle-ActivationPolicy: lazy
Import-Package: javax.jms;version="1.1.0",
javax.servlet;version="3.1.0",
Import-Package: javax.servlet;version="3.1.0",
javax.servlet.descriptor;version="3.1.0",
javax.servlet.http;version="3.1.0",
org.apache.http.client;version="4.2.5",
@@ -308,7 +308,14 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
String url = "RegattaOverview.html?ignoreLocalSettings=true&onlyrunningraces=false&event=" + getCtx().getEventId();
url += "&onlyracesofsameday=" + getCtx().getEventDTO().isRunning();
if(showRegattaMetadata()) {
url += "&regatta=" + getCtx().getRegattaId();
if(getRegattaMetadata().isFlexibleLeaderboard()) {
String defaultCourseAreaId = getRegattaMetadata().getDefaultCourseAreaId();
if(defaultCourseAreaId != null && !defaultCourseAreaId.isEmpty()) {
url += "&coursearea=" + defaultCourseAreaId;
}
} else {
url += "&regatta=" + getCtx().getRegattaId();
}
}
return url;
}
@@ -319,9 +326,6 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
if(ctx.getRegatta().getState() == RegattaState.RUNNING) {
return true;
}
if(ctx.getRegatta().getState() != RegattaState.UNKNOWN) {
return false;
}
}
return ctx.getEventDTO().getState() == EventState.RUNNING;
}
@@ -157,8 +157,8 @@ public class MultiregattaOverviewTabView extends Composite implements Multiregat
private class MultiregattaOverviewRegattasTabViewRegattaFilterList implements
DropdownFilterList<String>, RefreshableWidget<SortedSetResult<RegattaWithProgressDTO>> {
@Override
public void setData(SortedSetResult<RegattaWithProgressDTO> data, long nextUpdate, int updateNo) {
regattaListUi.setData(data, nextUpdate, updateNo);
public void setData(SortedSetResult<RegattaWithProgressDTO> data) {
regattaListUi.setData(data);
boatCategoryFilterUi.updateFilterValues();
}
@Override
@@ -155,8 +155,8 @@ public class MultiregattaRegattasTabView extends Composite implements Multiregat
DropdownFilterList<String>, RefreshableWidget<SortedSetResult<RegattaWithProgressDTO>> {
@Override
public void setData(SortedSetResult<RegattaWithProgressDTO> data, long nextUpdate, int updateNo) {
regattaListUi.setData(data, nextUpdate, updateNo);
public void setData(SortedSetResult<RegattaWithProgressDTO> data) {
regattaListUi.setData(data);
boatCategoryFilterUi.updateFilterValues();
}
@@ -172,4 +172,4 @@ public class MultiregattaRegattasTabView extends Composite implements Multiregat
}
}
}
@@ -33,13 +33,13 @@ public class EventOverviewStage extends Composite {
private final RefreshableWidget<EventOverviewStageDTO> refreshable = new RefreshableWidget<EventOverviewStageDTO>() {
@Override
public void setData(EventOverviewStageDTO data, long nextUpdate, int updateNo) {
public void setData(EventOverviewStageDTO data) {
setStageData(data);
}
};
private final RefreshableWidget<ListResult<NewsEntryDTO>> newsRefreshable = new RefreshableWidget<ListResult<NewsEntryDTO>>() {
@Override
public void setData(ListResult<NewsEntryDTO> data, long nextUpdate, int updateNo) {
public void setData(ListResult<NewsEntryDTO> data) {
setNews(data.getValues());
}
};
@@ -32,7 +32,7 @@ public class MultiRegattaList extends Composite implements RefreshableWidget<Sor
}
@Override
public void setData(SortedSetResult<RegattaWithProgressDTO> data, long nextUpdate, int updateNo) {
public void setData(SortedSetResult<RegattaWithProgressDTO> data) {
this.setListData(data == null ? Collections.<RegattaWithProgressDTO>emptySet() : data.getValues());
}
@@ -21,15 +21,15 @@ public class MultiRegattaListItem extends Composite {
@UiField(provided = true) MultiRegattaListSteps regattaStepsUi;
private final RegattaWithProgressDTO regattaWithProgress;
public MultiRegattaListItem(RegattaWithProgressDTO regattaWithProgress) {
public MultiRegattaListItem(RegattaWithProgressDTO regattaWithProgress, boolean showStateMarker) {
this.regattaWithProgress = regattaWithProgress;
regattaHeaderUi = new RegattaHeader(regattaWithProgress);
regattaHeaderUi = new RegattaHeader(regattaWithProgress, showStateMarker);
regattaStepsUi = new MultiRegattaListSteps(regattaWithProgress.getProgress());
initWidget(uiBinder.createAndBindUi(this));
}
public MultiRegattaListItem(RegattaWithProgressDTO regattaWithProgress, Presenter presenter) {
this(regattaWithProgress);
this(regattaWithProgress, presenter.getCtx().getEventDTO().isRunning());
regattaHeaderUi.setRegattaNavigation(presenter.getRegattaNavigation(regattaWithProgress.getId()));
PlaceNavigation<?> leaderboardNavigation = presenter.getRegattaLeaderboardNavigation(regattaWithProgress.getId());
regattaStepsUi.setLeaderboardNavigation(regattaWithProgress.getState(), leaderboardNavigation);
@@ -72,20 +72,31 @@
min-width: 13.333333333333334em;
max-width: 16.666666666666668em;
}
.race_item_flag {
max-width: 1.6em;
max-height: 1.6em;
img.race_item_flag {
vertical-align: middle;
height: 1em;
width: auto;
}
.race_item_position {
margin-right: 0.333333333333333em;
}
.race_item_winner {
.race_item_sailid {
margin-left: 5px;
vertical-align: middle;
font-weight: bold;
font-size: 0.8em;
}
.race_item_winner {
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.race_itemwinner {
padding: 0.733333333333333em 1.333333333333333em;
padding: 0 1.333333333333333em;
}
.race_itemwinner > div {
vertical-align: middle;
}
.race_itemcenter {
text-align: center;
@@ -211,4 +222,13 @@
.racesListIcon > div {
vertical-align: middle;
display: inline-block;
}
.racesListHideColumn {
width: 0 !important;
padding: 0 !important;
margin: 0 !important;
display: none !important;
}
.racesListHideColumn > * {
display: none !important;
}
@@ -24,6 +24,7 @@ public interface RacesListLiveResources extends ClientBundle {
String race_itemwinner();
String race_item_flag();
String race_item_position();
String race_item_sailid();
String race_item_winner();
String race_itemcenter();
String race_itemright();
@@ -49,5 +50,6 @@ public interface RacesListLiveResources extends ClientBundle {
String raceslistlive();
String racesListIcon();
String racesListHideColumn();
}
}
@@ -1,6 +1,9 @@
package com.sap.sailing.gwt.home.client.place.event.partials.racelist;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortList;
@@ -33,33 +36,23 @@ public abstract class AbstractRaceList<T extends RaceMetadataDTO<? extends Abstr
protected final SortableRaceListColumn<T, ?> windDirectionColumn = RaceListColumnFactory.getWindDirectionColumn();
protected final SortableRaceListColumn<T, ?> raceViewerButtonColumn;
private SortedCellTable<T> cellTable;
private SortedCellTable<T> cellTable = new SortedCellTable<T>(0, CleanCellTableResources.INSTANCE);
private boolean tableColumnsInitialized = false;
protected AbstractRaceList(EventView.Presenter presenter) {
CSS.ensureInjected();
this.raceViewerButtonColumn = RaceListColumnFactory.getRaceViewerButtonColumn(presenter);
this.cellTableContainer.setWidget(this.cellTable);
this.initTableStyle();
this.initWidget(cellTableContainer);
}
@SuppressWarnings("unchecked")
protected void setTableData(Collection<T> data) {
ColumnSortList sortList = this.cellTable == null ? null : this.cellTable.getColumnSortList();
this.cellTable = new SortedCellTable<T>(data.size(), CleanCellTableResources.INSTANCE);
this.cellTableContainer.setWidget(this.cellTable);
this.initTableStyle();
this.initTableColumns();
this.ensureInitTableColumns();
this.updateColumnVisibility();
this.cellTable.setPageSize(data.size());
this.cellTable.setList(data);
if (sortList != null && sortList.size() > 0) {
for (int i = sortList.size() - 1; i >= 0; i--) {
ColumnSortInfo sortInfo = sortList.get(i);
Column<T, ?> column = (Column<T, ?>) sortInfo.getColumn();
if(this.cellTable.getColumnIndex(column) >= 0) {
this.cellTable.sortColumn(column, sortInfo.isAscending());
}
}
} else {
this.cellTable.sortColumn(startTimeColumn);
}
this.restoreColumnSortInfos();
}
private void initTableStyle() {
@@ -73,24 +66,52 @@ public abstract class AbstractRaceList<T extends RaceMetadataDTO<? extends Abstr
});
}
private void ensureInitTableColumns() {
if (tableColumnsInitialized) return;
tableColumnsInitialized = true;
this.initTableColumns();
}
protected abstract void initTableColumns();
protected void add(SortableRaceListColumn<T, ?> column) {
if (column.isShowDetails()) {
if (column.getColumnStyle() != null) {
column.setCellStyleNames(column.getColumnStyle());
}
private void updateColumnVisibility() {
for (int i = 0; i < this.cellTable.getColumnCount(); i++) {
SortableRaceListColumn<T, ?> column = (SortableRaceListColumn<T, ?>) this.cellTable.getColumn(i);
Header<?> header = column.getHeader();
if (header != null && column.getHeaderStyle() != null) {
header.setHeaderStyleNames(column.getHeaderStyle());
if (header != null) {
header.setHeaderStyleNames(column.getCurrentHeaderStyle());
}
boolean ascending = column.getPreferredSortingOrder().isAscending();
InvertibleComparator<T> comperator = column.getComparator();
if (comperator != null) {
comperator.setAscending(ascending);
}
this.cellTable.addColumn(column, header, comperator, ascending);
column.setCellStyleNames(column.getCurrentColumnStyle());
}
}
@SuppressWarnings("unchecked")
private void restoreColumnSortInfos() {
ColumnSortList sortList = this.cellTable.getColumnSortList();
List<ColumnSortInfo> oldSortInfos;
if (sortList.size() == 0) {
boolean ascending = startTimeColumn.getPreferredSortingOrder().isAscending();
oldSortInfos = Collections.singletonList(new ColumnSortInfo(startTimeColumn, ascending));
} else {
oldSortInfos = new ArrayList<ColumnSortList.ColumnSortInfo>(sortList.size());
for (int i = sortList.size() - 1; i >= 0; i--) {
oldSortInfos.add(sortList.get(i));
}
}
for (ColumnSortInfo sortInfo : oldSortInfos) {
Column<T, ?> column = (Column<T, ?>) sortInfo.getColumn();
this.cellTable.sortColumn(column);
}
}
protected void add(SortableRaceListColumn<T, ?> column) {
Header<?> header = column.getHeader();
boolean ascending = column.getPreferredSortingOrder().isAscending();
InvertibleComparator<T> comperator = column.getComparator();
if (comperator != null) {
comperator.setAscending(ascending);
}
this.cellTable.addColumn(column, header, comperator, ascending);
}
}
@@ -76,8 +76,8 @@ public class RaceListColumnFactory {
@Template("<a href=\"{2}\" class=\"{0}\" target=\"_blank\">{1}</a>")
SafeHtml raceViewerLinkButton(String styleNames, String text, String link);
@Template("<img src=\"{2}\" class=\"{0}\" /><span class=\"{1}\">{3}</span>")
SafeHtml winner(String styleNamesFlag, String styleNamesText, SafeUri flagImageURL, String name);
@Template("<img src=\"{3}\" class=\"{0}\" /><span class=\"{1}\">{4}</span><div class=\"{2}\" title=\"{5}\">{5}</div>")
SafeHtml winner(String styleNamesFlag, String styleNamesSailId, String styleNamesText, SafeUri flagImageURL, String sailId, String name);
@Template("<img src=\"{1}\" class=\"{0}\" />")
SafeHtml imageHeader(String styleNames, SafeUri imageURL);
@@ -470,7 +470,8 @@ public class RaceListColumnFactory {
public void render(Context context, SimpleCompetitorDTO value, SafeHtmlBuilder sb) {
if (value != null) {
SafeUri flagImageUri = FlagImageResolver.getFlagImageUri(value.getFlagImageURL(), value.getTwoLetterIsoCountryCode());
sb.append(TEMPLATE.winner(CSS.race_item_flag(), CSS.race_item_winner(), flagImageUri, value.getName()));
String flagStyle = CSS.race_item_flag(), sailIdStyle = CSS.race_item_sailid(), nameStyle = CSS.race_item_winner();
sb.append(TEMPLATE.winner(flagStyle, sailIdStyle, nameStyle, flagImageUri, value.getSailID(), value.getName()));
}
}
};
@@ -43,7 +43,7 @@ public class RaceListContainer<T extends RaceMetadataDTO<?>> extends Composite i
}
@Override
public void setData(CollectionResult<T> data, long nextUpdate, int updateNo) {
public void setData(CollectionResult<T> data) {
setRaceListData(data == null ? null : data.getValues());
}
@@ -131,7 +131,6 @@ public class RaceListDataUtil {
});
}
private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
public static <T extends RaceMetadataDTO<?>> boolean hasDifferentStartDates(Collection<T> data) {
return hasDifferentValues(data, new ValueProvider<T, Long>() {
@Override
@@ -140,7 +139,7 @@ public class RaceListDataUtil {
if(start == null) {
return null;
}
return start.getTime() / DAY_IN_MILLIS;
return start.getTime() / Duration.ONE_DAY.asMillis();
}
});
}
@@ -1,10 +1,12 @@
package com.sap.sailing.gwt.home.client.place.event.partials.racelist;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.user.cellview.client.TextHeader;
import com.google.gwt.user.cellview.client.SafeHtmlHeader;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.gwt.home.client.place.event.partials.raceListLive.RacesListLiveResources;
import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn;
import com.sap.sse.common.Util;
@@ -16,11 +18,11 @@ public abstract class SortableRaceListColumn<T, C> extends SortableColumn<T, C>
private boolean showDetails = true;
protected SortableRaceListColumn(String headerText, Cell<C> cell, InvertibleComparator<T> comparator) {
this(new TextHeader(headerText), cell, comparator);
this(new WrappedTextHeader(headerText), cell, comparator);
}
protected SortableRaceListColumn(String headerText, Cell<C> cell, InvertibleComparator<T> comparator, SortingOrder preferredSortingOrder) {
this(new TextHeader(headerText), cell, comparator, preferredSortingOrder);
this(new WrappedTextHeader(headerText), cell, comparator, preferredSortingOrder);
}
protected SortableRaceListColumn(Header<?> header, Cell<C> cell, InvertibleComparator<T> comparator) {
@@ -54,4 +56,25 @@ public abstract class SortableRaceListColumn<T, C> extends SortableColumn<T, C>
protected final String getStyleNamesString(String... styleNames) {
return Util.join(" ", styleNames);
}
private static final String COLUMN_HIDDEN_STYLE = RacesListLiveResources.INSTANCE.css().racesListHideColumn();
protected final String getCurrentHeaderStyle() {
return getShowOrHiddenStyle(getHeaderStyle());
}
protected final String getCurrentColumnStyle() {
return getShowOrHiddenStyle(getColumnStyle());
}
private String getShowOrHiddenStyle(String showStyle) {
showStyle = showStyle == null ? "" : showStyle;
return showDetails ? showStyle : getStyleNamesString(showStyle, COLUMN_HIDDEN_STYLE);
}
private static class WrappedTextHeader extends SafeHtmlHeader {
public WrappedTextHeader(String headerText) {
super(SafeHtmlUtils.fromTrustedString("<div>" + headerText + "</div>"));
}
}
}
@@ -19,9 +19,9 @@ public class RegattaHeader extends Composite {
@UiField AnchorElement headerBodyUi;
@UiField AnchorElement headerArrowUi;
public RegattaHeader(RegattaMetadataDTO regattaMetadata) {
public RegattaHeader(RegattaMetadataDTO regattaMetadata, boolean showStateMarker) {
initWidget(uiBinder.createAndBindUi(this));
headerBodyUi.appendChild(new RegattaHeaderBody(regattaMetadata).getElement());
headerBodyUi.appendChild(new RegattaHeaderBody(regattaMetadata, showStateMarker).getElement());
}
public void setRegattaNavigation(PlaceNavigation<?> placeNavigation) {
@@ -12,7 +12,9 @@ import com.google.gwt.user.client.ui.UIObject;
import com.sap.sailing.gwt.common.client.BoatClassImageResolver;
import com.sap.sailing.gwt.home.client.shared.LabelTypeUtil;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.eventview.HasRegattaMetadata.RegattaState;
import com.sap.sailing.gwt.ui.shared.eventview.RegattaMetadataDTO;
import com.sap.sailing.gwt.ui.shared.general.LabelType;
public class RegattaHeaderBody extends UIObject {
@@ -27,13 +29,14 @@ public class RegattaHeaderBody extends UIObject {
@UiField protected DivElement labelUi;
@UiField protected DivElement detailsItemContainerUi;
public RegattaHeaderBody(RegattaMetadataDTO regattaMetadata) {
public RegattaHeaderBody(RegattaMetadataDTO regattaMetadata, boolean showStateMarker) {
RegattaHeaderResources.INSTANCE.css().ensureInjected();
setElement(uiBinder.createAndBindUi(this));
ImageResource logo = BoatClassImageResolver.getBoatClassIconResource(regattaMetadata.getBoatClass());
logoUi.getStyle().setBackgroundImage("url('" + logo.getSafeUri().asString() + "')");
nameUi.setInnerText(regattaMetadata.getDisplayName());
LabelTypeUtil.renderLabelTypeOrHide(labelUi, regattaMetadata.getState().getStateMarker());
RegattaState state = regattaMetadata.getState();
LabelTypeUtil.renderLabelTypeOrHide(labelUi, showStateMarker ? state.getStateMarker() : LabelType.NONE);
addDetailsItem(regattaMetadata.getCompetitorsCount(), I18N.competitorsCount(regattaMetadata.getCompetitorsCount()));
addDetailsItem(regattaMetadata.getRaceCount(), I18N.racesCount(regattaMetadata.getRaceCount()));
String defaultCourseAreaName = regattaMetadata.getDefaultCourseAreaName();
@@ -132,8 +132,8 @@ public class RegattaRacesTabView extends Composite implements RegattaTabView<Reg
refreshManager.add(liveRacesListUi.getRefreshable(), new GetLiveRacesForRegattaAction(myPlace.getCtx().getEventDTO().getId(), myPlace.getRegattaId()));
refreshManager.add(new RefreshableWidget<RegattaWithProgressDTO>() {
@Override
public void setData(RegattaWithProgressDTO data, long nextUpdate, int updateNo) {
regattaInfoContainerUi.setWidget(new MultiRegattaListItem(data));
public void setData(RegattaWithProgressDTO data) {
regattaInfoContainerUi.setWidget(new MultiRegattaListItem(data, true));
}
}, new GetRegattaWithProgressAction(myPlace.getCtx().getEventDTO().getId(), myPlace.getRegattaId()));
refreshManager.add(raceListContainerUi, new GetFinishedRacesAction(myPlace.getCtx().getEventDTO().getId(), myPlace.getRegattaId()));
@@ -2,6 +2,8 @@ package com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -15,10 +17,12 @@ import com.sap.sailing.gwt.home.shared.dispatch.DispatchSystem;
import com.sap.sailing.gwt.ui.shared.dispatch.Action;
import com.sap.sailing.gwt.ui.shared.dispatch.DTO;
import com.sap.sailing.gwt.ui.shared.dispatch.ResultWithTTL;
import com.sap.sse.common.Duration;
public class RefreshManager {
private static final Logger LOG = Logger.getLogger(RefreshManager.class.getName());
private static final long PAUSE_ON_ERROR = 1000 * 30;
private static final long PAUSE_ON_ERROR = Duration.ONE_SECOND.times(30).asMillis();
private List<RefreshHolder<DTO, Action<ResultWithTTL<DTO>>>> refreshables = new ArrayList<>();
private final Timer timer = new Timer() {
@@ -47,16 +51,14 @@ public class RefreshManager {
});
}
private int updateNo;
private void update() {
updateNo++;
for (final RefreshHolder<DTO, Action<ResultWithTTL<DTO>>> refreshable : refreshables) {
// Everything that needs refresh within the next 5000ms will be refreshed now.
// This makes it possible to use batching resulting in less requests.
if (!refreshable.callRunning && refreshable.timeout < System.currentTimeMillis() + 5000) {
refreshable.callRunning = true;
actionExecutor.execute(refreshable.provider.getAction(), new AsyncCallback<ResultWithTTL<DTO>>() {
final Action<ResultWithTTL<DTO>> action = refreshable.provider.getAction();
actionExecutor.execute(action, new AsyncCallback<ResultWithTTL<DTO>>() {
@Override
public void onFailure(Throwable caught) {
refreshable.callRunning = false;
@@ -67,8 +69,12 @@ public class RefreshManager {
@Override
public void onSuccess(ResultWithTTL<DTO> result) {
refreshable.callRunning = false;
refreshable.timeout = System.currentTimeMillis() + result.getTtl();
refreshable.widget.setData(result.getDto(), refreshable.timeout, updateNo);
refreshable.timeout = System.currentTimeMillis() + result.getTtlMillis();
try {
refreshable.widget.setData(result.getDto());
} catch(Throwable error) {
LOG.log(Level.SEVERE, "Error while refreshing content with action " + action.getClass().getName(), error);
}
reschedule();
}
});
@@ -3,5 +3,5 @@ package com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload;
import com.sap.sailing.gwt.ui.shared.dispatch.DTO;
public interface RefreshableWidget<D extends DTO> {
void setData(D data, long nextUpdate, int updateNo);
void setData(D data);
}
@@ -1,6 +1,6 @@
package com.sap.sailing.gwt.home.client.shared.media;
import java.util.List;
import java.util.Collection;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -27,7 +27,7 @@ public class SailingFullscreenViewer implements FullscreenViewer<SailingImageDTO
};
};
public void show(SailingImageDTO selected, List<SailingImageDTO> images) {
public void show(SailingImageDTO selected, Collection<SailingImageDTO> images) {
final SailingGalleryPlayer viewer = new SailingGalleryPlayer(selected, images);
Window.addResizeHandler(new ResizeHandler() {
@@ -1,6 +1,6 @@
package com.sap.sailing.gwt.home.client.shared.media;
import java.util.List;
import java.util.Collection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
@@ -16,6 +16,7 @@ import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.ResizeComposite;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.shared.media.SailingImageDTO;
import com.sap.sse.common.Util;
public class SailingGalleryPlayer extends ResizeComposite {
private static MyBinder uiBinder = GWT.create(MyBinder.class);
@@ -31,9 +32,9 @@ public class SailingGalleryPlayer extends ResizeComposite {
private int selectedIdx;
public SailingGalleryPlayer(SailingImageDTO selected, List<SailingImageDTO> images) {
public SailingGalleryPlayer(SailingImageDTO selected, Collection<SailingImageDTO> images) {
initWidget(uiBinder.createAndBindUi(this));
selectedIdx = Math.max(selectedIdx, images.indexOf(selected));
selectedIdx = Math.max(selectedIdx, Util.indexOf(images, selected));
for (SailingImageDTO i : images) {
mainSliderUi.appendChild(createMainImgElement(i));
subSliderUi.appendChild(createThumbImgElement(i));
@@ -44,6 +44,7 @@ public class VideoPlayer extends Composite {
@Override
public void onClick(ClickEvent event) {
videoJSPlayer.play();
playButton.setVisible(false);
}
}, ClickEvent.getType());
panel.add(playButton);
@@ -1,6 +1,6 @@
package com.sap.sailing.gwt.home.mobile.partials.impressions;
import java.util.List;
import java.util.Collection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
@@ -57,7 +57,7 @@ public class Impressions extends Composite {
headerUi.setSubtitle(sb.toString());
}
public void addImages(List<SailingImageDTO> images) {
public void addImages(Collection<SailingImageDTO> images) {
if (images.isEmpty()) {
return;
}
@@ -38,17 +38,13 @@ public class MinileaderboardBox extends Composite implements RefreshableWidget<G
}
@Override
public void setData(final GetMiniLeaderboardDTO data, long nextUpdate, int updateNo) {
setData(data);
}
public void setAction(String infoText, final PlaceNavigation<?> placeNavigation) {
headerUi.setInfoText(infoText);
headerUi.setClickAction(placeNavigation);
}
@Override
public void setData(final GetMiniLeaderboardDTO data) {
itemContainerUi.clearContent();
@@ -44,7 +44,7 @@ public class RegattaStatus extends Composite implements RefreshableWidget<Regatt
}
@Override
public void setData(RegattasAndLiveRacesDTO data, long nextUpdate, int updateNo) {
public void setData(RegattasAndLiveRacesDTO data) {
regattaContainerUi.clearContent();
collapsableContainerUi.clearContent();
if (data.hasRegattasWithRaces()) {
@@ -64,7 +64,7 @@ public class StatisticsBox extends Composite implements RefreshableWidget<EventS
}
@Override
public void setData(EventStatisticsDTO statistics, long nextUpdate, int updateNo) {
public void setData(EventStatisticsDTO statistics) {
itemContainerUi.clearContent();
if (showRegattaInformation) {
addItem(StatisticsBox.ICON_REGATTAS_FOUGHT, MSG.regattas(), statistics.getRegattasFoughtCount());
@@ -48,7 +48,7 @@ public class UpdatesBox extends Composite implements RefreshableWidget<ListResul
@Override
public void setData(final ListResult<NewsEntryDTO> data, long nextUpdate, int updateNo) {
public void setData(final ListResult<NewsEntryDTO> data) {
setData(data == null ? null :data.getValues());
}
@@ -11,7 +11,6 @@ import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO;
public class QuickfinderPresenter {
private static final StringMessages MSG = StringMessages.INSTANCE;
public QuickfinderPresenter(Quickfinder quickfinder, RegattaLeaderboardNavigationProvider navigator, Collection<RegattaMetadataDTO> regattaMetadatas) {
if (regattaMetadatas == null) {
quickfinder.removeFromParent();
@@ -19,7 +18,11 @@ public class QuickfinderPresenter {
}
quickfinder.addPlaceholderItem(MSG.resultsQuickfinder());
for (RegattaMetadataDTO regattaMetadata : regattaMetadatas) {
quickfinder.addItemToGroup(regattaMetadata.getBoatCategory(), regattaMetadata.getDisplayName(), navigator.getRegattaMiniLeaderboardNavigation(regattaMetadata.getId()));
String boatCategory = regattaMetadata.getBoatCategory();
if(boatCategory == null || boatCategory.isEmpty()) {
boatCategory = MSG.regattas();
}
quickfinder.addItemToGroup(boatCategory, regattaMetadata.getDisplayName(), navigator.getRegattaMiniLeaderboardNavigation(regattaMetadata.getId()));
}
}
@@ -38,5 +41,4 @@ public class QuickfinderPresenter {
quickfinder.addItemToGroup(seriesName, displayName, navigator.getMiniLeaderboardNavigation(eventOfSeries.getId()));
}
}
}
@@ -1,7 +1,6 @@
package com.sap.sailing.gwt.home.mobile.places.event;
import java.util.Collection;
import java.util.List;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.client.place.event.EventContext;
@@ -28,7 +27,7 @@ public interface EventView {
void hideQuickfinder();
void setMediaForImpressions(int nrOfImages, int nrOfVideos, List<SailingImageDTO> images);
void setMediaForImpressions(int nrOfImages, int nrOfVideos, Collection<SailingImageDTO> images);
public interface Presenter extends NewsItemLinkProvider, RegattaLeaderboardNavigationProvider, SeriesLeaderboardNavigationProvider {
EventContext getCtx();
@@ -1,7 +1,6 @@
package com.sap.sailing.gwt.home.mobile.places.event;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import com.google.gwt.core.client.GWT;
@@ -129,7 +128,7 @@ public class EventViewImpl extends Composite implements EventView {
}
@Override
public void setMediaForImpressions(int nrOfImages, int nrOfVideos, List<SailingImageDTO> images) {
public void setMediaForImpressions(int nrOfImages, int nrOfVideos, Collection<SailingImageDTO> images) {
impressionsUi.getElement().getStyle().setDisplay(Display.BLOCK);
impressionsUi.setStatistis(nrOfImages, nrOfVideos);
impressionsUi.addImages(images);
@@ -15,16 +15,16 @@
padding-bottom: 10px;
}
</ui:style>
<g:HTMLPanel>
<div class="{style.headerWrapper}">
<mp:eventheader.EventHeader ui:field="eventHeaderUi" />
<mp:quickfinder.Quickfinder ui:field="quickFinderUi" />
<mp:simpleinfoblock.SimpleInfoBlock ui:field="simpleInfoUi" />
</div>
<g:HTMLPanel>
<div class="{style.headerWrapper}">
<mp:eventheader.EventHeader ui:field="eventHeaderUi" />
<mp:quickfinder.Quickfinder ui:field="quickFinderUi" />
<mp:simpleinfoblock.SimpleInfoBlock ui:field="simpleInfoUi" />
</div>
<p:overview.EventOverviewStage ui:field="overviewStageUi" />
<g:SimplePanel ui:field="listContentUi"/>
<mp:updatesBox.UpdatesBox ui:field="updatesBoxUi" />
<mp:impressions.Impressions ui:field="impressionsUi" />
<mp:statisticsBox.StatisticsBox ui:field="statisticsBoxUi" />
<mp:impressions.Impressions ui:field="impressionsUi" />
<mp:statisticsBox.StatisticsBox ui:field="statisticsBoxUi" />
</g:HTMLPanel>
</ui:UiBinder>
@@ -36,7 +36,7 @@ public class EventOverviewStage extends Composite implements RefreshableWidget<E
}
@Override
public void setData(EventOverviewStageDTO stageData, long nextUpdate, int updateNo) {
public void setData(EventOverviewStageDTO stageData) {
message.setMessage(stageData.getEventMessage());
EventOverviewStageContentDTO data = stageData.getStageContent();
@@ -13,8 +13,10 @@ import com.sap.sailing.gwt.home.mobile.partials.minileaderboard.MinileaderboardB
import com.sap.sailing.gwt.home.mobile.partials.quickfinder.Quickfinder;
import com.sap.sailing.gwt.home.mobile.partials.recents.EventsOverviewRecentYearEvent;
import com.sap.sailing.gwt.home.mobile.partials.seriesheader.SeriesHeader;
import com.sap.sailing.gwt.home.mobile.partials.statisticsBox.StatisticsBox;
import com.sap.sailing.gwt.home.mobile.places.QuickfinderPresenter;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.dispatch.event.GetEventStatisticsAction;
import com.sap.sailing.gwt.ui.shared.dispatch.event.GetMiniOverallLeaderbordAction;
import com.sap.sailing.gwt.ui.shared.fakeseries.EventSeriesViewDTO;
import com.sap.sailing.gwt.ui.shared.general.EventMetadataDTO;
@@ -31,7 +33,7 @@ public class SeriesViewImpl extends Composite implements SeriesView {
@UiField Quickfinder quickFinderUi;
@UiField(provided = true) MinileaderboardBox leaderboardUi;
@UiField FlowPanel eventsUi;
// @UiField(provided = true) StatisticsBox statisticsBoxUi;
@UiField(provided = true) StatisticsBox statisticsBoxUi;
private final Presenter currentPresenter;
private final RefreshManager refreshManager;
@@ -41,7 +43,7 @@ public class SeriesViewImpl extends Composite implements SeriesView {
this.refreshManager = new RefreshManager(this, currentPresenter.getDispatch());
EventSeriesViewDTO series = currentPresenter.getCtx().getSeriesDTO();
eventHeaderUi = new SeriesHeader(series);
// this.setupStatisticsBox(event);
this.setupStatisticsBox(series);
leaderboardUi = new MinileaderboardBox(true);
initWidget(uiBinder.createAndBindUi(this));
this.setupListContent(series);
@@ -70,9 +72,9 @@ public class SeriesViewImpl extends Composite implements SeriesView {
leaderboardUi.setAction(MSG.showAll(), currentPresenter.getMiniOverallLeaderboardNavigation());
refreshManager.add(leaderboardUi, new GetMiniOverallLeaderbordAction(event.getId(), 3));
}
//
// private void setupStatisticsBox(EventViewDTO event) {
// statisticsBoxUi = new StatisticsBox(event.getType() == EventType.MULTI_REGATTA);
// refreshManager.add(statisticsBoxUi, new GetEventStatisticsAction(event.getId()));
// }
private void setupStatisticsBox(EventSeriesViewDTO series) {
statisticsBoxUi = new StatisticsBox(true);
refreshManager.add(statisticsBoxUi, new GetEventStatisticsAction(series.getId(), false));
}
}
@@ -22,6 +22,6 @@
</div>
<g:FlowPanel ui:field="eventsUi" />
<mp:minileaderboard.MinileaderboardBox ui:field="leaderboardUi"/>
<!-- <mp:statisticsBox.StatisticsBox ui:field="statisticsBoxUi" /> -->
<mp:statisticsBox.StatisticsBox ui:field="statisticsBoxUi" />
</g:HTMLPanel>
</ui:UiBinder>
@@ -19,17 +19,12 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.LeaderboardGroupBase;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.gwt.ui.shared.eventlist.EventListEventDTO;
import com.sap.sailing.gwt.ui.shared.eventview.HasRegattaMetadata.RegattaState;
import com.sap.sailing.gwt.ui.shared.eventview.RegattaMetadataDTO;
import com.sap.sailing.gwt.ui.shared.general.EventMetadataDTO;
import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO;
import com.sap.sailing.gwt.ui.shared.general.EventState;
@@ -40,7 +35,6 @@ import com.sap.sailing.gwt.ui.shared.start.StageEventType;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.media.ImageDescriptor;
import com.sap.sse.common.media.MediaDescriptor;
@@ -86,22 +80,6 @@ public final class HomeServiceUtil {
return true;
}
public static RegattaState calculateRegattaState(RegattaMetadataDTO regatta) {
Date now = new Date();
Date startDate = regatta.getStartDate();
Date endDate = regatta.getEndDate();
if(startDate != null && now.compareTo(startDate) < 0) {
return RegattaState.UPCOMING;
}
if(endDate != null && now.compareTo(endDate) > 0) {
return RegattaState.FINISHED;
}
if(startDate != null && now.compareTo(startDate) >= 0 && endDate != null && now.compareTo(endDate) <= 0) {
return RegattaState.RUNNING;
}
return RegattaState.UNKNOWN;
}
public static EventState calculateEventState(EventBase event) {
return calculateEventState(event.isPublic(), event.getStartDate().asDate(), event.getEndDate().asDate());
}
@@ -450,46 +428,11 @@ public final class HomeServiceUtil {
return result;
}
public static RegattaMetadataDTO toRegattaMetadataDTO(EventBase event, LeaderboardGroup leaderboardGroup, Leaderboard leaderboard) {
RegattaMetadataDTO regattaDTO = new RegattaMetadataDTO();
fillRegattaFields(event, leaderboardGroup, leaderboard, regattaDTO);
return regattaDTO;
}
public static void fillRegattaFields(EventBase event, LeaderboardGroup leaderboardGroup, Leaderboard leaderboard,
RegattaMetadataDTO regattaDTO) {
regattaDTO.setId(leaderboard.getName());
regattaDTO.setDisplayName(leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : leaderboard.getName());
if(hasMultipleLeaderboardGroups(event)) {
regattaDTO.setBoatCategory(leaderboardGroup.getDisplayName() != null ? leaderboardGroup.getDisplayName() : leaderboardGroup.getName());
}
regattaDTO.setCompetitorsCount(calculateCompetitorsCount(leaderboard));
regattaDTO.setRaceCount(calculateRaceColumnCount(leaderboard));
regattaDTO.setBoatClass(getBoatClassName(leaderboard));
if(leaderboard instanceof RegattaLeaderboard) {
Regatta regatta = ((RegattaLeaderboard) leaderboard).getRegatta();
regattaDTO.setStartDate(regatta.getStartDate() != null ? regatta.getStartDate().asDate() : null);
regattaDTO.setEndDate(regatta.getEndDate() != null ? regatta.getEndDate().asDate() : null);
}
regattaDTO.setState(calculateRegattaState(regattaDTO));
regattaDTO.setDefaultCourseAreaName(getCourseAreaNameForRegattaIdThereIsMoreThanOne(event, leaderboard));
}
private static boolean hasMultipleLeaderboardGroups(EventBase event) {
return Util.size(event.getLeaderboardGroups()) > 1;
}
public static boolean hasLiveRace(LeaderboardDTO leaderboard) {
List<Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(getLiveTimePointInMillis());
return !liveRaces.isEmpty();
}
public static TimePoint getLiveTimePoint() {
return new MillisecondsTimePoint(getLiveTimePointInMillis());
}
private static long getLiveTimePointInMillis() {
public static long getLiveTimePointInMillis() {
// TODO better solution
long livePlayDelayInMillis = 15_000;
return System.currentTimeMillis() - livePlayDelayInMillis;
@@ -511,4 +454,17 @@ public final class HomeServiceUtil {
}
return courseArea == null ? null : courseArea.getName();
}
public static String getCourseAreaIdForRegatta(EventBase event, Leaderboard leaderboard) {
CourseArea courseArea = null;
if (leaderboard instanceof FlexibleLeaderboard) {
courseArea = ((FlexibleLeaderboard) leaderboard).getDefaultCourseArea();
} else if(leaderboard instanceof RegattaLeaderboard) {
Regatta regatta = ((RegattaLeaderboard) leaderboard).getRegatta();
if (regatta != null) {
courseArea = regatta.getDefaultCourseArea();
}
}
return courseArea == null ? null : courseArea.getId().toString();
}
}
@@ -36,6 +36,7 @@ import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.impl.NaturalComparator;
import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
import com.sap.sailing.gwt.ui.client.LeaderboardsDisplayer;
@@ -156,7 +157,15 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : "";
}
};
leaderboardDisplayNameColumn.setSortable(true);
leaderboardColumnListHandler.setComparator(leaderboardDisplayNameColumn, new Comparator<StrippedLeaderboardDTO>() {
@Override
public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) {
return new NaturalComparator().compare(o1.getDisplayName(), o2.getDisplayName());
}
});
TextColumn<StrippedLeaderboardDTO> discardingOptionsColumn = new TextColumn<StrippedLeaderboardDTO>() {
@Override
public String getValue(StrippedLeaderboardDTO leaderboard) {
@@ -169,6 +178,25 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
return result;
}
};
discardingOptionsColumn.setSortable(true);
leaderboardColumnListHandler.setComparator(discardingOptionsColumn, new Comparator<StrippedLeaderboardDTO>() {
@Override
public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) {
String s1 = "";
String s2 = "";
if (o1.discardThresholds != null) {
for (int i : o1.discardThresholds) {
s1 += i;
}
}
if (o2.discardThresholds != null) {
for (int i : o2.discardThresholds) {
s2 += i;
}
}
return new NaturalComparator().compare(s1, s2);
}
});
TextColumn<StrippedLeaderboardDTO> leaderboardTypeColumn = new TextColumn<StrippedLeaderboardDTO>() {
@Override
@@ -180,6 +208,14 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
return result;
}
};
leaderboardTypeColumn.setSortable(true);
leaderboardColumnListHandler.setComparator(leaderboardTypeColumn, new Comparator<StrippedLeaderboardDTO>() {
@Override
public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) {
return o1.type.compareTo(o2.type);
}
});
TextColumn<StrippedLeaderboardDTO> scoringSystemColumn = new TextColumn<StrippedLeaderboardDTO>() {
@Override
@@ -187,6 +223,16 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
return leaderboard.scoringScheme == null ? "" : ScoringSchemeTypeFormatter.format(leaderboard.scoringScheme, stringMessages);
}
};
scoringSystemColumn.setSortable(true);
leaderboardColumnListHandler.setComparator(scoringSystemColumn, new Comparator<StrippedLeaderboardDTO>() {
@Override
public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) {
String s1 = o1.scoringScheme == null ? null:o1.scoringScheme.toString();
String s2 = o2.scoringScheme == null ? null:o2.scoringScheme.toString();
return new NaturalComparator().compare(s1, s2);
}
});
TextColumn<StrippedLeaderboardDTO> courseAreaColumn = new TextColumn<StrippedLeaderboardDTO>() {
@Override
@@ -194,6 +240,14 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
return leaderboard.defaultCourseAreaId == null ? "" : leaderboard.defaultCourseAreaName;
}
};
courseAreaColumn.setSortable(true);
leaderboardColumnListHandler.setComparator(courseAreaColumn, new Comparator<StrippedLeaderboardDTO>() {
@Override
public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) {
return new NaturalComparator().compare(o1.defaultCourseAreaName, o2.defaultCourseAreaName);
}
});
ImagesBarColumn<StrippedLeaderboardDTO, LeaderboardConfigImagesBarCell> leaderboardActionColumn = new ImagesBarColumn<StrippedLeaderboardDTO, LeaderboardConfigImagesBarCell>(
new LeaderboardConfigImagesBarCell(stringMessages));

Some files were not shown because too many files have changed in this diff Show More