Merge branch 'master' into bug4656

This commit is contained in:
RobinFleige committed 2018-08-13 08:04:24 +02:00
commit 9b6669a446
131 files changed
+1392 -741

No files matched your search

@@ -26,10 +26,10 @@ Require-Bundle: com.sap.sailing.domain,
com.googlecode.mgwt;bundle-version="2.0.0";resolution:=optional,
com.sap.sse.gwt.adminconsole,
org.apache.commons.math;bundle-version="2.1.0",
com.sap.sse.security.ui
com.sap.sse.security.ui,
com.sap.sse
Bundle-ActivationPolicy: lazy
Import-Package: com.sap.sse.util,
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.commons.io,
@@ -21,7 +21,7 @@ public class RibDashboardEntryPoint extends AbstractEntryPoint<StringMessages> {
public void doOnModuleLoad() {
applyMGWTSettings();
DashboardClientFactory dashboardClientFactory = new DashboardClientFactoryImpl();
DashboardPanel rootPanel = new DashboardPanel(dashboardClientFactory);
DashboardPanel rootPanel = new DashboardPanel(dashboardClientFactory, this);
RootLayoutPanel.get().add(rootPanel);
WindBotDataRetriever windBotDataRetriever = new WindBotDataRetriever(dashboardClientFactory);
windBotDataRetriever.addNumberOfWindBotsChangeListeners(rootPanel);
@@ -36,6 +36,7 @@ import com.sap.sailing.dashboards.gwt.shared.dto.RaceIdDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.media.ImageDTO;
/**
@@ -88,12 +89,12 @@ public class DashboardPanel extends Composite implements NumberOfWindBotsChangeL
DashboardClientFactory dashboardClientFactory;
private static final Logger logger = Logger.getLogger(DashboardPanel.class.getName());
public DashboardPanel(DashboardClientFactory dashboardClientFactory) {
public DashboardPanel(DashboardClientFactory dashboardClientFactory, ErrorReporter errorReporter) {
DashboardPanelResources.INSTANCE.style().ensureInjected();
this.dashboardClientFactory = dashboardClientFactory;
leftWindBotWidget = new WindBotWidget(dashboardClientFactory);
rightWindBotWidget = new WindBotWidget(dashboardClientFactory);
startanalysisComponent = new StartAnalysisWidget(this.dashboardClientFactory);
startanalysisComponent = new StartAnalysisWidget(this.dashboardClientFactory, errorReporter);
startlineAdvantagesByWindComponent = new StartlineAdvantagesByWindWidget(this.dashboardClientFactory);
startlineAdvantageByGeometryWidget = new StartlineAdvantageByGeometryWidget(this.dashboardClientFactory);
stringConstants = StringMessages.INSTANCE;
@@ -43,6 +43,7 @@ import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
import com.sap.sse.gwt.client.ErrorReporter;
/**
* The class contains an collection of {@link StartAnalysisCard}s that are displayed in horizontal aligned pages. It
@@ -112,11 +113,12 @@ public class StartAnalysisWidget extends Composite implements HasWidgets, PollsL
* */
private int numberOfStartAnalysisCards = 0;
private boolean displaysCards;
private List<StartAnalysisDTO> starts;
private List<StartAnalysisCard> pageChangeListener;
private final List<StartAnalysisDTO> starts;
private final List<StartAnalysisCard> pageChangeListener;
private BottomNotification bottomNotification;
private CompetitorSelectionPopup competitorSelectionPopup;
private DashboardClientFactory dashboardClientFactory;
private final DashboardClientFactory dashboardClientFactory;
private final ErrorReporter errorReporter;
private final String cookieKeyForSelectedCompetitorInLeaderboard;
@@ -131,8 +133,9 @@ public class StartAnalysisWidget extends Composite implements HasWidgets, PollsL
/**
* Component that contains handles, displays and loads startanalysis cards.
* */
public StartAnalysisWidget(DashboardClientFactory dashboardClientFactory) {
public StartAnalysisWidget(DashboardClientFactory dashboardClientFactory, ErrorReporter errorReporter) {
StartAnalysisWidgetResources.INSTANCE.gss().ensureInjected();
this.errorReporter = errorReporter;
dashboardWidgetHeaderAndNoDataMessage = new DashboardWidgetHeaderAndNoDataMessage();
selectedCompetitorWithSettingsButton = new SettingsButtonWithSelectionIndicationLabel();
selectedCompetitorWithSettingsButton.disable();
@@ -378,7 +381,7 @@ public class StartAnalysisWidget extends Composite implements HasWidgets, PollsL
}
final StartAnalysisCard startlineAnalysisCard = new StartAnalysisCard(numberOfStartAnalysisCards
* SCROLL_OFFSET_STARTANALYSIS_CARDS + MARGIN_LEFT_STARTANALYSIS_CARD, numberOfStartAnalysisCards,
startAnalysisDTO, dashboardClientFactory.getSailingService(), raceMapResources);
startAnalysisDTO, dashboardClientFactory.getSailingService(), errorReporter, raceMapResources);
startanalysis_card_container.add(startlineAnalysisCard);
startlineAnalysisCard.startAnalysisComponentPageChangedToIndexAndStartAnalysis(page, startAnalysisDTO);
registerPageChangeListener(startlineAnalysisCard);
@@ -38,6 +38,7 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
@@ -70,8 +71,9 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
private StartAnalysisDTO startAnalysisDTO;
private RaceMap raceMap;
private SailingServiceAsync sailingServiceAsync;
private StringMessages stringMessages;
private final SailingServiceAsync sailingServiceAsync;
private final ErrorReporter errorReporter;
private final StringMessages stringMessages;
private final RaceCompetitorSelectionModel competitorSelectionModel;
private final double WIND_LINE_ADVANTAGE_DIV_WIDTH_IN_PT = 185;
@@ -80,9 +82,11 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
private RaceMapResources raceMapResources;
public StartAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO, SailingServiceAsync sailingServiceAsync, RaceMapResources raceMapResources) {
public StartAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO,
SailingServiceAsync sailingServiceAsync, ErrorReporter errorReporter, RaceMapResources raceMapResources) {
stringMessages = StringMessages.INSTANCE;
this.sailingServiceAsync = sailingServiceAsync;
this.errorReporter = errorReporter;
this.raceMapResources = raceMapResources;
competitorSelectionModel = new RaceCompetitorSelectionModel(/* hasMultiSelection */true);
competitorSelectionModel.setCompetitors(startAnalysisDTO.getCompetitorDTOsFromStartAnaylsisCompetitorDTOs(), raceMap);
@@ -167,10 +171,11 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingServiceAsync,
asyncActionsExecutor, null, Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier), 5000l /* requestInterval */);
asyncActionsExecutor, errorReporter,
Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier), 5000l /* requestInterval */);
raceMap = new RaceMap(null, null, new RaceMapLifecycle(StringMessages.INSTANCE), raceMapSettings,
sailingServiceAsync,
asyncActionsExecutor, null, timer, competitorSelectionModel,
asyncActionsExecutor, errorReporter, timer, competitorSelectionModel,
new RaceCompetitorSet(competitorSelectionModel), StringMessages.INSTANCE,
startAnalysisDTO.regattaAndRaceIdentifier, raceMapResources, /* showHeaderPanel */ true, new DefaultQuickRanksDTOProvider());
raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
@@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Rumbo relativo absoluto a la siguiente
RatioBetweenDistanceSailedWithAndWithoutManeuver=Ratio entre distancia navegada con y sin maniobra
RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Ratio entre distancia navegada hacia la proyección del ángulo medio con y sin maniobra
DurationLostByManeuver=Tiempo perdido por maniobra
DurationLostByManeuverTowardMiddleAngleProjection=tiempo perdido por maniobra hacia la proyección del ángulo medio
DurationLostByManeuverTowardMiddleAngleProjection=Tiempo perdido por maniobra hacia la proyección del ángulo medio
JibingCount=Recuento de trasluchadas
TackingCount=Recuento de viradas
AbsTwaAtMaxTurningRate=TWA absoluto en velocidad máxima de giro
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Porcentaje de progreso de curva p
PercentageOfMainCurveProgressUntilMaxTurningRate=Porcentaje de progreso de curva principal hasta velocidad máxima de giro
AbsTwaAtManeuverMiddle=TWA absoluto en mitad de maniobra
GpsSamplingRate=Velocidad de muestreo de GPS
RaceDuration=Duración de carrera
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Pourcentage de progression de la
PercentageOfMainCurveProgressUntilMaxTurningRate=Pourcentage de progression de la courbe principale jusqu''à la vitesse angulaire max.
AbsTwaAtManeuverMiddle=TWA absolu au milieu de la manœuvre
GpsSamplingRate=Fréquence d''échantillonage GPS
RaceDuration=Durée de la course
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=最高速度までの主曲線進
PercentageOfMainCurveProgressUntilMaxTurningRate=回転率最大速度までの主曲線進行の比率 (%)
AbsTwaAtManeuverMiddle=マニューバー中間での絶対 TWA
GpsSamplingRate=GPS サンプリング率
RaceDuration=レース持続時間
@@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Rumo relativo absoluto até a marca se
RatioBetweenDistanceSailedWithAndWithoutManeuver=Proporção entre distância navegada com e sem manobra
RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Proporção entre distância navegada em direção à projeção do ângulo médio com e sem manobra
DurationLostByManeuver=Duração perdida por manobra
DurationLostByManeuverTowardMiddleAngleProjection=duração perdida por manobra em direção à projeção do ângulo médio
DurationLostByManeuverTowardMiddleAngleProjection=Duração perdida por manobra em direção à projeção do ângulo médio
JibingCount=Contagem de jaibe
TackingCount=Contagem de cambada
AbsTwaAtMaxTurningRate=TWA absoluto na taxa de viragem máxima
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Porcentagem de progresso da curva
PercentageOfMainCurveProgressUntilMaxTurningRate=Porcentagem de progresso da curva principal até velocidade de taxa de viragem máxima
AbsTwaAtManeuverMiddle=TWA absoluto no meio da manobra
GpsSamplingRate=Taxa de amostra de GPS
RaceDuration=Duração da corrida
@@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Абс. относительное н
RatioBetweenDistanceSailedWithAndWithoutManeuver=Соотношение пройденного расстояния с маневром и без
RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Соотношение пройденного расстояния на срединную угловую проекцию с маневром и без
DurationLostByManeuver=Потеря длительности за счет маневра
DurationLostByManeuverTowardMiddleAngleProjection=потеря длительности за счет маневра на срединную угловую проекцию
DurationLostByManeuverTowardMiddleAngleProjection=Потеря длительности за счет маневра на срединную угловую проекцию
JibingCount=Поворотов фордевинд
TackingCount=Поворотов оверштаг
AbsTwaAtMaxTurningRate=Абс. TWA при макс. скорости поворота
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Процент прохожден
PercentageOfMainCurveProgressUntilMaxTurningRate=Процент прохождения основной кривой до макс. скорости поворота
AbsTwaAtManeuverMiddle=Абс. TWA в середине маневра
GpsSamplingRate=Частота дискретизации GPS
RaceDuration=Длительность гонки
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=最高速度前主要曲线进度
PercentageOfMainCurveProgressUntilMaxTurningRate=最大回转率速度前主要曲线进度的百分比
AbsTwaAtManeuverMiddle=操作中间的绝对实际风向角
GpsSamplingRate=GPS 采样率
RaceDuration=比赛轮次持续时间
@@ -17,7 +17,7 @@ public enum BoatClassMasterdata {
_49ER ("49er", true, 4.88, 1.93, BoatHullType.MONOHULL, true),
_49ERFX ("49erFX", true, 4.88, 1.93, BoatHullType.MONOHULL, true, "49FX"),
_420 ("420", true, 4.20, 1.65, BoatHullType.MONOHULL, true, "420er", "420M", "420W"),
_470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W"),
_470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W", "470 Men", "470 Women"),
_5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, true, "505", "5o5", "505er", "5o5er"),
_5_5MR ("5.5mR", true, 9.50, 1.92, BoatHullType.MONOHULL, true, "5.5 Meter", "5.5 Metre", "5.5", "5.5M", "5.5-metre", "5.5 metre"),
_6MR ("6mR", true, 11, 2.2, BoatHullType.MONOHULL, true, "6 Meter", "6 Metre", "6", "6M", "6-metre", "6 metre"),
@@ -46,7 +46,11 @@ public enum MaxPointsReason {
/** Time limit Expired */
TLE(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true, /* appliesAtStartOfRace */ false),
/** Standard Penalty by Race Committee; gives a certain number of penalty points on top of rank-inferred score */
STP(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ false, /* appliesAtStartOfRace */ true);
STP(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ false, /* appliesAtStartOfRace */ true),
/** Disqualified after causing a tangle in an incident */
DCT(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true, /* appliesAtStartOfRace */ false),
/** Retired after causing a tangle */
RCT(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true, /* appliesAtStartOfRace */ false);
private final boolean discardable;
@@ -18,6 +18,7 @@ public class BasicRaceDTO extends NamedDTO {
public Date startOfRace;
public Date endOfRace;
public Date raceFinishedTime;
public TrackedRaceDTO trackedRace;
public BasicRaceDTO() {} // for GWT serialization only
@@ -67,10 +67,8 @@ public abstract class PassAwareRaceLogAnalyzerTest<AnalyzerType extends RaceLogA
RaceLogEvent minorEvent = createEvent(RaceLogEvent.class, 0, 0, UUID.randomUUID(), author);
TargetPair pair = getTargetEventsAndResultForPassAwareTests(1, author);
ResultType expectedResult = pair.getB();
pair.addAll(raceLog);
raceLog.add(minorEvent);
assertEquals(expectedResult, analyzer.analyze());
}
@@ -30,7 +30,8 @@ public abstract class RaceLogAnalyzerTest<AnalyzerType extends RaceLogAnalyzer<?
protected abstract AnalyzerType createAnalyzer(RaceLog raceLog);
protected static <T extends RaceLogEvent> T createEvent(Class<T> type, long milliseconds) {
return createEvent(/* priority */ 0, type, milliseconds);
final T result = createEvent(/* priority */ 0, type, milliseconds);
return result;
}
protected static <T extends RaceLogEvent> T createEvent(int priority, Class<T> type, long milliseconds) {
@@ -7,6 +7,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
@@ -18,21 +19,36 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceStatusAnalyzer;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceStatusAnalyzer.Clock;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.ReadonlyRacingProcedureFactory;
import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceStatusAnalyzer, RaceLogRaceStatus> {
public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceStatusAnalyzer, Pair<RaceLogRaceStatus, TimePoint>> {
private RacingProcedure racingProcedure = mock(RacingProcedure.class);
private TimePoint now = MillisecondsTimePoint.now();
private Clock constantTimeClock = ()->now;
@Override
protected RaceStatusAnalyzer createAnalyzer(RaceLog raceLog) {
return new RaceStatusAnalyzer(mock(RaceLogResolver.class), raceLog, racingProcedure);
return new RaceStatusAnalyzer(mock(RaceLogResolver.class), raceLog, constantTimeClock, racingProcedure);
}
@Override
@@ -40,7 +56,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author);
when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.RUNNING);
doAnswer(new StatusVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class));
return new TargetPair(Arrays.asList(event), event.getNextStatus());
return new TargetPair(Arrays.asList(event), new Pair<>(event.getNextStatus(), now));
}
@Override
@@ -49,38 +65,46 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author);
when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHING);
doAnswer(new StatusVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class));
return new TargetPair(Arrays.asList(event), event.getNextStatus());
return new TargetPair(Arrays.asList(event), new Pair<>(event.getNextStatus(), now));
}
@Test
public void testUnscheduldedForNone() {
RaceLogEvent event1 = createEvent(RaceLogEvent.class, 1);
raceLog.add(event1);
assertEquals(RaceLogRaceStatus.UNSCHEDULED, analyzer.analyze());
assertEquals(RaceLogRaceStatus.UNSCHEDULED, analyzer.analyze().getA());
}
@Test
public void testMostRecent() {
RaceLogRaceStatusEvent event1 = createEvent(RaceLogRaceStatusEvent.class, 1);
when(event1.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHING);
RaceLogRaceStatusEvent event2 = createEvent(RaceLogRaceStatusEvent.class, 2);
when(event2.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHED);
doAnswer(new StatusVisitorAnswer()).when(event2).accept(any(RaceLogEventVisitor.class));
LogEventAuthorImpl author = new LogEventAuthorImpl("author", 0);
RaceLogRaceStatusEvent event1 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(1), author, /* passId */ 1, RaceLogRaceStatus.FINISHING);
RaceLogRaceStatusEvent event2 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(2), author, /* passId */ 1, RaceLogRaceStatus.FINISHED);
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event2.getNextStatus(), analyzer.analyze());
assertEquals(event2.getNextStatus(), analyzer.analyze().getA());
}
@Test
public void testFinishingWithFinishedInFuture() {
now = new MillisecondsTimePoint(2);
LogEventAuthorImpl author = new LogEventAuthorImpl("author", 0);
RaceLogRaceStatusEvent event1 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(1), author, /* passId */ 1, RaceLogRaceStatus.FINISHING);
RaceLogRaceStatusEvent event2 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(3), author, /* passId */ 1, RaceLogRaceStatus.FINISHED);
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event1.getNextStatus(), analyzer.analyze().getA());
}
@Test
public void testStartphaseNotYetActive() {
when(racingProcedure.isStartphaseActive(any(TimePoint.class), any(TimePoint.class))).thenReturn(false);
RaceLogStartTimeEvent event = createStartTimeEvent(MillisecondsTimePoint.now().plus(20000).asMillis(), true);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze());
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze().getA());
}
@Test
@@ -90,7 +114,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogStartTimeEvent event = createStartTimeEvent(MillisecondsTimePoint.now().plus(20000).asMillis(), true);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.STARTPHASE, analyzer.analyze());
assertEquals(RaceLogRaceStatus.STARTPHASE, analyzer.analyze().getA());
}
@Test
@@ -100,7 +124,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogStartTimeEvent event = createStartTimeEvent(0, false);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.RUNNING, analyzer.analyze());
assertEquals(RaceLogRaceStatus.RUNNING, analyzer.analyze().getA());
}
@Test
@@ -112,13 +136,47 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
return startTime.minus(1);
}
}, racingProcedure);
when(racingProcedure.isStartphaseActive(any(TimePoint.class), any(TimePoint.class))).thenReturn(false);
RaceLogStartTimeEvent event = createStartTimeEvent(startTime);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze());
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze().getA());
}
@Test
public void testRaceStateStatusUpdateWithoutNewRaceLogEvent() throws InterruptedException {
final TimePoint[] timeForClock = { MillisecondsTimePoint.now() };
final RaceStatusAnalyzer.Clock clock = new RaceStatusAnalyzer.Clock() {
@Override
public TimePoint now() {
return timeForClock[0];
}
};
final int analyzeAndUpdateCachedRaceStatusCalls[] = new int[1];
ReadonlyRaceState raceState = new ReadonlyRaceStateImpl(mock(RaceLogResolver.class), raceLog,
new SimpleRaceLogIdentifierImpl("regatta", "column", "fleet"), clock, new ReadonlyRacingProcedureFactory(
new EmptyRegattaConfiguration()), Collections.emptyMap()) {
@Override
protected void analyzeAndUpdateCachedRaceStatus() {
analyzeAndUpdateCachedRaceStatusCalls[0]++;
super.analyzeAndUpdateCachedRaceStatus();
}
};
RaceLogStartProcedureChangedEvent startProcedureEvent = new RaceLogStartProcedureChangedEventImpl(
timeForClock[0], mock(AbstractLogEventAuthor.class), /* passId */ 0, RacingProcedureType.RRS26);
raceLog.add(startProcedureEvent);
final TimePoint startTime = timeForClock[0].plus(Duration.ONE_MINUTE.times(10));
RaceLogStartTimeEvent event = createStartTimeEvent(startTime);
raceLog.add(event);
assertEquals(startTime, raceState.getStartTime());
assertEquals(RaceLogRaceStatus.SCHEDULED, raceState.getStatus());
timeForClock[0] = timeForClock[0].plus(Duration.ONE_MINUTE.times(9)); // one minute before start; we should be in STARTPHASE
assertEquals(RaceLogRaceStatus.STARTPHASE, raceState.getStatus());
timeForClock[0] = timeForClock[0].plus(Duration.ONE_MINUTE.times(2)); // one minute after start; we should be RUNNING
assertEquals(RaceLogRaceStatus.RUNNING, raceState.getStatus());
timeForClock[0] = timeForClock[0].plus(Duration.ONE_MINUTE.times(2)); // three minutes after start; we should still be RUNNING, without recalculating
final int analyzeAndUpdateCachedRaceStatusCallsBefore = analyzeAndUpdateCachedRaceStatusCalls[0];
assertEquals(RaceLogRaceStatus.RUNNING, raceState.getStatus());
assertEquals(analyzeAndUpdateCachedRaceStatusCalls[0], analyzeAndUpdateCachedRaceStatusCallsBefore);
}
@Test
@@ -141,7 +199,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event2.getNextStatus(), analyzer.analyze());
assertEquals(event2.getNextStatus(), analyzer.analyze().getA());
}
private static RaceLogStartTimeEvent createStartTimeEvent(long startTimeAsMillis, boolean isGreater) {
@@ -155,10 +213,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
}
private static RaceLogStartTimeEvent createStartTimeEvent(TimePoint startTime) {
RaceLogStartTimeEvent event = createEvent(RaceLogStartTimeEvent.class, 1);
when(event.getStartTime()).thenReturn(startTime);
doAnswer(new StartTimeVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class));
return event;
return new RaceLogStartTimeEventImpl(startTime, new LogEventAuthorImpl("Author", /* priority */ 0), /* pass */ 0, startTime);
}
private static class StartTimeVisitorAnswer implements Answer<Void> {
@@ -140,33 +140,27 @@ public class RaceStateTest {
}
@Test
public void testAdvancePass() {
public void testAdvancePass() throws InterruptedException {
state.addChangedListener(listener);
int oldPassId = raceLog.getCurrentPassId();
state.setAdvancePass(mock(TimePoint.class));
assertEquals(oldPassId + 1, raceLog.getCurrentPassId());
verify(listener).onAdvancePass(state);
verifyNoMoreInteractions(listener);
}
@Test
public void testAbort() {
public void testAbort() throws InterruptedException {
state.addChangedListener(listener);
state.setAborted(mock(TimePoint.class), false, Flags.NONE);
// TODO: change test when interface is complete
verifyNoMoreInteractions(listener);
}
@Test
public void testGeneralRecall() {
public void testGeneralRecall() throws InterruptedException {
state.addChangedListener(listener);
state.setGeneralRecall(mock(TimePoint.class));
// TODO: change test when interface is complete
verifyNoMoreInteractions(listener);
}
@@ -174,14 +168,11 @@ public class RaceStateTest {
@Test
public void testInvalidateAfterAdvancePass() throws InterruptedException {
state.addChangedListener(listener);
state.forceNewStartTime(nowMock, new MillisecondsTimePoint(1));
Thread.sleep(100);
state.setFinishedTime(new MillisecondsTimePoint(10));
state.setCourseDesign(nowMock, mock(CourseBase.class), CourseDesignerMode.ADMIN_CONSOLE);
state.setAdvancePass(mock(TimePoint.class));
assertNull(state.getStartTime());
assertEquals(RaceLogRaceStatus.UNSCHEDULED, state.getStatus());
verify(listener, times(1)).onAdvancePass(state);
@@ -1,5 +1,9 @@
package com.sap.sailing.domain.abstractlog.race.analyzing.impl;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
@@ -10,11 +14,19 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventCompar
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet;
public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
/**
* Returns the status and the clock {@link TimePoint} at which it was calculated. That time point is obtained
* from the {@link Clock} passed to this analyzer at construction time. It is queried at the time the analysis
* takes place ({@link #performAnalysis()}.
*
* @author Axel Uhl (d043530)
*
*/
public class RaceStatusAnalyzer extends RaceLogAnalyzer<Pair<RaceLogRaceStatus, TimePoint>> {
public interface Clock {
TimePoint now();
}
@@ -24,26 +36,27 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
public TimePoint now() {
return MillisecondsTimePoint.now();
}
}
private final EventDispatcher eventDispatcher;
private RaceLogResolver resolver;
private final RaceLogResolver resolver;
private final Clock clock;
private final ReadonlyRacingProcedure racingProcedure;
public RaceStatusAnalyzer(RaceLogResolver resolver, RaceLog raceLog, ReadonlyRacingProcedure racingProcedure) {
super(raceLog);
this.eventDispatcher = new EventDispatcher(new StandardClock(), racingProcedure);
this.resolver = resolver;
this(resolver, raceLog, new StandardClock(), racingProcedure);
}
public RaceStatusAnalyzer(RaceLogResolver resolver, RaceLog raceLog, Clock clock, ReadonlyRacingProcedure racingProcedure) {
super(raceLog);
this.eventDispatcher = new EventDispatcher(clock, racingProcedure);
this.resolver = resolver;
this.clock = clock;
this.racingProcedure = racingProcedure;
}
private static final Set<RaceLogRaceStatus> statusTypesToIgnoreIfEventNotValidYet = new HashSet<>(Arrays.asList(
new RaceLogRaceStatus[] { RaceLogRaceStatus.FINISHED, RaceLogRaceStatus.FINISHING }));
@Override
protected RaceLogRaceStatus performAnalysis() {
protected Pair<RaceLogRaceStatus, TimePoint> performAnalysis() {
ArrayListNavigableSet<RaceLogRaceStatusEvent> statusEvents = new ArrayListNavigableSet<>(
RaceLogRaceStatusEventComparator.INSTANCE);
for (RaceLogEvent event : getPassEvents()) {
@@ -51,21 +64,42 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
statusEvents.add((RaceLogRaceStatusEvent) event);
}
}
final TimePoint now = clock.now();
final EventDispatcher eventDispatcher = new EventDispatcher(now, racingProcedure);
Set<RaceLogRaceStatus> statusesToIgnore = new HashSet<>();
RaceLogRaceStatus result = RaceLogRaceStatus.UNSCHEDULED;
for (RaceLogRaceStatusEvent event : statusEvents.descendingSet()) {
event.accept(eventDispatcher);
return eventDispatcher.nextStatus;
// We need to read across a not yet valid FINISHED event and record that other FINISHED
// events need to be ignored; if we find a not yet valid FINISHING event, ignore further FINISHING
// events; but dispatch all other events (particularly start-related events) to the dispatcher:
final boolean dispatch;
if (statusTypesToIgnoreIfEventNotValidYet.contains(event.getNextStatus())) {
if (event.getLogicalTimePoint().after(now)) { // event not valid yet
statusesToIgnore.add(event.getNextStatus()); // but ignore events of same status with lesser relevance
dispatch = false;
} else {
dispatch = true;
}
} else {
dispatch = true;
}
if (dispatch) {
event.accept(eventDispatcher);
result = eventDispatcher.nextStatus;
break;
}
}
return RaceLogRaceStatus.UNSCHEDULED;
return new Pair<>(result, now);
}
private class EventDispatcher extends BaseRaceLogEventVisitor {
private final Clock clock;
private final TimePoint now;
private final ReadonlyRacingProcedure racingProcedure;
public RaceLogRaceStatus nextStatus;
public EventDispatcher(Clock clock, ReadonlyRacingProcedure racingProcedure) {
this.clock = clock;
public EventDispatcher(TimePoint now, ReadonlyRacingProcedure racingProcedure) {
this.now = now;
this.racingProcedure = racingProcedure;
this.nextStatus = RaceLogRaceStatus.UNKNOWN;
}
@@ -80,7 +114,6 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
if (startTime == null) {
nextStatus = RaceLogRaceStatus.PRESCHEDULED;
} else {
TimePoint now = clock.now();
if (racingProcedure.isStartphaseActive(startTime, now)) {
nextStatus = RaceLogRaceStatus.STARTPHASE;
} else if (now.before(startTime)) {
@@ -1,12 +1,10 @@
package com.sap.sailing.domain.abstractlog.race.state;
import java.util.Collection;
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateEvents;
public interface RaceStateEventScheduler {
void scheduleStateEvents(Collection<RaceStateEvent> stateEvents);
void scheduleStateEvents(Iterable<RaceStateEvent> stateEvents);
void unscheduleStateEvent(RaceStateEvents stateEventName);
@@ -55,10 +55,6 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
private final AbstractLogEventAuthor author;
public static ReadonlyRaceState create(RaceLogResolver raceLogResolver, RaceLog raceLog) {
return create(raceLogResolver, raceLog, /* forRaceLogIdentifier */ null, Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap());
}
/**
* Creates a {@link RaceState} with the initial racing procedure type set to a fallback value and an empty configuration.
*/
@@ -82,7 +78,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
private RaceStateImpl(RaceLogResolver raceLogResolver, RaceLog raceLog, AbstractLogEventAuthor author, RaceStatusAnalyzer.Clock analyzersClock,
RacingProcedureFactory procedureFactory) {
super(raceLogResolver, raceLog, /* forRaceLogIdentifier */ null, analyzersClock, procedureFactory,
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap(), /* update */ true);
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap());
this.author = author;
}
@@ -2,6 +2,8 @@ package com.sap.sailing.domain.abstractlog.race.state.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
@@ -44,6 +46,9 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.util.WeakIdentityHashMap;
/**
* Implementation of {@link ReadonlyRaceState}. Use the static factory methods to instantiate your race state.
@@ -55,11 +60,48 @@ import com.sap.sse.common.Util;
* See {@link ReadonlyRaceStateImpl#update()} for the core of the algorithm.
*/
public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedListener {
public static ReadonlyRaceState create(RaceLogResolver raceLogResolver, RaceLog raceLog) {
return create(raceLogResolver, raceLog, /* forRaceLogIdentifier */null,
private static final WeakIdentityHashMap<RaceLog, WeakIdentityHashMap<RaceLogResolver, ReadonlyRaceState>> raceStateCache =
new WeakIdentityHashMap<>();
/**
* Obtains a race state for the combination of {@code raceLog} and {@code raceLogResolver}. The result is
* cache using weak references to the race log and the race log resolver for quick retrieval upon equal
* requests, saving memory, saving listeners and thereby avoiding more synchronization issues.
*
* See also bug4704.
*/
public static ReadonlyRaceState getOrCreate(RaceLogResolver raceLogResolver, RaceLog raceLog) {
return getOrCreate(raceLogResolver, raceLog, /* forRaceLogIdentifier */null,
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState> emptyMap());
}
public static ReadonlyRaceState getOrCreate(RaceLogResolver raceLogResolver, RaceLog raceLog,
SimpleRaceLogIdentifier forRaceLogIdentifier,
Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
WeakIdentityHashMap<RaceLogResolver, ReadonlyRaceState> raceStatesForRaceLog;
ReadonlyRaceState result = null;
synchronized (raceStateCache) {
raceStatesForRaceLog = raceStateCache.get(raceLog);
if (raceStatesForRaceLog == null) {
raceStatesForRaceLog = new WeakIdentityHashMap<>();
raceStateCache.put(raceLog, raceStatesForRaceLog);
} else {
result = raceStatesForRaceLog.get(raceLogResolver);
}
if (result == null) {
result = createInternal(raceLogResolver, raceLog, forRaceLogIdentifier, dependentRaceStates);
raceStatesForRaceLog.put(raceLogResolver, result);
}
}
return result;
}
private static final ReadonlyRaceState createInternal(RaceLogResolver raceLogResolver, RaceLog raceLog,
SimpleRaceLogIdentifier forRaceLogIdentifier,
Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
return create(raceLogResolver, raceLog, forRaceLogIdentifier, dependentRaceStates);
}
/**
* Creates a {@link ReadonlyRaceState}.
@@ -109,7 +151,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private RaceStateEventScheduler scheduler;
/**
* We need one for each change in {@link RacingProcedureType}.
* We need one for each change in {@link RacingProcedureType}. The analyzer uses the {@link #statusAnalyzerClock}
* as its time source.
*/
private RaceStatusAnalyzer statusAnalyzer;
private final RacingProcedureTypeAnalyzer racingProcedureAnalyzer;
@@ -138,6 +181,22 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private RacingProcedureType cachedRacingProcedureTypeNoFallback;
private RaceLogRaceStatus cachedRaceStatus;
/**
* The time taken from {@link #statusAnalyzerClock} when the race status was last updated. Set to a valid
* value whenever {@link #cachedRaceStatus} is set.
*/
private TimePoint clockTimePointWhenCachedRaceStatusWasSet;
/**
* Time points determined during {@link #updateCachedRaceStatusAndValidity(RaceLogRaceStatus)} at which
* the race status may change. If the time range between {@link #clockTimePointWhenCachedRaceStatusWasSet} and
* the current {@link #statusAnalyzerClock clock} time point spans any of these, the {@link #cachedRaceStatus}
* is to be considered invalid and needs to be re-calculated. Set to a valid but possibly empty iterable
* whenever {@link #cachedRaceStatus} is set.
*/
private Iterable<TimePoint> timePointsWhenRaceStatusMayChange;
private int cachedPassId;
private StartTimeFinderResult cachedStartTimeFinderResult;
private TimePoint cachedFinishingTime;
@@ -157,7 +216,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
SimpleRaceLogIdentifier forRaceLogIdentifier, RacingProcedureFactory procedureFactory,
Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
this(raceLogResolver, raceLog, forRaceLogIdentifier, new RaceStatusAnalyzer.StandardClock(),
procedureFactory, dependentRaceStates, /* update */true);
procedureFactory, dependentRaceStates);
}
/**
@@ -166,14 +225,11 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
* new race state. This can help avoid endless recursions because this race state can be passed along
* together with the identifier so that when again along a dependent call chain a race state is required
* for that race log identifier, this one can be used.
* @param update
* if <code>true</code>, the race state will be updated whenever the underlying <code>raceLog</code>
* changes.
*/
protected ReadonlyRaceStateImpl(RaceLogResolver raceLogResolver, RaceLog raceLog,
SimpleRaceLogIdentifier forRaceLogIdentifier, Clock analyzersClock,
RacingProcedureFactory procedureFactory,
final Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates, boolean update) {
final Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
this.raceLog = raceLog;
this.raceLogResolver = raceLogResolver;
this.procedureFactory = procedureFactory;
@@ -295,9 +351,30 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
@Override
public RaceLogRaceStatus getStatus() {
final TimePoint timePointForAnalysis = statusAnalyzerClock.now();
final TimeRange timeRangeSinceLastStatusUpdate = new TimeRangeImpl(clockTimePointWhenCachedRaceStatusWasSet, timePointForAnalysis);
if (overlapsTimePointForPotentialStatusChange(timeRangeSinceLastStatusUpdate)) {
analyzeAndUpdateCachedRaceStatus();
}
return cachedRaceStatus;
}
/**
* Finds out whether any of the time points in {@link #timePointsWhenRaceStatusMayChange} falls into the time range
* provided. In this case we assume that a race status update may have happened between the time point when the
* {@link #cachedRaceStatus} was updated last and the current request time point, forming the time range provided
* as parameter here.
*/
private boolean overlapsTimePointForPotentialStatusChange(TimeRange timeRangeSinceLastStatusUpdate) {
for (final TimePoint e : timePointsWhenRaceStatusMayChange) {
if (timeRangeSinceLastStatusUpdate.includes(e)) {
return true;
}
}
return false;
}
@Override
public TimePoint getStartTime() {
return cachedStartTimeFinderResult.getStartTime();
@@ -404,7 +481,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
} else {
RaceLog resolvedRaceLog = raceLogResolver.resolve(dependentOnRaceIdentifier);
if (resolvedRaceLog != null) {
raceStateToObserve = ReadonlyRaceStateImpl.create(raceLogResolver, resolvedRaceLog,
raceStateToObserve = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, resolvedRaceLog,
dependentOnRaceIdentifier, dependentRaceStates);
raceStateToObserve.addChangedListener(raceStateToObserveListener);
}
@@ -433,13 +510,6 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
recreateRacingProcedure(dependentRaceStates);
changedListeners.onRacingProcedureChanged(this);
}
RaceLogRaceStatus status = statusAnalyzer.analyze();
if (!Util.equalsWithNull(cachedRaceStatus, status)) {
cachedRaceStatus = status;
changedListeners.onStatusChanged(this);
}
int passId = raceLog.getCurrentPassId();
if (cachedPassId != passId) {
cachedPassId = passId;
@@ -448,38 +518,33 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
cachedRacingProcedureType = null;
cachedRacingProcedureTypeNoFallback = null;
}
StartTimeFinderResult startTimeFinderResult = startTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedStartTimeFinderResult, startTimeFinderResult)) {
cachedStartTimeFinderResult = startTimeFinderResult;
changedListeners.onStartTimeChanged(this);
}
adjustObserverForRelativeStartTime(startTimeFinderResult, dependentRaceStates);
TimePoint finishingTime = finishingTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedFinishingTime, finishingTime)) {
cachedFinishingTime = finishingTime;
changedListeners.onFinishingTimeChanged(this);
}
TimePoint finishedTime = finishedTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedFinishedTime, finishedTime)) {
cachedFinishedTime = finishedTime;
changedListeners.onFinishedTimeChanged(this);
}
analyzeAndUpdateCachedRaceStatus(); // do this after start and finishing times have been updated
TimeRange protest = protestTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedProtest, protest)) {
cachedProtest = protest;
changedListeners.onProtestTimeChanged(this);
}
CompetitorResults positionedCompetitors = finishPositioningListAnalyzer.analyze();
if (!Util.equalsWithNull(cachedPositionedCompetitors, positionedCompetitors)) {
cachedPositionedCompetitors = positionedCompetitors;
changedListeners.onFinishingPositioningsChanged(this);
}
CompetitorResults confirmedPositionedCompetitors = confirmedFinishPositioningListAnalyzer.analyze();
if (!Util.equalsWithNull(cachedConfirmedPositionedCompetitors, confirmedPositionedCompetitors)) {
cachedConfirmedPositionedCompetitors = confirmedPositionedCompetitors;
@@ -487,13 +552,11 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
changedListeners.onFinishingPositionsConfirmed(this);
}
}
CourseBase courseDesign = courseDesignerAnalyzer.analyze();
if (!Util.equalsWithNull(cachedCourseDesign, courseDesign)) {
cachedCourseDesign = courseDesign;
changedListeners.onCourseDesignChanged(this);
}
Wind windFix = lastWindFixAnalyzer.analyze();
if (!Util.equalsWithNull(cachedWindFix, windFix)) {
cachedWindFix = windFix;
@@ -501,6 +564,46 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
}
}
/**
* This method has protected visibility (and not private) to allow instrumenting by test cases
*/
protected void analyzeAndUpdateCachedRaceStatus() {
Pair<RaceLogRaceStatus, TimePoint> statusAndTimePoint = statusAnalyzer.analyze();
if (!Util.equalsWithNull(cachedRaceStatus, statusAndTimePoint)) {
updateCachedRaceStatusAndValidity(statusAndTimePoint);
}
}
/**
* Updates the {@link #cachedRaceStatus} and calculates and remembers its validity, based on
* {@link ReadonlyRacingProcedure#createStartStateEvents(TimePoint)} and which of those have already
* passed at the current clock time and which ones are still lying ahead.
*/
private void updateCachedRaceStatusAndValidity(Pair<RaceLogRaceStatus, TimePoint> statusAndTimePoint) {
final RaceLogRaceStatus oldRaceStatus = cachedRaceStatus;
cachedRaceStatus = statusAndTimePoint.getA();
clockTimePointWhenCachedRaceStatusWasSet = statusAndTimePoint.getB();
if (getStartTimeFinderResult() == null || getStartTime() == null) {
timePointsWhenRaceStatusMayChange = Collections.emptyList();
} else {
final TimePoint startTime = getStartTime();
List<TimePoint> newTimePointsWhenRaceStatusMayChange = new LinkedList<>();
for (final RaceStateEvent e : racingProcedure.createStartStateEvents(startTime)) {
newTimePointsWhenRaceStatusMayChange.add(e.getTimePoint());
}
if (getFinishingTime() != null) {
newTimePointsWhenRaceStatusMayChange.add(getFinishingTime());
}
if (getFinishedTime() != null) {
newTimePointsWhenRaceStatusMayChange.add(getFinishedTime());
}
timePointsWhenRaceStatusMayChange = newTimePointsWhenRaceStatusMayChange;
}
if (!Util.equalsWithNull(oldRaceStatus, cachedRaceStatus)) {
changedListeners.onStatusChanged(this);
}
}
private void recreateRacingProcedure(Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
if (racingProcedure != null) {
removeChangedListener(racingProcedure);
@@ -509,10 +612,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
racingProcedure = procedureFactory.createRacingProcedure(cachedRacingProcedureType, raceLog, raceLogResolver);
racingProcedure.setStateEventScheduler(scheduler);
addChangedListener(racingProcedure);
statusAnalyzer = new RaceStatusAnalyzer(raceLogResolver, raceLog, statusAnalyzerClock, racingProcedure);
// let's do an update because status might have changed with new procedure
update(dependentRaceStates);
}
}
@@ -4,6 +4,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogAnalyzer;
import com.sap.sailing.domain.abstractlog.race.state.RaceState;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateChangedListener;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventProcessor;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventScheduler;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
@@ -122,6 +123,13 @@ public interface ReadonlyRacingProcedure extends RaceStateEventProcessor, RaceSt
RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime,
RacingProcedurePrerequisite.FulfillmentFunction function);
/**
* Delivers the time points and types of the events around the start, each optionally
* leading to a change in race status.
*/
Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime);
/**
* This method is called by the framework. You don't need to call it.
*/
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.basic.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -66,7 +65,7 @@ public class BasicRacingProcedureImpl extends BaseRacingProcedure {
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent>asList(new RaceStateEventImpl(startTime, RaceStateEvents.START));
}
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ess.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -64,7 +63,7 @@ public class ESSRacingProcedureImpl extends BaseRacingProcedure implements ESSRa
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(new RaceStateEventImpl(startTime.minus(startPhaseAPDownInterval),
RaceStateEvents.ESS_AP_DOWN), new RaceStateEventImpl(startTime.minus(startPhaseESSThreeUpInterval),
RaceStateEvents.ESS_THREE_UP), new RaceStateEventImpl(startTime.minus(startPhaseESSTwoUpInterval),
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.impl;
import java.util.Arrays;
import java.util.Collection;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -119,7 +118,7 @@ public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(
new RaceStateEventImpl(startTime.minus(startPhaseClassOverGolfUpInterval),
RaceStateEvents.GATE_CLASS_OVER_GOLF_UP),
@@ -276,8 +276,6 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
return configuration;
}
protected abstract Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime);
@Override
public void onStartTimeChanged(ReadonlyRaceState state) {
getChangedListeners().onActiveFlagsChanged(this);
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.league.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -81,7 +80,7 @@ public class LeagueRacingProcedureImpl extends BaseRacingProcedure implements Le
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
// Remark: We are reusing here CLASS_UP, STARTMODE_UP and STARTMODE_DOWN from the RRS26 procedure
// The question is why there are no generic CLASS_UP, STARTMODE_UP and STARTMODE_DOWN events
return Arrays.<RaceStateEvent> asList(
@@ -28,17 +28,14 @@ public abstract class ConfigurableStartModeFlagRacingProcedureImpl extends BaseR
public ConfigurableStartModeFlagRacingProcedureImpl(RaceLog raceLog, AbstractLogEventAuthor author,
ConfigurableStartModeFlagRacingProcedureConfiguration configuration, RaceLogResolver raceLogResolver) {
super(raceLog, author, configuration, raceLogResolver);
RacingProcedureTypeAnalyzer procedureAnalyzer = new RacingProcedureTypeAnalyzer(raceLog);
if (configuration.getStartModeFlags() != null) {
this.startModeFlagAnalyzer = new StartModeFlagFinder(procedureAnalyzer, raceLog, configuration.getStartModeFlags());
} else {
this.startModeFlagAnalyzer = new StartModeFlagFinder(procedureAnalyzer, raceLog, getDefaultStartModeFlags());
}
this.cachedStartmodeFlag = getDefaultStartMode();
this.startmodeFlagHasBeenSet = false;
update();
}
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.line.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -64,7 +63,7 @@ public class RRS26RacingProcedureImpl extends ConfigurableStartModeFlagRacingPro
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(
new RaceStateEventImpl(startTime.minus(startPhaseClassUpInterval), RaceStateEvents.RRS26_CLASS_UP),
new RaceStateEventImpl(startTime.minus(startPhaseStartModeUpInterval), RaceStateEvents.RRS26_STARTMODE_UP),
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.line.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -69,7 +68,7 @@ public class SWCRacingProcedureImpl extends ConfigurableStartModeFlagRacingProce
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(
new RaceStateEventImpl(startTime.minus(CLASS_AND_STARTMODE_UP_INTERVAL), RaceStateEvents.SWC_CLASS_AND_STARTMODE_UP),
new RaceStateEventImpl(startTime.minus(FIVE_MINUTES_FLAG_UP_INTERVAL), RaceStateEvents.SWC_FIVE_UP),
@@ -2,7 +2,8 @@ package com.sap.sailing.domain.swisstimingadapter.persistence.impl;
public enum FieldNames {
// SwissTiming configuration parameters:
ST_CONFIG_NAME, ST_CONFIG_HOSTNAME, ST_CONFIG_PORT, ST_CONFIG_JSON_URL, ST_ARCHIVE_JSON_URL,
ST_CONFIG_NAME, ST_CONFIG_HOSTNAME, ST_CONFIG_PORT, ST_CONFIG_JSON_URL, ST_CONFIG_UPDATE_URL,
ST_CONFIG_UPDATE_USERNAME, ST_CONFIG_UPDATE_PASSWORD, ST_ARCHIVE_JSON_URL,
// last message count field:
LAST_MESSAGE_COUNT,
@@ -37,8 +37,8 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
DBCollection stConfigs = database.getCollection(CollectionNames.SWISSTIMING_CONFIGURATIONS.name());
for (DBObject o : stConfigs.find()) {
SwissTimingConfiguration stConfig = loadSwissTimingConfiguration(o);
// the old swisstiming config was not based on a json URL -> ignore such configs
if(stConfig.getJsonURL() != null) {
// the old SwissTiming configuration was not based on a JSON URL -> ignore such configurations
if (stConfig.getJsonURL() != null) {
result.add(stConfig);
}
}
@@ -57,8 +57,10 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
String jsonURL = (String) object.get(FieldNames.ST_CONFIG_JSON_URL.name());
String hostname = (String) object.get(FieldNames.ST_CONFIG_HOSTNAME.name());
Integer port = (Integer) object.get(FieldNames.ST_CONFIG_PORT.name());
return swissTimingFactory.createSwissTimingConfiguration(name, jsonURL, hostname, port);
String updateURL = (String) object.get(FieldNames.ST_CONFIG_UPDATE_URL.name());
String updateUsername = (String) object.get(FieldNames.ST_CONFIG_UPDATE_USERNAME.name());
String updatePassword = (String) object.get(FieldNames.ST_CONFIG_UPDATE_PASSWORD.name());
return swissTimingFactory.createSwissTimingConfiguration(name, jsonURL, hostname, port, updateURL, updateUsername, updatePassword);
}
@Override
@@ -96,6 +98,9 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
result.put(FieldNames.ST_CONFIG_JSON_URL.name(), swissTimingConfiguration.getJsonURL());
result.put(FieldNames.ST_CONFIG_HOSTNAME.name(), swissTimingConfiguration.getHostname());
result.put(FieldNames.ST_CONFIG_PORT.name(), swissTimingConfiguration.getPort());
result.put(FieldNames.ST_CONFIG_UPDATE_URL.name(), swissTimingConfiguration.getUpdateURL());
result.put(FieldNames.ST_CONFIG_UPDATE_USERNAME.name(), swissTimingConfiguration.getUpdateUsername());
result.put(FieldNames.ST_CONFIG_UPDATE_PASSWORD.name(), swissTimingConfiguration.getUpdatePassword());
stConfigCollection.insert(result);
}
@@ -54,6 +54,9 @@ public class SwissTimingConnectivityParamsHandler extends AbstractRaceTrackingCo
private static final String PORT = "port";
private static final String BOAT_CLASS_NAME = "boatClassName";
private static final String HOSTNAME = "hostname";
private static final String UPDATE_URL = "updateURL";
private static final String UPDATE_USERNAME = "updateUsername";
private static final String UPDATE_PASSWORD = "updatePassword";
private final RaceLogStore raceLogStore;
private final RegattaLogStore regattaLogStore;
private final DomainFactory domainFactory;
@@ -78,6 +81,9 @@ public class SwissTimingConnectivityParamsHandler extends AbstractRaceTrackingCo
result.put(START_LIST, createStartListDBObject(stParams));
result.put(DELAY_TO_LIVE_IN_MILLIS, stParams.getDelayToLiveInMillis());
result.put(USE_INTERNAL_MARK_PASSING_ALGORITHM, stParams.isUseInternalMarkPassingAlgorithm());
result.put(UPDATE_URL, stParams.getUpdateURL());
result.put(UPDATE_USERNAME, stParams.getUpdateUsername());
result.put(UPDATE_PASSWORD, stParams.getUpdatePassword());
addWindTrackingParameters(stParams, result);
return result;
}
@@ -175,7 +181,10 @@ public class SwissTimingConnectivityParamsHandler extends AbstractRaceTrackingCo
map.get(START_LIST) == null ? null : createStartListFromDBObject((String) map.get(RACE_ID), (BasicDBList) map.get(START_LIST)),
((Number) map.get(DELAY_TO_LIVE_IN_MILLIS)).longValue(),
swissTimingFactory, domainFactory, raceLogStore, regattaLogStore,
(boolean) map.get(USE_INTERNAL_MARK_PASSING_ALGORITHM), isTrackWind(map), isCorrectWindDirectionByMagneticDeclination(map));
(boolean) map.get(USE_INTERNAL_MARK_PASSING_ALGORITHM), isTrackWind(map), isCorrectWindDirectionByMagneticDeclination(map),
(String) map.get(UPDATE_URL),
(String) map.get(UPDATE_USERNAME),
(String) map.get(UPDATE_PASSWORD));
}
@Override
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.swisstimingadapter;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
@@ -18,6 +19,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -70,10 +72,17 @@ public interface DomainFactory {
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID,
String raceName, String raceDescription, BoatClass boatClass, StartList startList,
long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination);
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword);
ControlPoint getOrCreateControlPoint(String description, Iterable<Serializable> deviceIds, MarkType markType);
RaceDefinition createRaceDefinition(Regatta regatta, String swissTimingRaceID, Map<Competitor, Boat> competitorsAndBoats,
List<ControlPoint> courseDefinition, String raceName, String raceIdForRaceDefinition);
/**
* Adds update handlers that forward events about a race, such as start time changes, course changes
* or postponents, to an update URL using specific REST requests.
*/
void addUpdateHandlers(String updateURL, String username, String password, Serializable eventId,
RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) throws URISyntaxException;
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.swisstimingadapter;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
@@ -16,9 +17,9 @@ public interface SailMasterListener {
void receivedClockAtMark(String raceID, List<Util.Triple<Integer, TimePoint, String>> markIndicesTimePointsAndBoatIDs);
void receivedStartList(String raceID, StartList startList);
void receivedStartList(String raceID, StartList startList) throws URISyntaxException;
void receivedCourseConfiguration(String raceID, Course course);
void receivedCourseConfiguration(String raceID, Course course) throws URISyntaxException;
void receivedAvailableRaces(Iterable<Race> races);
@@ -26,12 +26,10 @@ public interface SwissTimingAdapter {
* is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used
* to add the races to. Otherwise, a default regatta as described above will be created and used.
* @param useInternalMarkPassingAlgorithm use our own instead of the SwissTiming-provided mark passing / split times
* @param trackWind TODO
* @param correctWindDirectionByMagneticDeclination TODO
*/
RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID,
String raceName, String raceDescription, BoatClass boatClass, String hostname, int port,
StartList startList, RaceLogStore logStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination)
StartList startList, RaceLogStore logStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword)
throws InterruptedException, UnknownHostException, IOException, ParseException, Exception;
StartList readStartListForRace(String raceId, RegattaResults regattaResults);
@@ -14,4 +14,10 @@ public interface SwissTimingConfiguration {
String getHostname();
Integer getPort();
String getUpdateURL();
String getUpdateUsername();
String getUpdatePassword();
}
@@ -49,20 +49,14 @@ public interface SwissTimingFactory {
SailMasterTransceiver createSailMasterTransceiver();
SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port);
SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword);
SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription,
BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams) throws InterruptedException, UnknownHostException,
IOException, ParseException;
SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException;
RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription,
BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis,
WindStore windStore, boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams)
RaceTracker createRaceTracker(Regatta regatta, WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams)
throws UnknownHostException, InterruptedException, IOException, ParseException;
Race createRace(String raceId, String raceName, String description, BoatClass boatClass);
@@ -1,6 +1,8 @@
package com.sap.sailing.domain.swisstimingadapter.impl;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
@@ -50,9 +52,14 @@ import com.sap.sailing.domain.swisstimingadapter.RaceType;
import com.sap.sailing.domain.swisstimingadapter.RaceType.OlympicRaceCode;
import com.sap.sailing.domain.swisstimingadapter.StartList;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler;
import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler;
import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler;
import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler;
import com.sap.sse.common.Named;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -402,9 +409,29 @@ public class DomainFactoryImpl implements DomainFactory {
String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList,
long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm,
boolean trackWind, boolean correctWindDirectionByMagneticDeclination) {
boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL,
String updateUsername, String updatePassword) {
return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, raceName, raceDescription,
boatClass, startList, delayToLiveInMillis, swissTimingFactory, domainFactory, raceLogStore,
regattaLogStore, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination);
regattaLogStore, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination,
updateURL, updateUsername, updatePassword);
}
@Override
public void addUpdateHandlers(String updateURL, String username, String password, Serializable eventId,
RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) throws URISyntaxException {
final URI updateURI = new URI(updateURL);
CourseDesignUpdateHandler courseDesignHandler = new CourseDesignUpdateHandler(
updateURI, username, password, eventId, raceDefinition.getId());
StartTimeUpdateHandler startTimeHandler = new StartTimeUpdateHandler(
updateURI, username, password, eventId,
raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta());
RaceAbortedHandler raceAbortedHandler = new RaceAbortedHandler(
updateURI, username, password, eventId,
raceDefinition.getId());
final FinishTimeUpdateHandler finishTimeUpdateHandler = new FinishTimeUpdateHandler(updateURI, username, password, eventId,
raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta());
baseDomainFactory.addUpdateHandlers(trackedRace, courseDesignHandler, startTimeHandler, raceAbortedHandler,
finishTimeUpdateHandler);
}
}
@@ -69,12 +69,13 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter {
public RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID,
String raceName, String raceDescription, BoatClass boatClass, String hostname, int port,
StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds,
boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) throws Exception {
boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination,
String updateURL, String updateUsername, String updatePassword) throws Exception {
return trackerManager.addRace(regattaToAddTo,
swissTimingDomainFactory.createTrackingConnectivityParameters(hostname, port, raceID, raceName,
raceDescription, boatClass, startList, DEFAULT_SWISSTIMING_LIVE_DELAY_IN_MILLISECONDS,
swissTimingFactory, swissTimingDomainFactory, raceLogStore, regattaLogStore,
useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination),
useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, updateURL, updateUsername, updatePassword),
timeoutInMilliseconds);
}
@@ -118,9 +119,9 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter {
Person person = persons.get(crewMember.getPersonID());
crew.add(new CrewMemberImpl(
person.getGivenName() + " " + person.getFamilyName(),
person.getNOC().name(), crewMember.getPosition().name()));
(person.getNOC()==null)?null:(person.getNOC().name()), crewMember.getPosition().name()));
}
String nationality = team.getNOC().name();
String nationality = team.getNOC()==null?null:team.getNOC().name();
CompetitorWithID competitor = new CompetitorWithID(team.getTeamID(),
boat.getSailNumber(), nationality, team.getTeamName(), crew);
competitors.add(competitor);
@@ -7,13 +7,19 @@ public class SwissTimingConfigurationImpl implements SwissTimingConfiguration {
private final String jsonURL;
private final String hostname;
private final Integer port;
private final String updateURL;
private final String updateUsername;
private final String updatePassword;
public SwissTimingConfigurationImpl(String name, String jsonURL, String hostname, Integer port) {
public SwissTimingConfigurationImpl(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) {
super();
this.name = name;
this.jsonURL = jsonURL;
this.hostname = hostname;
this.port = port;
this.updateURL = updateURL;
this.updateUsername = updateUsername;
this.updatePassword = updatePassword;
}
@Override
@@ -36,4 +42,15 @@ public class SwissTimingConfigurationImpl implements SwissTimingConfiguration {
return port;
}
public String getUpdateURL() {
return updateURL;
}
public String getUpdateUsername() {
return updateUsername;
}
public String getUpdatePassword() {
return updatePassword;
}
}
@@ -17,7 +17,6 @@ import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector;
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage;
import com.sap.sailing.domain.swisstimingadapter.SailMasterTransceiver;
import com.sap.sailing.domain.swisstimingadapter.StartList;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
@@ -83,32 +82,27 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory {
}
@Override
public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port) {
return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port);
public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname,
Integer port, String updateURL, String updateUsername, String updatePassword) {
return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port, updateURL, updateUsername, updatePassword);
}
@Override
public SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription,
BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams)
public SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException {
return new SwissTimingRaceTrackerImpl(raceID, raceName, raceDescription, boatClass, hostname, port, startList,
raceLogStore, regattaLogStore, windStore, domainFactory, this,
trackedRegattaRegistry, raceLogResolver, delayToLiveInMillis, useInternalMarkPassingAlgorithm, connectivityParams);
return new SwissTimingRaceTrackerImpl(raceLogStore, regattaLogStore, windStore, domainFactory, this,
trackedRegattaRegistry, raceLogResolver, connectivityParams);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription,
BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis,
WindStore windStore, boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory,
public RaceTracker createRaceTracker(Regatta regatta, WindStore windStore, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams)
throws UnknownHostException, InterruptedException, IOException, ParseException {
return new SwissTimingRaceTrackerImpl(regatta, raceID, raceName, raceDescription, boatClass, hostname, port,
startList, windStore, domainFactory, this, trackedRegattaRegistry, raceLogStore, regattaLogStore,
raceLogResolver, delayToLiveInMillis, useInternalMarkPassingAlgorithm, connectivityParams);
return new SwissTimingRaceTrackerImpl(regatta, windStore, domainFactory, this, trackedRegattaRegistry,
raceLogStore, regattaLogStore, raceLogResolver, connectivityParams);
}
@Override
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.swisstimingadapter.impl;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.Collection;
@@ -63,6 +64,7 @@ import com.sap.sailing.domain.tracking.TrackingDataLoader;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
import com.sap.sailing.domain.tracking.impl.UpdateHandler;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -110,23 +112,30 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
private final TMDMessageQueue tmdMessageQueue;
private final RaceLogResolver raceLogResolver;
/**
* If set to a non-{@code null}, non-{@link String#isEmpty() empty} value, updates about start time changes, course
* changes, etc. will be sent as REST requests to this URL, using the {@link #updateUsername} /
* {@link #updatePassword} as credentials.
*/
private final String updateURL;
private final String updateUsername;
private final String updatePassword;
protected SwissTimingRaceTrackerImpl(String raceID, String raceName, String raceDescription, BoatClass boatClass,
String hostname, int port, StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
protected SwissTimingRaceTrackerImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, DomainFactory domainFactory, SwissTimingFactory factory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, long delayToLiveInMillis,
boolean useInternalMarkPassingAlgorithm, SwissTimingTrackingConnectivityParameters connectivityParams) throws InterruptedException,
UnknownHostException, IOException, ParseException {
this(/* regatta */ null, raceID, raceName, raceDescription, boatClass, hostname, port, startList, windStore,
domainFactory, factory, trackedRegattaRegistry, raceLogStore, regattaLogStore, raceLogResolver,
delayToLiveInMillis, useInternalMarkPassingAlgorithm, connectivityParams);
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver,
SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException {
this(/* regatta */ null, windStore, domainFactory, factory, trackedRegattaRegistry, raceLogStore, regattaLogStore, raceLogResolver, connectivityParams);
}
protected SwissTimingRaceTrackerImpl(Regatta regatta, String raceID, String raceName, String raceDescription,
BoatClass boatClass, String hostname, int port, StartList startList, WindStore windStore,
DomainFactory domainFactory, SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, RaceLogResolver raceLogResolver,
long delayToLiveInMillis, boolean useInternalMarkPassingAlgorithm, SwissTimingTrackingConnectivityParameters connectivityParams)
protected SwissTimingRaceTrackerImpl(Regatta regatta, WindStore windStore, DomainFactory domainFactory,
SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, RaceLogResolver raceLogResolver,
SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException {
super(connectivityParams);
this.raceLogResolver = raceLogResolver;
@@ -134,29 +143,35 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
final Regatta effectiveRegatta;
// Try to find a pre-associated event based on the Race ID
if (regatta == null) {
effectiveRegatta = trackedRegattaRegistry.getRememberedRegattaForRace(raceID);
effectiveRegatta = trackedRegattaRegistry.getRememberedRegattaForRace(connectivityParams.getRaceID());
} else {
effectiveRegatta = regatta;
}
// if regatta is still null, no previous assignment of any of the races in this TracTrac event to a Regatta was
// found; in this case, create a default regatta based on the TracTrac event data
this.regatta = effectiveRegatta == null ? domainFactory.getOrCreateDefaultRegatta(raceLogStore, regattaLogStore,
raceID, boatClass, trackedRegattaRegistry) : effectiveRegatta;
this.connector = factory.getOrCreateSailMasterConnector(hostname, port, raceID, raceName, raceDescription, boatClass);
connectivityParams.getRaceID(), connectivityParams.getBoatClass(), trackedRegattaRegistry) : effectiveRegatta;
this.connector = factory.getOrCreateSailMasterConnector(connectivityParams.getHostname(), connectivityParams.getPort(),
connectivityParams.getRaceID(), connectivityParams.getRaceName(), connectivityParams.getRaceDescription(), connectivityParams.getBoatClass());
this.domainFactory = domainFactory;
this.raceID = raceID;
this.raceName = raceName;
this.startList = startList;
this.startListFromManage2Sail = startList != null;
this.raceDescription = raceDescription;
this.boatClass = boatClass;
this.raceID = connectivityParams.getRaceID();
// start out with an empty course, so we don't depend on receiving the CCG message before the timeout
this.course = new CourseImpl(this.raceID, /* start with empty marks list */ Collections.emptyList());
this.raceName = connectivityParams.getRaceName();
this.startList = connectivityParams.getStartList();
this.startListFromManage2Sail = connectivityParams.getStartList() != null;
this.raceDescription = connectivityParams.getRaceDescription();
this.boatClass = connectivityParams.getBoatClass();
this.windStore = windStore;
this.id = createID(raceID, hostname, port);
this.id = createID(connectivityParams.getRaceID(), connectivityParams.getHostname(), connectivityParams.getPort());
connector.addSailMasterListener(this);
trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(this.regatta);
this.delayToLiveInMillis = delayToLiveInMillis;
this.delayToLiveInMillis = connectivityParams.getDelayToLiveInMillis();
this.competitorsByBoatId = new HashMap<String, Competitor>();
this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm;
this.useInternalMarkPassingAlgorithm = connectivityParams.isUseInternalMarkPassingAlgorithm();
this.updateURL = connectivityParams.getUpdateURL();
this.updateUsername = connectivityParams.getUpdateUsername();
this.updatePassword = connectivityParams.getUpdatePassword();
}
@Override
@@ -406,7 +421,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
}
@Override
public void receivedStartList(String raceID, StartList startList) {
public void receivedStartList(String raceID, StartList startList) throws URISyntaxException {
// ignore STL messages if the startlist has been already provided by Manage2Sail
if (!startListFromManage2Sail && this.raceID.equals(raceID)) {
StartList oldStartList = this.startList;
@@ -435,7 +450,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
}
}
private void createRaceDefinition(Course course) {
private void createRaceDefinition(Course course) throws URISyntaxException {
assert this.raceID.equals(raceID);
assert startList != null;
assert course != null;
@@ -465,9 +480,20 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
}
}, useInternalMarkPassingAlgorithm, raceLogResolver,
/* Not needed because the RaceTracker is not active on a replica */ Optional.empty());
addUpdateHandlers();
notifyRaceCreationListeners();
logger.info("Created SwissTiming RaceDefinition and TrackedRace for "+race.getName());
}
/**
* If a non-{@code null}, non-{@link String#isEmpty() empty} {@link #updateURL} is set, adds {@link UpdateHandler}s
* to the race that will forward changes to the race such as start time changes or course changes to the URL as REST
* requests.
*/
private void addUpdateHandlers() throws URISyntaxException {
getDomainFactory().addUpdateHandlers(updateURL, updateUsername, updatePassword,
/* TODO using the regatta ID as the "eventId" for now... */ regatta.getId(), trackedRace.getRace(), trackedRace);
}
/**
* Checks if {@link #getRegatta()} still contains the {@link RaceDefinition} obtained when calling
@@ -481,7 +507,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
}
@Override
public void receivedCourseConfiguration(String raceID, Course course) {
public void receivedCourseConfiguration(String raceID, Course course) throws URISyntaxException {
Course oldCourse = this.course;
if (trackedRace == null) {
if (oldCourse == null && startList != null) {
@@ -31,11 +31,15 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack
private final long delayToLiveInMillis;
private final StartList startList;
private final boolean useInternalMarkPassingAlgorithm;
private final String updateURL;
private final String updateUsername;
private final String updatePassword;
public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName,
String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) {
RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind,
boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword) {
super(trackWind, correctWindDirectionByMagneticDeclination);
this.hostname = hostname;
this.port = port;
@@ -50,6 +54,9 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack
this.raceLogStore = raceLogStore;
this.regattaLogStore = regattaLogStore;
this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm;
this.updateURL = updateURL;
this.updateUsername = updateUsername;
this.updatePassword = updatePassword;
}
@Override
@@ -60,18 +67,15 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
RaceLogResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, long timeoutInMilliseconds) throws Exception {
return swissTimingFactory.createRaceTracker(raceID, raceName, raceDescription, boatClass, hostname, port,
startList, delayToLiveInMillis, raceLogStore, regattaLogStore, windStore,
useInternalMarkPassingAlgorithm,
domainFactory, trackedRegattaRegistry, raceLogResolver, this);
return swissTimingFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, domainFactory, trackedRegattaRegistry, raceLogResolver,
this);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
WindStore windStore, RaceLogResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, long timeoutInMilliseconds) throws Exception {
return swissTimingFactory.createRaceTracker(regatta, raceID, raceName, raceDescription, boatClass, hostname,
port, startList, delayToLiveInMillis, windStore, useInternalMarkPassingAlgorithm,
domainFactory, trackedRegattaRegistry, raceLogResolver, raceLogStore, regattaLogStore, this);
return swissTimingFactory.createRaceTracker(regatta, windStore, domainFactory, trackedRegattaRegistry, raceLogResolver, raceLogStore,
regattaLogStore, this);
}
@Override
@@ -92,6 +96,18 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack
return port;
}
public String getUpdateURL() {
return updateURL;
}
public String getUpdateUsername() {
return updateUsername;
}
public String getUpdatePassword() {
return updatePassword;
}
public String getRaceID() {
return raceID;
}
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogDependentStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogPassChangeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl;
import com.sap.sailing.domain.base.CompetitorWithBoat;
@@ -73,5 +74,8 @@ public class TrackedRaceStartTimeUpdateForDependentStartTimeTest extends TrackBa
assertEquals(r1StartTimeToSet, r1StartTime[0]);
assertNotNull(r2StartTime[0]);
assertEquals(r1StartTimeToSet.plus(startTimeDiff), r2StartTime[0]);
// bug 4708: test that r2's start time is reverted to null after r1 loses its start time
r1RaceLog.add(new RaceLogPassChangeEventImpl(MillisecondsTimePoint.now(), author, /* pass */ 1));
assertNull(r2StartTime[0]);
}
}
@@ -14,8 +14,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -73,6 +73,9 @@ import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler;
import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler;
import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.JSONService;
import com.sap.sailing.domain.tractracadapter.MetadataParser;
@@ -677,17 +680,16 @@ public class DomainFactoryImpl implements DomainFactory {
TracTracCourseDesignUpdateHandler courseDesignHandler = new TracTracCourseDesignUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId(), tractracRace, this);
trackedRace.addCourseDesignChangedListener(courseDesignHandler);
TracTracStartTimeUpdateHandler startTimeHandler = new TracTracStartTimeUpdateHandler(
StartTimeUpdateHandler startTimeHandler = new StartTimeUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta());
trackedRace.addStartTimeChangedListener(startTimeHandler);
TracTracRaceAbortedHandler raceAbortedHandler = new TracTracRaceAbortedHandler(
RaceAbortedHandler raceAbortedHandler = new RaceAbortedHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
trackedRace.addRaceAbortedListener(raceAbortedHandler);
trackedRace.addListener(new TracTracFinishTimeUpdateHandler(tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta()).getListener());
final FinishTimeUpdateHandler finishTimeUpdateHandler = new FinishTimeUpdateHandler(tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta());
baseDomainFactory.addUpdateHandlers(trackedRace, courseDesignHandler, startTimeHandler, raceAbortedHandler,
finishTimeUpdateHandler);
}
private DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race,
@@ -251,17 +251,19 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
}
}
private DynamicTrackedRace createTrackedRace(RaceDefinition race, Iterable<Sideline> sidelines, Consumer<DynamicTrackedRace> runAfterCreatingTrackedRace) {
DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines,
windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
private DynamicTrackedRace createTrackedRace(RaceDefinition race, Iterable<Sideline> sidelines,
Consumer<DynamicTrackedRace> runAfterCreatingTrackedRace) {
DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines, windStore,
delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass()
.getApproximateManeuverDurationInMilliseconds(),
raceDefinitionSetToUpdate, useInternalMarkPassingAlgorithm, raceLogResolver,
/* Not needed because the RaceTracker is not active on a replica */ Optional.empty());
if (runAfterCreatingTrackedRace != null) {
runAfterCreatingTrackedRace.accept(trackedRace);
runAfterCreatingTrackedRace.accept(trackedRace);
}
getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, tracTracPassword, race, trackedRace,
tractracRace);
getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername,
tracTracPassword, race, trackedRace, tractracRace);
return trackedRace;
}
@@ -1,86 +1,32 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.IOException;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Iterator;
import java.util.logging.Logger;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.ControlPointJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.GateJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJsonSerializer;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.event.IRace;
import com.tractrac.model.lib.api.route.IControl;
public class TracTracCourseDesignUpdateHandler extends UpdateHandler implements CourseDesignChangedListener {
private final static String ACTION = "update_course";
private final static Logger logger = Logger.getLogger(TracTracCourseDesignUpdateHandler.class.getName());
private final JsonSerializer<CourseBase> courseSerializer;
public class TracTracCourseDesignUpdateHandler extends CourseDesignUpdateHandler {
private final IRace tractracRace;
private final DomainFactory domainFactory;
public TracTracCourseDesignUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId, IRace tractracRace, DomainFactory domainFactory) {
super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
super(updateURI, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
this.domainFactory = domainFactory;
this.tractracRace = tractracRace;
this.courseSerializer = new CourseJsonSerializer(
new CourseBaseJsonSerializer(
new WaypointJsonSerializer(
new ControlPointJsonSerializer(
new MarkJsonSerializer(),
new GateJsonSerializer(new MarkJsonSerializer())))));
}
@Override
public void courseDesignChanged(final CourseBase newCourseDesign) throws MalformedURLException, IOException {
if (!isActive()) {
logger.info("Not sending course update to TracTrac because no URL has been provided.");
return;
}
final CourseBase newCourseDesignWithExistingControlPoints = replaceControlPointsByMatchingExistingControlPoints(newCourseDesign);
JSONObject serializedCourseDesign = courseSerializer.serialize(newCourseDesignWithExistingControlPoints);
String payload = serializedCourseDesign.toJSONString();
URL currentCourseDesignURL = buildUpdateURL();
logger.info("Using " + currentCourseDesignURL.toString() + " for the course update!");
logger.info("Payload is " + payload);
HttpURLConnection connection = (HttpURLConnection) currentCourseDesignURL.openConnection();
try {
setConnectionPropertiesAndSendWithPayload(connection, payload);
try {
checkAndLogUpdateResponse(connection);
} catch (ParseException e) {
e.printStackTrace();
}
} finally {
if (connection != null) {
connection.disconnect();
} else {
logger.severe("Connection to TracTrac Course Update URL " + currentCourseDesignURL.toString() + " could not be established");
}
}
}
private CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) {
protected CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) {
final Iterable<IControl> candidates = domainFactory.getControlsForCourseArea(tractracRace.getEvent(), tractracRace.getCourseArea());
final CourseBase result = new CourseDataImpl(courseDesign.getName());
int zeroBasedPosition = 0;
@@ -47,5 +47,12 @@ Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sse.shared.android,
org.apache.commons.math,
com.sap.sailing.declination,
com.sap.sailing.server.gateway.serialization.shared.android,
com.sap.sse.datamining.annotations
Import-Package: org.apache.http,
org.apache.http.client;version="[4.2.5,4.3.0)",
org.apache.http.client.entity;version="[4.2.5,4.3.0)",
org.apache.http.client.methods;version="[4.2.5,4.3.0)",
org.apache.http.impl.client;version="[4.2.5,4.3.0)",
org.apache.http.message;version="[4.2.5,4.3.0)"
Automatic-Module-Name: com.sap.sailing.domain
@@ -21,9 +21,14 @@ import com.sap.sailing.domain.common.dto.TrackedRaceStatisticsDTO;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler;
import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler;
import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler;
import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
@@ -101,4 +106,11 @@ public interface DomainFactory extends SharedDomainFactory {
*/
void updateRaceDTOWithTrackedRaceData(TrackedRace trackedRace, RaceDTO raceDTO);
/**
* Adds update handlers to a {@link TrackedRace} which can send updates to a REST service about race status
* changes such as a start time change, a course change, or a postponement.
*/
void addUpdateHandlers(DynamicTrackedRace trackedRace, CourseDesignUpdateHandler courseDesignHandler,
StartTimeUpdateHandler startTimeHandler, RaceAbortedHandler raceAbortedHandler,
final FinishTimeUpdateHandler finishTimeUpdateHandler);
}
@@ -11,6 +11,8 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -60,15 +62,15 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
private TrackedRaces trackedRaces;
private Map<Fleet, RaceIdentifier> raceIdentifiers;
private Map<Fleet, RaceLog> raceLogs;
private ConcurrentMap<Fleet, RaceLog> raceLogs;
private transient RaceLogStore raceLogStore;
private RegattaLikeIdentifier regattaLikeParent;
public AbstractRaceColumn() {
this.trackedRaces = new TrackedRaces();
this.raceIdentifiers = new HashMap<Fleet, RaceIdentifier>();
this.raceLogs = new HashMap<Fleet, RaceLog>();
this.raceLogs = new ConcurrentHashMap<Fleet, RaceLog>();
}
@Override
@@ -82,9 +84,7 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
@Override
public RaceLog getRaceLog(Fleet fleet) {
synchronized (raceLogs) {
return raceLogs.get(fleet);
}
return raceLogs.get(fleet);
}
@Override
@@ -197,25 +197,23 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
@Override
public void reloadRaceLog(Fleet fleet) {
synchronized (raceLogs) {
RaceLogIdentifier identifier = getRaceLogIdentifier(fleet);
RaceLog newOrLoadedRaceLog = raceLogStore.getRaceLog(identifier, /* ignoreCache */true);
RaceLog raceLogAvailable = raceLogs.get(fleet);
if (raceLogAvailable == null) {
RaceColumnRaceLogReplicator listener = new RaceColumnRaceLogReplicator(this, identifier);
// FIXME Wouldn't this skip any listener notifications that a merge below would trigger if the race log already existed?
// FIXME For example, how about the race log-provided score corrections that need application to the leaderboard and replication?
newOrLoadedRaceLog.addListener(listener);
raceLogs.put(fleet, newOrLoadedRaceLog);
final TrackedRace trackedRace = getTrackedRace(fleet);
if (trackedRace != null) {
// need to attach race log
trackedRace.attachRaceLog(newOrLoadedRaceLog);
}
} else {
// now add all race log events from newOrLoadedRaceLog that are not already in raceLogAvailable
raceLogAvailable.merge(newOrLoadedRaceLog);
RaceLogIdentifier identifier = getRaceLogIdentifier(fleet);
RaceLog newOrLoadedRaceLog = raceLogStore.getRaceLog(identifier, /* ignoreCache */true);
RaceLog raceLogAvailable = raceLogs.get(fleet);
if (raceLogAvailable == null) {
RaceColumnRaceLogReplicator listener = new RaceColumnRaceLogReplicator(this, identifier);
// FIXME Wouldn't this skip any listener notifications that a merge below would trigger if the race log already existed?
// FIXME For example, how about the race log-provided score corrections that need application to the leaderboard and replication?
newOrLoadedRaceLog.addListener(listener);
raceLogs.put(fleet, newOrLoadedRaceLog);
final TrackedRace trackedRace = getTrackedRace(fleet);
if (trackedRace != null) {
// need to attach race log
trackedRace.attachRaceLog(newOrLoadedRaceLog);
}
} else {
// now add all race log events from newOrLoadedRaceLog that are not already in raceLogAvailable
raceLogAvailable.merge(newOrLoadedRaceLog);
}
}
@@ -64,11 +64,16 @@ import com.sap.sailing.domain.leaderboard.impl.LowPointTieBreakBasedOnLastSeries
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
import com.sap.sailing.domain.leaderboard.impl.LowPointWithAutomaticRDG;
import com.sap.sailing.domain.leaderboard.impl.LowPointWithEliminationsAndRoundsWinnerGets07;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler;
import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler;
import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler;
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
import com.sap.sailing.geocoding.ReverseGeocoder;
import com.sap.sse.common.Distance;
@@ -201,11 +206,12 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
assert trackedRace != null;
raceDTO.startOfRace = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate();
raceDTO.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate();
raceDTO.raceFinishedTime = trackedRace.getFinishedTime() == null ? null : trackedRace.getFinishedTime().asDate();
raceDTO.status = new RaceStatusDTO();
raceDTO.status.status = trackedRace.getStatus() == null ? null : trackedRace.getStatus().getStatus();
raceDTO.status.loadingProgress = trackedRace.getStatus() == null ? 0.0 : trackedRace.getStatus().getLoadingProgress();
}
@Override
public TrackedRaceDTO createTrackedRaceDTO(TrackedRace trackedRace) {
TrackedRaceDTO trackedRaceDTO = new TrackedRaceDTO();
@@ -394,6 +400,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
return result;
}
@Override
public List<CompetitorAndBoatDTO> getCompetitorDTOList(List<Pair<Competitor, Boat>> competitors) {
List<CompetitorAndBoatDTO> result = new ArrayList<>();
for (Pair<Competitor, Boat> competitorAndBoat : competitors) {
@@ -401,4 +408,14 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
}
return result;
}
@Override
public void addUpdateHandlers(DynamicTrackedRace trackedRace, CourseDesignUpdateHandler courseDesignHandler,
StartTimeUpdateHandler startTimeHandler, RaceAbortedHandler raceAbortedHandler,
final FinishTimeUpdateHandler finishTimeUpdateHandler) {
trackedRace.addCourseDesignChangedListener(courseDesignHandler);
trackedRace.addStartTimeChangedListener(startTimeHandler);
trackedRace.addRaceAbortedListener(raceAbortedHandler);
trackedRace.addListener(finishTimeUpdateHandler.getListener());
}
}
@@ -698,8 +698,9 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
} catch (NoWindException e) {
tackAfterManeuver = null;
}
maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverMainCurveDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(), maneuverUnstableCourseAndSpeedBoundaries,
maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition,
maneuverMainCurveDetails.getTimePoint(), maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverUnstableCourseAndSpeedBoundaries,
maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing, maneuverLoss);
}
return maneuver;
@@ -723,7 +724,8 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
// For upwind/downwind legs, find the mean course between inbound and outbound course and project actual
// and
// extrapolated positions onto it:
Bearing middleManeuverAngle = speedWithBearingWhenSpeedStartedToDrop.getBearing().middle(speedWithBearingAfterManeuver.getBearing());
Bearing middleManeuverAngle = speedWithBearingWhenSpeedStartedToDrop.getBearing()
.middle(speedWithBearingAfterManeuver.getBearing());
// extrapolate maximum speed before maneuver to time point of maximum speed after maneuver and project
// resulting position
// onto the average maneuver course; compare to the projected position actually reached at the time
@@ -731,9 +733,9 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
// maneuver:
Position positionWhenSpeedStartedToDrop = track.getEstimatedPosition(timePointWhenSpeedStartedToDrop,
/* extrapolate */ false);
Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = speedWithBearingWhenSpeedStartedToDrop.travelTo(
positionWhenSpeedStartedToDrop, timePointWhenSpeedStartedToDrop,
timePointWhenSpeedLevelledOffAfterManeuver);
Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = speedWithBearingWhenSpeedStartedToDrop
.travelTo(positionWhenSpeedStartedToDrop, timePointWhenSpeedStartedToDrop,
timePointWhenSpeedLevelledOffAfterManeuver);
Position actualPositionAtTimePointOfMaxSpeedAfterManeuver = track
.getEstimatedPosition(timePointWhenSpeedLevelledOffAfterManeuver, /* extrapolate */ false);
Position projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver
@@ -745,9 +747,8 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
Distance projectedDistanceSailedIfNotManeuvering = positionWhenSpeedStartedToDrop
.getDistance(projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver);
return new ManeuverLoss(projectedDistanceSailed, projectedDistanceSailedIfNotManeuvering,
positionWhenSpeedStartedToDrop,
actualPositionAtTimePointOfMaxSpeedAfterManeuver, maneuverDuration, speedWithBearingWhenSpeedStartedToDrop,
middleManeuverAngle);
positionWhenSpeedStartedToDrop, actualPositionAtTimePointOfMaxSpeedAfterManeuver, maneuverDuration,
speedWithBearingWhenSpeedStartedToDrop, middleManeuverAngle);
}
protected Duration getDurationForDouglasPeuckerExtensionForMainCurveAnalysis(Duration approximateManeuverDuration) {
@@ -938,11 +939,25 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
if (isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, maneuverStart)) {
maneuverStart = null;
}
// Stable course analysis is considered as not necessary for preparation phase of maneuver because no
// oversteering is usually performed before maneuver
TimePoint stableBearingAnalysisUntil = maneuverStart == null ? maneuverMainCurveDetails.getTimePointBefore()
: maneuverStart.getExtensionTimePoint();
Speed lowestSpeed = maneuverStart == null ? null : maneuverStart.getLowestSpeedWithinExtensionArea();
double courseChangeSinceManeuverMainCurveInDegrees = maneuverStart == null ? 0
: maneuverStart.getCourseChangeInDegreesWithinExtensionArea();
stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, earliestTimePointForSpeedTrendAnalysis,
stableBearingAnalysisUntil);
ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed(
stepsToAnalyze, true, MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS);
if (stableBearingExtension != null
&& !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) {
maneuverStart = stableBearingExtension;
courseChangeSinceManeuverMainCurveInDegrees += stableBearingExtension
.getCourseChangeInDegreesWithinExtensionArea();
if (lowestSpeed == null
|| lowestSpeed.compareTo(stableBearingExtension.getLowestSpeedWithinExtensionArea()) > 0) {
lowestSpeed = stableBearingExtension.getLowestSpeedWithinExtensionArea();
}
}
return maneuverStart != null
? new ManeuverCurveBoundaryExtension(maneuverStart.getExtensionTimePoint(),
maneuverStart.getSpeedWithBearingAtExtensionTimePoint(),
@@ -959,8 +974,8 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
if (curveBoundaryExtension == null) {
return false;
}
return Math.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) > Math
.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) / 3.0;
return Math.abs(maneuverMainCurveDetails.getDirectionChangeInDegrees()) / 2.0 < Math
.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea());
}
/**
@@ -0,0 +1,78 @@
package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.logging.Logger;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.ControlPointJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.GateJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJsonSerializer;
public class CourseDesignUpdateHandler extends UpdateHandler implements CourseDesignChangedListener {
private final static String ACTION = "update_course";
private final static Logger logger = Logger.getLogger(CourseDesignUpdateHandler.class.getName());
private final JsonSerializer<CourseBase> courseSerializer;
public CourseDesignUpdateHandler(URI updateURI, String username, String password, Serializable eventId, Serializable raceId) {
super(updateURI, ACTION, username, password, eventId, raceId);
this.courseSerializer = new CourseJsonSerializer(
new CourseBaseJsonSerializer(
new WaypointJsonSerializer(
new ControlPointJsonSerializer(
new MarkJsonSerializer(),
new GateJsonSerializer(new MarkJsonSerializer())))));
}
@Override
public void courseDesignChanged(final CourseBase newCourseDesign) throws MalformedURLException, IOException {
if (!isActive()) {
logger.info("Not sending course update to TracTrac because no URL has been provided.");
return;
}
final CourseBase newCourseDesignWithExistingControlPoints = replaceControlPointsByMatchingExistingControlPoints(newCourseDesign);
JSONObject serializedCourseDesign = courseSerializer.serialize(newCourseDesignWithExistingControlPoints);
String payload = serializedCourseDesign.toJSONString();
URL currentCourseDesignURL = buildUpdateURL();
logger.info("Using " + currentCourseDesignURL.toString() + " for the course update!");
logger.info("Payload is " + payload);
HttpURLConnection connection = (HttpURLConnection) currentCourseDesignURL.openConnection();
try {
setConnectionPropertiesAndSendWithPayload(connection, payload);
try {
checkAndLogUpdateResponse(connection);
} catch (ParseException e) {
e.printStackTrace();
}
} finally {
if (connection != null) {
connection.disconnect();
} else {
logger.severe("Connection to TracTrac Course Update URL " + currentCourseDesignURL.toString() + " could not be established");
}
}
}
/**
* This default implementation returns the {@code courseDesign} unchanged. Subclasses have the possibility to
* override this method in order to replace individual course marks provided in a detailed course design,
* based on the actual course marks available.
*/
protected CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) {
return courseDesign;
}
}
@@ -188,8 +188,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
public void onStartTimeChanged(ReadonlyRaceState state) {
final TimePoint oldStartTime = getStartOfRace();
invalidateStartTime();
if (!Util.equalsWithNull(oldStartTime, getStartOfRace())) {
onStartTimeChangedByRaceCommittee(getStartOfRace());
final TimePoint startTimeFromRaceLog = state.getStartTime();
if (!Util.equalsWithNull(oldStartTime, startTimeFromRaceLog)) {
onStartTimeChangedByRaceCommittee(startTimeFromRaceLog);
}
}
};
@@ -79,7 +79,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
logger.severe("Trying to add " + this + " as listener to a null race log for tracked race " + trackedRace.getRace());
} else {
raceLog.addListener(this);
final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.create(trackedRace.getRaceLogResolver(), raceLog);
final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(trackedRace.getRaceLogResolver(), raceLog);
raceLogs.put(raceLog, raceState);
raceState.addChangedListener(new BaseRaceStateChangedListener() {
@Override
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.tractracadapter.impl;
package com.sap.sailing.domain.tracking.impl;
import java.io.BufferedReader;
import java.io.IOException;
@@ -20,12 +20,11 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sse.common.TimePoint;
import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes;
public class TracTracFinishTimeUpdateHandler extends UpdateHandler {
private final static Logger logger = Logger.getLogger(TracTracFinishTimeUpdateHandler.class.getName());
public class FinishTimeUpdateHandler extends UpdateHandler {
private final static Logger logger = Logger.getLogger(FinishTimeUpdateHandler.class.getName());
private final static String ACTION_STOP_TRACKING = "end_tracking";
private final static String FIELD_TRACKING_END_TIME = "tracking_end_time";
@@ -36,9 +35,9 @@ public class TracTracFinishTimeUpdateHandler extends UpdateHandler {
*/
private final Regatta regatta;
public TracTracFinishTimeUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword,
Serializable tracTracEventId, Serializable raceId, Regatta regatta) {
super(updateURI, ACTION_STOP_TRACKING, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
public FinishTimeUpdateHandler(URI updateURI, String username, String password,
Serializable eventId, Serializable raceId, Regatta regatta) {
super(updateURI, ACTION_STOP_TRACKING, username, password, eventId, raceId);
this.regatta = regatta;
}
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.tractracadapter.impl;
package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.Serializable;
@@ -15,14 +15,14 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
public class TracTracRaceAbortedHandler extends UpdateHandler implements RaceAbortedListener {
public class RaceAbortedHandler extends UpdateHandler implements RaceAbortedListener {
private final static String ACTION = "update_race_status";
private final static Logger logger = Logger.getLogger(TracTracRaceAbortedHandler.class.getName());
private final static Logger logger = Logger.getLogger(RaceAbortedHandler.class.getName());
public TracTracRaceAbortedHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) {
super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
public RaceAbortedHandler(URI updateURI, String username, String password, Serializable eventId, Serializable raceId) {
super(updateURI, ACTION, username, password, eventId, raceId);
}
@Override
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.tractracadapter.impl;
package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.Serializable;
@@ -13,14 +13,14 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sse.common.TimePoint;
public class TracTracStartTimeResetHandler extends UpdateHandler implements StartTimeChangedListener {
public class StartTimeResetHandler extends UpdateHandler implements StartTimeChangedListener {
private static final String ACTION = "resetStartTime";
private final static Logger logger = Logger.getLogger(TracTracStartTimeResetHandler.class.getName());
private final static Logger logger = Logger.getLogger(StartTimeResetHandler.class.getName());
public TracTracStartTimeResetHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) {
super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
public StartTimeResetHandler(URI updateURI, String username, String password, Serializable eventId, Serializable raceId) {
super(updateURI, ACTION, username, password, eventId, raceId);
}
@Override
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.tractracadapter.impl;
package com.sap.sailing.domain.tracking.impl;
import java.io.BufferedReader;
import java.io.IOException;
@@ -23,13 +23,14 @@ import org.apache.http.message.BasicNameValuePair;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes;
public class TracTracStartTimeUpdateHandler extends UpdateHandler implements StartTimeChangedListener {
private final static Logger logger = Logger.getLogger(TracTracStartTimeUpdateHandler.class.getName());
public class StartTimeUpdateHandler extends UpdateHandler implements StartTimeChangedListener {
private final static Logger logger = Logger.getLogger(StartTimeUpdateHandler.class.getName());
private final static String ACTION = "update_race_start_time";
private final static String ACTION_START_TRACKING = "start_tracking";
@@ -41,10 +42,13 @@ public class TracTracStartTimeUpdateHandler extends UpdateHandler implements Sta
* a new start time is received.
*/
private final Regatta regatta;
private final RaceAbortedHandler raceAbortedHandler;
public TracTracStartTimeUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword,
public StartTimeUpdateHandler(URI updateURI, String username, String password,
Serializable tracTracEventId, Serializable raceId, Regatta regatta) {
super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
super(updateURI, ACTION, username, password, tracTracEventId, raceId);
this.raceAbortedHandler = new RaceAbortedHandler(updateURI, username, password, tracTracEventId, raceId);
this.regatta = regatta;
}
@@ -52,20 +56,12 @@ public class TracTracStartTimeUpdateHandler extends UpdateHandler implements Sta
public void startTimeChanged(TimePoint newStartTime) throws MalformedURLException, IOException, URISyntaxException {
if (isActive()) {
if (newStartTime == null) {
/*
* Do not reset start time based on request by Jorge from TracTrac:
*
* """
* We have detected that when you want to update the race start time your
* system sends before a message to reset the race start time. When your
* system invokes the first service our system changes the race start time
* to null and this value causes some secondary effects in our side.
*
* Can you just send the update_race_start_time without the
* reset_race_start_time? The reset_race_start_time service has to be used
* if you want to set the value to null.
* """
/* notify race status as POSTPONED according to Jorge's comment https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4708#c5 :
* The method that @frank has commented has to work:
* http://em.aws.tractrac.com/update_race_status?eventid=5f2f20f0-6cb2-0136-9eca-60a44ce903c3&raceid=81c082a0-7b1f-0136-166e-028f184941da&username=trac%40sapsailing.com&password=sap0912&race_status=POSTPONED
* This method changes the race start time to null.
*/
raceAbortedHandler.raceAborted(Flags.AP); // will send POSTPONED
} else {
HashMap<String, String> additionalParameters = new HashMap<String, String>();
additionalParameters.put(FIELD_RACE_START_TIME, String.valueOf(newStartTime.asMillis()));
@@ -50,7 +50,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinde
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogGateLineOpeningTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.state.RaceState;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDefinedMarkAnalyzer;
@@ -2975,7 +2975,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
synchronized (raceStates) {
result = raceStates.get(raceLog);
if (result == null) {
result = RaceStateImpl.create(raceLogResolver, raceLog);
result = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog);
raceStates.put(raceLog, result);
}
}
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.tractracadapter.impl;
package com.sap.sailing.domain.tracking.impl;
import java.io.BufferedReader;
import java.io.DataOutputStream;
@@ -25,21 +25,28 @@ import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.tractracadapter.UpdateResponse;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
import com.sap.sse.common.Duration;
/**
* An update handler can be registered as a listener on a {@link TrackedRace} and can then propagate information to
* an external service. This can, e.g., be a tracking partner's system that is interested in receiving race status
* updates such as new start times or postponements.
*
* @author Axel Uhl (d043530)
*
*/
public class UpdateHandler {
private final static Logger logger = Logger.getLogger(UpdateHandler.class.getName());
private JsonDeserializer<UpdateResponse> updateDeserializer;
private final URI baseURI;
private final String tracTracUsername;
private final String tracTracPassword;
private final Serializable tracTracEventId;
private final String username;
private final String password;
private final Serializable eventId;
private final Serializable raceId;
private final String action;
private final boolean active;
@@ -53,16 +60,16 @@ public class UpdateHandler {
private final static String ResponseCodeForFailure = "FAILURE";
private final static String UpdateUrlTemplate = "%s%s?eventid=%s&raceid=%s&username=%s&password=%s";
public UpdateHandler(URI updateURI, String action, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) {
public UpdateHandler(URI updateURI, String action, String username, String password, Serializable eventId, Serializable raceId) {
this.baseURI = updateURI;
this.action = action;
this.tracTracUsername = tracTracUsername;
this.tracTracPassword = tracTracPassword;
this.tracTracEventId = tracTracEventId;
this.username = username;
this.password = password;
this.eventId = eventId;
this.raceId = raceId;
this.updateDeserializer = new UpdateResponseDeserializer();
if (updateURI != null && !updateURI.toString().equals("")) {
logger.info("Activating TracTrac update handler "+this+" for race with ID "+raceId);
logger.info("Activating update handler "+this+" for race with ID "+raceId);
this.active = true;
} else {
this.active = false;
@@ -87,10 +94,10 @@ public class UpdateHandler {
*/
protected List<BasicNameValuePair> getDefaultParametersAsNewList() {
final List<BasicNameValuePair> result = new ArrayList<>();
result.add(new BasicNameValuePair("eventid", tracTracEventId.toString()));
result.add(new BasicNameValuePair("eventid", eventId.toString()));
result.add(new BasicNameValuePair("raceid", this.raceId.toString()));
result.add(new BasicNameValuePair("username", tracTracUsername));
result.add(new BasicNameValuePair("password", tracTracPassword));
result.add(new BasicNameValuePair("username", username));
result.add(new BasicNameValuePair("password", password));
return result;
}
@@ -103,10 +110,10 @@ public class UpdateHandler {
String url = String.format(UpdateUrlTemplate,
serverUpdateURI.toString(),
this.action,
URLEncoder.encode(this.tracTracEventId.toString(), EncodingUtf8),
URLEncoder.encode(this.eventId.toString(), EncodingUtf8),
URLEncoder.encode(this.raceId.toString(), EncodingUtf8),
URLEncoder.encode(tracTracUsername, EncodingUtf8),
URLEncoder.encode(tracTracPassword, EncodingUtf8));
URLEncoder.encode(username, EncodingUtf8),
URLEncoder.encode(password, EncodingUtf8));
for (Entry<String, String> entry : additionalParameters.entrySet()) {
url += String.format("&%s=%s",
@@ -123,7 +130,7 @@ public class UpdateHandler {
protected void checkAndLogUpdateResponse(HttpURLConnection connection) throws IOException, ParseException {
connection.setConnectTimeout(10000/*milliseconds*/);
connection.connect();
BufferedReader reader = getResponseOnUpdateFromTracTrac(connection);
BufferedReader reader = getResponseOnUpdateFromProvider(connection);
parseAndLogResponse(reader);
}
@@ -133,13 +140,13 @@ public class UpdateHandler {
JSONObject responseObject = Helpers.toJSONObjectSafe(responseBody);
UpdateResponse updateResponse = updateDeserializer.deserialize(responseObject);
if (updateResponse.getStatus().equals(ResponseCodeForFailure)) {
logger.severe("Failed to send data to TracTrac, got following response: " + updateResponse.getMessage());
logger.severe("Failed to send data to provider, got following response: " + updateResponse.getMessage());
} else {
logger.info("Successfully sent data to TracTrac with response: " + updateResponse.getMessage());
logger.info("Successfully sent data to provider with response: " + updateResponse.getMessage());
}
}
private BufferedReader getResponseOnUpdateFromTracTrac(HttpURLConnection connection) throws IOException {
private BufferedReader getResponseOnUpdateFromProvider(HttpURLConnection connection) throws IOException {
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
return reader;
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.tractracadapter;
package com.sap.sailing.domain.tracking.impl;
public interface UpdateResponse {
String getStatus();
String getMessage();
@@ -1,8 +1,7 @@
package com.sap.sailing.domain.tractracadapter.impl;
package com.sap.sailing.domain.tracking.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.tractracadapter.UpdateResponse;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
@@ -1,6 +1,4 @@
package com.sap.sailing.domain.tractracadapter.impl;
import com.sap.sailing.domain.tractracadapter.UpdateResponse;
package com.sap.sailing.domain.tracking.impl;
public class UpdateResponseImpl implements UpdateResponse {
@@ -75,7 +75,7 @@ public class SailingServiceImplMock extends SailingServiceImpl {
public RaceHandle addSwissTimingRace(TrackerManager trackerManager,
RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription,
BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore logStore,
RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) throws InterruptedException,
RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword) throws InterruptedException,
UnknownHostException, IOException, ParseException, Exception {
// TODO Auto-generated method stub
return null;
@@ -12,16 +12,20 @@ import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.autoplay.client.app.AnimationPanel;
import com.sap.sailing.gwt.autoplay.client.app.AutoPlayClientFactory;
import com.sap.sailing.gwt.autoplay.client.app.AutoPlayPresenterConfigured;
import com.sap.sailing.gwt.autoplay.client.utils.AutoplayHelper;
import com.sap.sailing.gwt.settings.client.leaderboard.SingleRaceLeaderboardSettings;
import com.sap.sailing.gwt.ui.client.FlagImageResolverImpl;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet;
@@ -152,12 +156,7 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl
view.showErrorNoLive(this, panel, new IllegalStateException("No race is live"));
return;
}
final SingleRaceLeaderboardSettings leaderboardSettings = new SingleRaceLeaderboardSettings(
/* maneuverDetailsToShow */ null, /* legDetailsToShow */ null, /* raceDetailsToShow */ null,
/* overallDetailsToShow */ null, /* delayBetweenAutoAdvancesInMilliseconds */ null,
/* showAddedScores */ false, /* showCompetitorShortNameColumn */ true,
/* showCompetitorFullNameColumn */ false, /* isCompetitorNationalityColumnVisible */ false,
/* showCompetitorBoatInfoColumn */ false, /* showRaceRankColumn */ true);
final SingleRaceLeaderboardSettings leaderboardSettings = getLeaderboardSettings(false);
timer = new com.sap.sse.gwt.client.player.Timer(
// perform the first request as "live" but don't by default auto-play
PlayModes.Live, PlayStates.Playing,
@@ -167,12 +166,42 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl
getSlideCtx().getContextDefinition().getLeaderboardName(), errorReporter, StringMessages.INSTANCE,
false, null, false, null, false, true, false, false, false, new SixtyInchLeaderboardStyle(true),
FlagImageResolverImpl.get(), Arrays.asList(DetailType.values()));
leaderboardPanel.addLeaderboardUpdateListener(new LeaderboardUpdateListener() {
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
// if this is a changing boat race, enable the boat column
if (leaderboardPanel.getLeaderboard() != null) {
LeaderboardDTO leaderboardDTO = leaderboardPanel.getLeaderboard();
boolean boatsRequired = leaderboardDTO.canBoatsOfCompetitorsChangePerRace;
if (leaderboardPanel.getSettings().isShowCompetitorBoatInfoColumn() != boatsRequired) {
leaderboardPanel.updateSettings(getLeaderboardSettings(boatsRequired));
}
}
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
}
});
getPlace().getRaceMap().setQuickRanksDTOProvider(new QuickRanksDTOFromLeaderboardDTOProvider(
new RaceCompetitorSet(getPlace().getRaceMapSelectionProvider()), liveRace));
view.startingWith(this, panel, getPlace().getRaceMap(), leaderboardPanel);
selectionTimer.schedule(SWITCH_COMPETITOR_DELAY + AnimationPanel.ANIMATION_DURATION + AnimationPanel.DELAY);
}
private SingleRaceLeaderboardSettings getLeaderboardSettings(boolean showBoatColumn) {
final SingleRaceLeaderboardSettings leaderboardSettings = new SingleRaceLeaderboardSettings(
/* maneuverDetailsToShow */ null, /* legDetailsToShow */ null, /* raceDetailsToShow */ null,
/* overallDetailsToShow */ null, /* delayBetweenAutoAdvancesInMilliseconds */ null,
/* showAddedScores */ false, /* showCompetitorShortNameColumn */ true,
/* showCompetitorFullNameColumn */ false, showBoatColumn,
/* showCompetitorBoatInfoColumn */ false, /* showRaceRankColumn */ true);
return leaderboardSettings;
}
@Override
public void onStop() {
if (timer != null) {
@@ -137,12 +137,11 @@ public class AutoplayHelper {
RaceTimesInfoDTO raceTimes = raceTimesInfos.get(raceIdentifier);
boolean notNullInRequiredValues = raceTimes != null && raceTimes.startOfTracking != null
&& raceTimes.getStartOfRace() != null;
boolean raceHasNotEndedOrOnlyRecentlyEnded = raceTimes.endOfRace == null
Date endTime = raceTimes.getFinishedTime() != null ? raceTimes.getFinishedTime() : raceTimes.getEndOfRace();
boolean raceHasNotEndedOrOnlyRecentlyEnded = endTime == null
|| serverTimeDuringRequest.getTime()
- raceTimes.delayToLiveInMs < raceTimes.endOfRace.getTime()
- raceTimes.delayToLiveInMs < endTime.getTime()
+ waitTimeAfterRaceEndInMillis;
if (notNullInRequiredValues && raceHasNotEndedOrOnlyRecentlyEnded) {
long startTimeInMs = raceTimes.getStartOfRace().getTime();
long startIn = startTimeInMs - serverTimeDuringRequest.getTime() - raceTimes.delayToLiveInMs;
@@ -48,10 +48,11 @@ public class MultiRegattaListSteps extends Composite {
double parts = Math.pow(seriesProgress.getMaxRacesPerFleet(), 0.4);
sumParts += parts;
}
final boolean showSeriesName = regattaProgress.getSeries().size() > 1;
for(RegattaProgressSeriesDTO seriesProgress : regattaProgress.getSeries()) {
double parts = Math.pow(seriesProgress.getMaxRacesPerFleet(), 0.4);
double percentage = 100.0 * parts / sumParts;
MultiRegattaListStepsBody stepsBody = new MultiRegattaListStepsBody(seriesProgress);
MultiRegattaListStepsBody stepsBody = new MultiRegattaListStepsBody(seriesProgress, showSeriesName);
allMultiregattaSteps.add(stepsBody);
stepsBody.getElement().getStyle().setWidth(percentage, Unit.PCT);
stepsContainerUi.appendChild(stepsBody.getElement());
@@ -1,7 +1,5 @@
package com.sap.sailing.gwt.home.desktop.partials.multiregattalist;
import static com.sap.sailing.domain.common.LeaderboardNameConstants.DEFAULT_SERIES_NAME;
import java.util.Map;
import java.util.Map.Entry;
@@ -42,17 +40,17 @@ public class MultiRegattaListStepsBody extends UIObject implements RequiresResiz
private final String seriesName, seriesNameMedium, seriesNameShort;
private int seriesNameLength, seriesNameMediumLength;
public MultiRegattaListStepsBody(RegattaProgressSeriesDTO seriesProgress) {
MultiRegattaListStepsBody(RegattaProgressSeriesDTO seriesProgress, boolean showName) {
setElement(uiBinder.createAndBindUi(this));
textContainerUi.getStyle().setVisibility(Visibility.HIDDEN);
longNameDummyUi.setInnerText(seriesName = caculateSeriesName(seriesProgress));
longNameDummyUi.setInnerText(seriesName = caculateSeriesName(seriesProgress, showName));
mediumNameDummyUi.setInnerText(seriesNameMedium = caculateSeriesNameMedium());
seriesNameShort = caculateSeriesNameShort();
if (seriesProgress.isCompleted()) {
progressUi.setInnerText(String.valueOf(seriesProgress.getTotalRaceCount()));
progressUi.setInnerText(I18N.racesCount(seriesProgress.getTotalRaceCount()));
} else {
checkUi.getStyle().setDisplay(Display.NONE);
progressUi.setInnerText(I18N.currentOfTotal(
progressUi.setInnerText(I18N.currentOfTotalRaces(
seriesProgress.getProgressRaceCount(), seriesProgress.getTotalRaceCount()));
}
addFleetProgresses(seriesProgress.getFleetState(), seriesProgress.getMaxRacesPerFleet());
@@ -93,9 +91,9 @@ public class MultiRegattaListStepsBody extends UIObject implements RequiresResiz
}
}
private String caculateSeriesName(RegattaProgressSeriesDTO seriesProgress) {
if (seriesProgress.getName() == null || seriesProgress.getName().isEmpty()
|| DEFAULT_SERIES_NAME.equals(seriesProgress.getName())) {
private String caculateSeriesName(RegattaProgressSeriesDTO seriesProgress, boolean showName) {
if (!showName || seriesProgress.getName() == null || seriesProgress.getName().isEmpty()) {
nameUi.getStyle().setDisplay(Display.NONE);
return I18N.races();
} else {
return seriesProgress.getName();
@@ -1,5 +1,7 @@
package com.sap.sailing.gwt.home.desktop.places.event.regatta.racestab;
import java.util.Optional;
import com.google.gwt.place.shared.Prefix;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.AbstractEventRegattaPlace;
import com.sap.sailing.gwt.home.shared.app.HasMobileVersion;
@@ -8,24 +10,37 @@ import com.sap.sailing.gwt.home.shared.places.event.AbstractEventPlace;
import com.sap.sailing.gwt.home.shared.places.event.EventContext;
public class RegattaRacesPlace extends AbstractEventRegattaPlace implements HasMobileVersion {
private final String preferredSeriesName;
public RegattaRacesPlace(String id, String leaderboardName) {
super(id, leaderboardName);
this.preferredSeriesName = null;
}
public RegattaRacesPlace(EventContext context) {
this(context, null);
}
public RegattaRacesPlace(EventContext context, String preferredSeriesName) {
super(context);
this.preferredSeriesName = preferredSeriesName;
}
public final Optional<String> getPreferredSeriesName() {
return Optional.ofNullable(preferredSeriesName);
}
@Override
public AbstractEventRegattaPlace newInstanceWithContext(EventContext ctx) {
return new RegattaRacesPlace(ctx);
return new RegattaRacesPlace(ctx, null);
}
@Prefix(PlaceTokenPrefixes.RegattaRaces)
public static class Tokenizer extends AbstractEventPlace.Tokenizer<RegattaRacesPlace> {
@Override
protected RegattaRacesPlace getRealPlace(EventContext context) {
return new RegattaRacesPlace(context);
return new RegattaRacesPlace(context, null);
}
}
}
@@ -34,6 +34,7 @@ import com.sap.sailing.gwt.home.shared.places.user.confirmation.ConfirmationPlac
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetActivityProxy;
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetPlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace;
import com.sap.sse.gwt.client.DOMUtils;
public class MobileActivityMapper implements ActivityMapper {
private final MobileApplicationClientFactory clientFactory;
@@ -52,6 +53,7 @@ public class MobileActivityMapper implements ActivityMapper {
SwitchingEntryPoint.reloadApp();
return null;
}
DOMUtils.scrollToTop(clientFactory.getRoot());
if (place instanceof ErrorPlace) {
return new ErrorActivityProxy((ErrorPlace) place, clientFactory);
} else if (place instanceof StartPlace) {
@@ -21,6 +21,9 @@
.eventsteps_phases_phaseinactive {
color: #999;
}
.eventsteps_phases_phase_link {
color: #000;
}
/* .eventsteps_phases_phase:nth-child(2n-1) { */
/* padding-right: 0.333333333333333em; */
/* } */
@@ -35,6 +38,7 @@
/* padding: .666666666666667em 0; */
display: inline;
vertical-align: middle;
margin-right: 0.333333333333333rem;
}
.eventsteps_phases_phase_progressbar {
height: 1.266666666666667em;
@@ -58,7 +62,7 @@ img.eventsteps_phases_phase_progress_icon {
vertical-align: middle;
height: 1.133333333333333em;
width: 1.133333333333333em;
margin: 0 0.333333333333333em;
margin-right: 0.333333333333333em;
}
.eventsteps_phases_phase_progress_text {
font-size: 0.866666666666667em;
@@ -1,5 +1,7 @@
package com.sap.sailing.gwt.home.mobile.partials.eventsteps;
import java.util.function.Function;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.uibinder.client.UiBinder;
@@ -11,6 +13,7 @@ import com.sap.sailing.gwt.home.communication.regatta.RegattaProgressSeriesDTO;
import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO;
import com.sap.sailing.gwt.home.mobile.partials.section.MobileSection;
import com.sap.sailing.gwt.home.mobile.partials.sectionHeader.SectionHeaderContent;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.home.shared.refresh.RefreshableWidget;
public class EventSteps extends Composite implements RefreshableWidget<RegattaWithProgressDTO> {
@@ -22,8 +25,10 @@ public class EventSteps extends Composite implements RefreshableWidget<RegattaWi
@UiField SectionHeaderContent sectionHeaderUi;
private final MobileSection regattaProgessUi;
private final Function<String, PlaceNavigation<?>> racesNavigationFactory;
public EventSteps(RegattaMetadataDTO regatta) {
public EventSteps(RegattaMetadataDTO regatta, Function<String, PlaceNavigation<?>> racesNavigationFactory) {
this.racesNavigationFactory = racesNavigationFactory;
EventStepsResources.INSTANCE.css().ensureInjected();
initWidget(regattaProgessUi = uiBinder.createAndBindUi(this));
ImageResource boatClassIcon = BoatClassImageResolver.getBoatClassIconResource(regatta.getBoatClass());
@@ -36,8 +41,10 @@ public class EventSteps extends Composite implements RefreshableWidget<RegattaWi
@Override
public void setData(RegattaWithProgressDTO data) {
regattaProgessUi.clearContent();
final boolean showSeriesName = data.getProgress().getSeries().size() > 1;
for (RegattaProgressSeriesDTO seriesProgress : data.getProgress().getSeries()) {
regattaProgessUi.addContent(new EventStepsPhase(seriesProgress));
final PlaceNavigation<?> navigation = racesNavigationFactory.apply(seriesProgress.getName());
regattaProgessUi.addContent(new EventStepsPhase(seriesProgress, navigation, showSeriesName));
}
}
@@ -1,11 +1,10 @@
package com.sap.sailing.gwt.home.mobile.partials.eventsteps;
import static com.sap.sailing.domain.common.LeaderboardNameConstants.DEFAULT_SERIES_NAME;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.Style.Display;
@@ -16,7 +15,9 @@ import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.race.FleetMetadataDTO;
import com.sap.sailing.gwt.home.communication.regatta.RegattaProgressFleetDTO;
import com.sap.sailing.gwt.home.communication.regatta.RegattaProgressSeriesDTO;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.LinkUtil;
public class EventStepsPhase extends Composite {
@@ -26,20 +27,26 @@ public class EventStepsPhase extends Composite {
interface EventStepsPhaseUiBinder extends UiBinder<Widget, EventStepsPhase> {
}
@UiField AnchorElement anchorUi;
@UiField DivElement nameUi;
@UiField ImageElement checkUi;
@UiField DivElement progressUi;
@UiField DivElement fleetsContainerUi;
public EventStepsPhase(RegattaProgressSeriesDTO seriesProgress) {
EventStepsPhase(RegattaProgressSeriesDTO seriesProgress, PlaceNavigation<?> placeNavigation, boolean showName) {
initWidget(uiBinder.createAndBindUi(this));
nameUi.setInnerText(DEFAULT_SERIES_NAME.equals(seriesProgress.getName()) ? I18N.races() : seriesProgress.getName());
LinkUtil.configureForAction(anchorUi, () -> placeNavigation.goToPlace());
if (showName) {
nameUi.setInnerText(seriesProgress.getName());
} else {
nameUi.removeFromParent();
}
if (seriesProgress.isCompleted()) {
progressUi.setInnerText(String.valueOf(seriesProgress.getTotalRaceCount()));
progressUi.setInnerText(I18N.racesCount(seriesProgress.getTotalRaceCount()));
} else {
checkUi.getStyle().setDisplay(Display.NONE);
int current = seriesProgress.getProgressRaceCount(), total = seriesProgress.getTotalRaceCount();
progressUi.setInnerText(I18N.currentOfTotal(current, total));
progressUi.setInnerText(I18N.currentOfTotalRaces(current, total));
}
if (seriesProgress.getProgressRaceCount() == 0) {
addStyleName(EventStepsResources.INSTANCE.css().eventsteps_phases_phaseinactive());
@@ -6,11 +6,13 @@
<ui:with field="local_res" type="com.sap.sailing.gwt.home.mobile.partials.eventsteps.EventStepsResources" />
<ui:with field="shr" type="com.sap.sailing.gwt.home.shared.resources.SharedHomeResources" />
<g:HTMLPanel addStyleNames="{local_res.css.eventsteps_phases_phase}">
<div class="{local_res.css.eventsteps_phases_phase_progress}">
<div class="{local_res.css.eventsteps_phases_phase_name}" ui:field="nameUi"></div>
<img src="{shr.greenCheck.getSafeUri}" class="{local_res.css.eventsteps_phases_phase_progress_icon}" ui:field="checkUi"></img>
<div class="{local_res.css.eventsteps_phases_phase_progress_text}" ui:field="progressUi"></div>
</div>
<div class="{local_res.css.eventsteps_phases_phase_progressbar}" ui:field="fleetsContainerUi"></div>
<a class="{local_res.css.eventsteps_phases_phase_link}" ui:field="anchorUi">
<div class="{local_res.css.eventsteps_phases_phase_progress}">
<div class="{local_res.css.eventsteps_phases_phase_name}" ui:field="nameUi"></div>
<img src="{shr.greenCheck.getSafeUri}" class="{local_res.css.eventsteps_phases_phase_progress_icon}" ui:field="checkUi"></img>
<div class="{local_res.css.eventsteps_phases_phase_progress_text}" ui:field="progressUi"></div>
</div>
<div class="{local_res.css.eventsteps_phases_phase_progressbar}" ui:field="fleetsContainerUi"></div>
</a>
</g:HTMLPanel>
</ui:UiBinder>
@@ -16,6 +16,7 @@ public interface EventStepsResources extends ClientBundle {
String eventsteps_phases();
String eventsteps_phases_phase();
String eventsteps_phases_phaseinactive();
String eventsteps_phases_phase_link();
String eventsteps_phases_phase_name();
String eventsteps_phases_phase_progressbar();
String eventsteps_phases_phase_progressbar_fleet();
@@ -1,5 +1,8 @@
package com.sap.sailing.gwt.home.mobile.partials.regattacompetition;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
@@ -8,6 +11,7 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatSeriesDTO;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView;
import com.sap.sse.gwt.client.DOMUtils;
public class RegattaCompetition extends Composite implements RegattaCompetitionView {
@@ -17,6 +21,7 @@ public class RegattaCompetition extends Composite implements RegattaCompetitionV
}
@UiField FlowPanel regattaSeriesContainerUi;
private final Map<String, RegattaCompetitionSeries> seriesNameToUiComponent = new HashMap<>();
public RegattaCompetition() {
RegattaCompetitionResources.INSTANCE.css().ensureInjected();
@@ -31,8 +36,12 @@ public class RegattaCompetition extends Composite implements RegattaCompetitionV
@Override
public RegattaCompetitionSeriesView addSeriesView(RaceCompetitionFormatSeriesDTO series) {
RegattaCompetitionSeries seriesView = new RegattaCompetitionSeries(series);
seriesNameToUiComponent.put(series.getSeriesName(), seriesView);
regattaSeriesContainerUi.add(seriesView);
return seriesView;
}
public void scrollToSeries(final String seriesName) {
DOMUtils.scrollToTop(seriesNameToUiComponent.get(seriesName));
}
}
@@ -5,4 +5,6 @@ import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
public interface RegattaRacesNavigationProvider {
PlaceNavigation<?> getRegattaRacesNavigation(String regattaId);
PlaceNavigation<?> getRegattaRacesNavigation(String regattaId, String seriesName);
}
@@ -247,6 +247,12 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
EventContext ctx = new EventContext(getCtx()).withRegattaId(regattaId).withRegattaAnalyticsManager(null);
return clientFactory.getNavigator().getEventNavigation(new RegattaRacesPlace(ctx), null, false);
}
@Override
public PlaceNavigation<?> getRegattaRacesNavigation(String regattaId, String prefSeriesName) {
EventContext ctx = new EventContext(getCtx()).withRegattaId(regattaId).withRegattaAnalyticsManager(null);
return clientFactory.getNavigator().getEventNavigation(new RegattaRacesPlace(ctx, prefSeriesName), null, false);
}
public PlaceNavigation<?> getLatesNewsNavigation() {
EventContext ctx = new EventContext(getCtx()).withRegattaId(null).withRegattaAnalyticsManager(null);
@@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.mobile.places.event.overview.regatta;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
@@ -19,6 +20,7 @@ import com.sap.sailing.gwt.home.mobile.partials.quickfinder.Quickfinder;
import com.sap.sailing.gwt.home.mobile.places.QuickfinderPresenter;
import com.sap.sailing.gwt.home.mobile.places.event.EventViewBase;
import com.sap.sailing.gwt.home.mobile.places.event.overview.AbstractEventOverview;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -54,7 +56,9 @@ public class RegattaOverviewImpl extends AbstractEventOverview {
}
private void setupProgress(Panel container) {
eventStepsUi = new EventSteps(currentPresenter.getRegatta());
final Function<String, PlaceNavigation<?>> racesNavigationFactory = prefSeriesName -> currentPresenter
.getRegattaRacesNavigation(getRegattaId(), prefSeriesName);
eventStepsUi = new EventSteps(currentPresenter.getRegatta(), racesNavigationFactory);
refreshManager.add(eventStepsUi, new GetRegattaWithProgressAction(getEventId(), getRegattaId()));
container.add(eventStepsUi);
}
@@ -1,6 +1,7 @@
package com.sap.sailing.gwt.home.mobile.places.event.races;
import java.util.List;
import java.util.Optional;
import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.racestab.RegattaRacesPlace;
@@ -32,4 +33,9 @@ public class RacesActivity extends AbstractEventActivity<RegattaRacesPlace> impl
initQuickfinder(view, true);
return view;
}
@Override
public Optional<String> getPreferredSeriesName() {
return getPlace().getPreferredSeriesName();
}
}
@@ -1,10 +1,14 @@
package com.sap.sailing.gwt.home.mobile.places.event.races;
import java.util.Optional;
import com.sap.sailing.gwt.home.mobile.places.event.EventViewBase;
public interface RacesView extends EventViewBase {
public interface Presenter extends EventViewBase.Presenter {
Optional<String> getPreferredSeriesName();
}
}
@@ -5,13 +5,16 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO;
import com.sap.sailing.gwt.home.communication.event.GetCompetitionFormatRacesAction;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatSeriesDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
@@ -27,6 +30,7 @@ import com.sap.sailing.gwt.home.shared.partials.filter.RacesByCompetitorTextBoxF
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.dispatch.shared.commands.ListResult;
public class RacesViewImpl extends AbstractEventView<RacesView.Presenter> implements RacesView {
@@ -71,6 +75,13 @@ public class RacesViewImpl extends AbstractEventView<RacesView.Presenter> implem
protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) {
return currentPresenter.getRaceViewerURL(raceMetadata, mode);
}
@Override
public void setData(ListResult<RaceCompetitionFormatSeriesDTO> data) {
super.setData(data);
final Consumer<? super String> scrollCommand = regattaCompetitionUi::scrollToSeries;
Scheduler.get().scheduleDeferred(() -> currentPresenter.getPreferredSeriesName().ifPresent(scrollCommand));
}
}
private class RacesViewImplFilterPresenter extends FilterPresenter<SimpleRaceMetadataDTO, SimpleCompetitorDTO> {
@@ -104,7 +104,7 @@ public class RaceContext {
this.fleet = fleet;
trackedRace = raceColumn.getTrackedRace(fleet);
raceLog = raceColumn.getRaceLog(fleet);
state = (raceLog == null) ? null : ReadonlyRaceStateImpl.create(raceLogResolver, raceLog);
state = (raceLog == null) ? null : ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog);
}
private boolean isShowFleetData() {
@@ -12,7 +12,7 @@ public abstract class LeaderboardPanelLifecycle<T extends LeaderboardSettings>
protected final Iterable<DetailType> availableDetailTypes;
public LeaderboardPanelLifecycle(StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
protected LeaderboardPanelLifecycle(StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
this.stringMessages = stringMessages;
this.availableDetailTypes = availableDetailTypes;
}
@@ -66,7 +66,8 @@ public abstract class LeaderboardSettings extends AbstractGenericSerializableSet
showCompetitorBoatInfoColumn = new BooleanSetting("showCompetitorBoatInfoColumn", this, false);
}
public LeaderboardSettings() {
public LeaderboardSettings(boolean showCompetitorBoatInfoColumnDefault) {
showCompetitorBoatInfoColumn.setDefaultValue(showCompetitorBoatInfoColumnDefault);
}
/**
@@ -46,7 +46,8 @@ public abstract class LeaderboardSettingsDialogComponent<T extends LeaderboardSe
protected T initialSettings;
protected Iterable<DetailType> availableDetailTypes;
public LeaderboardSettingsDialogComponent(T initialSettings, StringMessages stringMessages, Iterable<DetailType> availableDetailTypes, boolean canBoatInfoBeShown) {
protected LeaderboardSettingsDialogComponent(T initialSettings, StringMessages stringMessages,
Iterable<DetailType> availableDetailTypes, boolean canBoatInfoBeShown) {
this.initialSettings = initialSettings;
this.stringMessages = stringMessages;
this.canBoatInfoBeShown = canBoatInfoBeShown;
@@ -44,7 +44,7 @@ public class LeaderboardSettingsFactory {
}
public SingleRaceLeaderboardSettings createNewSettingsWithCustomRaceDetails(List<DetailType> raceDetailsToShow) {
SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings();
SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(false);
return new SingleRaceLeaderboardSettings(
defaultSettings.getManeuverDetailsToShow(),
defaultSettings.getLegDetailsToShow(),
@@ -11,11 +11,13 @@ import com.sap.sse.common.settings.generic.support.SettingsUtil;
public class MultiRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecycle<MultiRaceLeaderboardSettings> {
protected final List<String> namesOfRaceColumns;
private final boolean canBoatInfoBeShown;
public MultiRaceLeaderboardPanelLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages,
Iterable<DetailType> availableDetailTypes) {
super(stringMessages, availableDetailTypes);
this.namesOfRaceColumns = leaderboard != null ? leaderboard.getNamesOfRaceColumns() : new ArrayList<String>();
this.canBoatInfoBeShown = leaderboard != null ? !leaderboard.canBoatsOfCompetitorsChangePerRace : false;
}
@Override
public MultiRaceLeaderboardSettings extractUserSettings(MultiRaceLeaderboardSettings currentLeaderboardSettings) {
@@ -55,7 +57,8 @@ public class MultiRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecycl
@Override
public MultiRaceLeaderboardSettingsDialogComponent getSettingsDialogComponent(MultiRaceLeaderboardSettings settings) {
return new MultiRaceLeaderboardSettingsDialogComponent(settings, namesOfRaceColumns, stringMessages, availableDetailTypes, false);
return new MultiRaceLeaderboardSettingsDialogComponent(settings, namesOfRaceColumns, stringMessages,
availableDetailTypes, canBoatInfoBeShown);
}
@Override
@@ -28,7 +28,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings {
public MultiRaceLeaderboardSettings() {
super();
super(false);
}
public MultiRaceLeaderboardSettings(Collection<DetailType> maneuverDetailsToShow,
@@ -48,6 +48,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings {
}
public MultiRaceLeaderboardSettings(Iterable<String> namesOfRaceColumnsToShow) {
super(false);
this.namesOfRaceColumnsToShow.setValues(namesOfRaceColumnsToShow);
}
@@ -63,6 +64,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings {
newSettings.showAddedScores.setValue(this.isShowAddedScores());
newSettings.showCompetitorShortNameColumn.setValue(this.isShowCompetitorShortNameColumn());
newSettings.showCompetitorFullNameColumn.setValue(this.isShowCompetitorFullNameColumn());
newSettings.showCompetitorBoatInfoColumn.setValue(this.isShowCompetitorBoatInfoColumn());
newSettings.isShowCompetitorNationality.setValue(this.isShowCompetitorNationality());
newSettings.namesOfRaceColumnsToShow.setValues(this.getNamesOfRaceColumnsToShow());
newSettings.namesOfRaceColumnsToShow.setDefaultValues(namesOfRaceColumns);
@@ -9,8 +9,10 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class OverallLeaderboardSettingsDialogComponent extends MultiRaceLeaderboardSettingsDialogComponent {
public OverallLeaderboardSettingsDialogComponent(MultiRaceLeaderboardSettings initialSettings, List<String> allRaceColumnNames, StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
super(initialSettings, allRaceColumnNames, stringMessages, availableDetailTypes, false);
public OverallLeaderboardSettingsDialogComponent(MultiRaceLeaderboardSettings initialSettings,
List<String> allRaceColumnNames, StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
super(initialSettings, allRaceColumnNames, stringMessages, availableDetailTypes,
/* canBoatInfoBeShown */ false);
}
@Override
@@ -10,15 +10,16 @@ public class SingleRaceLeaderboardSettings extends LeaderboardSettings {
private BooleanSetting showRaceRankColumn;
public SingleRaceLeaderboardSettings() {
super();
public SingleRaceLeaderboardSettings(boolean showCompetitorBoatInfoColumnDefault) {
super(showCompetitorBoatInfoColumnDefault);
}
public SingleRaceLeaderboardSettings(Collection<DetailType> maneuverDetailsToShow,
Collection<DetailType> legDetailsToShow, Collection<DetailType> raceDetailsToShow,
Collection<DetailType> overallDetailsToShow, Long delayBetweenAutoAdvancesInMilliseconds,
boolean showAddedScores, boolean showCompetitorShortNameColumn,
boolean showCompetitorFullNameColumn, boolean showCompetitorBoatInfoColumn, boolean isCompetitorNationalityColumnVisible,boolean showRaceRankColumn) {
boolean showAddedScores, boolean showCompetitorShortNameColumn, boolean showCompetitorFullNameColumn,
boolean showCompetitorBoatInfoColumn, boolean isCompetitorNationalityColumnVisible,
boolean showRaceRankColumn) {
super(maneuverDetailsToShow, legDetailsToShow, raceDetailsToShow, overallDetailsToShow,
delayBetweenAutoAdvancesInMilliseconds, showAddedScores, showCompetitorShortNameColumn,
showCompetitorFullNameColumn, showCompetitorBoatInfoColumn, isCompetitorNationalityColumnVisible);
@@ -13,11 +13,11 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
public class SingleRaceLeaderboardSettingsDialogComponent
extends LeaderboardSettingsDialogComponent<SingleRaceLeaderboardSettings> {
protected CheckBox showRaceRankColumn;
private CheckBox showRaceRankColumn;
public SingleRaceLeaderboardSettingsDialogComponent(SingleRaceLeaderboardSettings initialSettings,
StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
super(initialSettings, stringMessages, availableDetailTypes, true /*canBoatInfoBeShown*/);
super(initialSettings, stringMessages, availableDetailTypes, /* canBoatInfoBeShown */ true);
}
@Override
@@ -33,7 +33,8 @@ public class SingleRaceLeaderboardSettingsDialogComponent
1000l * (delayBetweenAutoAdvancesValue == null ? 0l : delayBetweenAutoAdvancesValue.longValue()),
/* showAddedScores */ showAddedScoresCheckBox.getValue().booleanValue(),
showCompetitorShortNameColumnCheckBox.getValue(), showCompetitorFullNameColumnCheckBox.getValue(),
showCompetitorBoatInfoColumnCheckBox.getValue(), isCompetitorNationalityColumnVisible.getValue(), showRaceRankColumn.getValue());
showCompetitorBoatInfoColumnCheckBox.getValue(), isCompetitorNationalityColumnVisible.getValue(),
showRaceRankColumn.getValue());
return newSettings;
}
@@ -25,6 +25,7 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IntegerBox;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.ListDataProvider;
@@ -62,6 +63,9 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
private final ListBox previousConfigurationsComboBox;
private final TextBox eventIdBox;
private final TextBox jsonUrlBox;
private final TextBox updateURLBox;
private final TextBox updateUsernameBox;
private final PasswordTextBox updatePasswordBox;
private final TextBox hostnameTextbox;
private final IntegerBox portIntegerbox;
private final List<SwissTimingRaceRecordDTO> availableSwissTimingRaces = new ArrayList<SwissTimingRaceRecordDTO>();
@@ -75,22 +79,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
RegattaRefresher regattaRefresher, StringMessages stringConstants) {
super(sailingService, regattaRefresher, errorReporter, true, stringConstants);
this.errorReporter = errorReporter;
VerticalPanel mainPanel = new VerticalPanel();
this.setWidget(mainPanel);
mainPanel.setWidth("100%");
CaptionPanel captionPanelConnections = new CaptionPanel(stringConstants.connections());
mainPanel.add(captionPanelConnections);
VerticalPanel verticalPanel = new VerticalPanel();
captionPanelConnections.setContentWidget(verticalPanel);
captionPanelConnections.setStyleName("bold");
Grid connectionsGrid = new Grid(6, 2);
Grid connectionsGrid = new Grid(9, 2);
verticalPanel.add(connectionsGrid);
previousConfigurations = new HashMap<String, SwissTimingConfigurationDTO>();
previousConfigurationsComboBox = new ListBox();
connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox);
@@ -107,10 +105,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
}
});
fillConfigurations();
jsonUrlBox = new TextBox();
jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":"));
connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox);
@@ -125,6 +119,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
}
});
jsonUrlBox = new TextBox();
jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(2, 0, new Label(stringMessages.manage2SailEventURLBox() + ":"));
connectionsGrid.setWidget(2, 1, jsonUrlBox);
jsonUrlBox.addChangeHandler(event -> {
@@ -142,8 +138,23 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
connectionsGrid.setWidget(4, 0, new Label(stringMessages.manage2SailPort() + ":"));
connectionsGrid.setWidget(4, 1, portIntegerbox);
updateURLBox = new TextBox();
updateURLBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(5, 0, new Label(stringMessages.swissTimingUpdateURL() + ":"));
connectionsGrid.setWidget(5, 1, updateURLBox);
updateUsernameBox = new TextBox();
updateUsernameBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(6, 0, new Label(stringMessages.swissTimingUpdateUsername() + ":"));
connectionsGrid.setWidget(6, 1, updateUsernameBox);
updatePasswordBox = new PasswordTextBox();
updatePasswordBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(7, 0, new Label(stringMessages.swissTimingUpdatePassword() + ":"));
connectionsGrid.setWidget(7, 1, updatePasswordBox);
Button btnListRaces = new Button(stringConstants.listRaces());
connectionsGrid.setWidget(5, 1, btnListRaces);
connectionsGrid.setWidget(8, 1, btnListRaces);
btnListRaces.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
@@ -459,6 +470,9 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
private void fillRaces(final SailingServiceAsync sailingService) {
final String jsonUrl = jsonUrlBox.getValue();
final String updateURL = updateURLBox.getValue();
final String updateUsername = updateUsernameBox.getValue();
final String updatePassword = updatePasswordBox.getValue();
sailingService.getRacesOfSwissTimingEvent(jsonUrl, new AsyncCallback<SwissTimingEventRecordDTO>() {
@Override
public void onFailure(Throwable caught) {
@@ -469,21 +483,18 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
@Override
public void onSuccess(final SwissTimingEventRecordDTO result) {
availableSwissTimingRaces.clear();
if (result != null)
if (result != null) {
availableSwissTimingRaces.addAll(result.races);
}
raceList.getList().clear();
raceList.getList().addAll(availableSwissTimingRaces);
filterablePanelEvents.getTextBox().setText(null);
filterablePanelEvents.updateAll(result.races);
// store a successful configuration in the database for later retrieval
final String hostname = result.trackingDataHost;
final Integer port = result.trackingDataPort;
final String configName = result.eventName;
sailingService.storeSwissTimingConfiguration(configName, jsonUrl, hostname, port,
sailingService.storeSwissTimingConfiguration(configName, jsonUrl, hostname, port, updateURL, updateUsername, updatePassword,
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
@@ -495,10 +506,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
public void onSuccess(Void voidResult) {
// refresh list of previous configurations
SwissTimingConfigurationDTO stConfig = new SwissTimingConfigurationDTO(configName, jsonUrl,
hostname, port);
hostname, port, updateURL, updateUsername, updatePassword);
if (previousConfigurations.put(stConfig.getName(), stConfig) == null) {
previousConfigurationsComboBox.addItem(stConfig.getName());
}
for (int i=0; i<previousConfigurationsComboBox.getItemCount(); i++) {
if (previousConfigurationsComboBox.getValue(i).equals(stConfig.getName())) {
previousConfigurationsComboBox.setSelectedIndex(i);
break;
}
}
updatePanelFromSelectedStoredConfiguration();
}
});
@@ -507,8 +524,11 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
}
private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) {
String hostname = hostnameTextbox.getValue();
Integer port = portIntegerbox.getValue();
final String hostname = hostnameTextbox.getValue();
final Integer port = portIntegerbox.getValue();
final String updateURL = updateURLBox.getValue();
final String updateUsername = updateUsernameBox.getValue();
final String updatePassword = updatePasswordBox.getValue();
final List<SwissTimingRaceRecordDTO> selectedRaces = new ArrayList<SwissTimingRaceRecordDTO>();
for (final SwissTimingRaceRecordDTO race : this.raceList.getList()) {
if (raceTable.getSelectionModel().isSelected(race)) {
@@ -526,7 +546,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
sailingService.trackWithSwissTiming(
/* regattaToAddTo */ regattaIdentifier,
selectedRaces, hostname, port, trackWind, correctWindByDeclination,
useInternalMarkPassingAlgorithm, new AsyncCallback<Void>() {
useInternalMarkPassingAlgorithm, updateURL, updateUsername, updatePassword, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to register races " + selectedRaces + " for tracking: "
@@ -549,6 +569,9 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
hostnameTextbox.setValue(stConfig.getHostname());
portIntegerbox.setValue(stConfig.getPort());
jsonUrlBox.setValue(stConfig.getJsonURL());
updateURLBox.setValue(stConfig.getUpdateURL());
updateUsernameBox.setValue(stConfig.getUpdateUsername());
updatePasswordBox.setValue(stConfig.getUpdatePassword());
}
}
}
@@ -28,6 +28,7 @@ import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -79,7 +80,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private TextBox jsonURLTextBox;
private TextBox tracTracUpdateURITextBox;
private TextBox tractracUsernameTextBox;
private TextBox tractracPasswordTextBox;
private PasswordTextBox tractracPasswordTextBox;
private Label loadingMessageLabel;
private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
@@ -199,7 +200,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
grid.setWidget(6, 1, tractracUsernameTextBox);
// TracTrac Password
tractracPasswordTextBox = new TextBox();
tractracPasswordTextBox = new PasswordTextBox();
tractracPasswordTextBox.ensureDebugId("TracTracPasswordTextBox");
tractracPasswordTextBox.setVisibleLength(40);
@@ -222,10 +223,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}
});
grid.setWidget(9, 1, listRacesButton);
loadingMessageLabel = new Label();
grid.setWidget(9, 2, loadingMessageLabel);
return connectionsPanel;
}
@@ -61,15 +61,16 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
controlsPanel.add(setStartTimeButton);
selectedRaceContentPanel.add(controlsPanel);
raceDataGrid = new Grid(6,2);
raceDataGrid = new Grid(7, 2);
this.selectedRaceContentPanel.add(raceDataGrid);
raceDataGrid.setText(0, 0, stringMessages.startTime()+":");
raceDataGrid.setText(1, 0, stringMessages.endTime()+":");
raceDataGrid.setText(2, 0, stringMessages.duration()+":");
raceDataGrid.setText(3, 0, stringMessages.startOfTracking()+":");
raceDataGrid.setText(4, 0, stringMessages.endOfTracking()+":");
raceDataGrid.setText(5, 0, stringMessages.delayForLiveMode()+" (ms):");
raceDataGrid.setText(2, 0, stringMessages.finishingTime()+":");
raceDataGrid.setText(3, 0, stringMessages.duration()+":");
raceDataGrid.setText(4, 0, stringMessages.startOfTracking()+":");
raceDataGrid.setText(5, 0, stringMessages.endOfTracking()+":");
raceDataGrid.setText(6, 0, stringMessages.delayForLiveMode()+" (ms):");
}
@Override
@@ -87,26 +88,32 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
} else {
raceDataGrid.setText(1, 1, "");
}
if(selectedRaceDTO.startOfRace != null && selectedRaceDTO.endOfRace != null) {
Date duration = new Date(selectedRaceDTO.endOfRace.getTime() - selectedRaceDTO.startOfRace.getTime());
raceDataGrid.setText(2, 1, durationFormatter.render(duration));
if (selectedRaceDTO.raceFinishedTime != null) {
raceDataGrid.setText(2, 1, dateFormatter.render(selectedRaceDTO.raceFinishedTime) + " "
+ timeFormatter.render(selectedRaceDTO.raceFinishedTime));
} else {
raceDataGrid.setText(2, 1, "");
}
if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.startOfTracking != null) {
raceDataGrid.setText(3, 1, dateFormatter.render(selectedRaceDTO.trackedRace.startOfTracking) + " "
+ timeFormatter.render(selectedRaceDTO.trackedRace.startOfTracking));
if(selectedRaceDTO.startOfRace != null && selectedRaceDTO.endOfRace != null) {
Date duration = new Date(selectedRaceDTO.endOfRace.getTime() - selectedRaceDTO.startOfRace.getTime());
raceDataGrid.setText(3, 1, durationFormatter.render(duration));
} else {
raceDataGrid.setText(3, 1, "");
}
if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.endOfTracking != null) {
raceDataGrid.setText(4, 1, dateFormatter.render(selectedRaceDTO.trackedRace.endOfTracking) + " "
+ timeFormatter.render(selectedRaceDTO.trackedRace.endOfTracking));
if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.startOfTracking != null) {
raceDataGrid.setText(4, 1, dateFormatter.render(selectedRaceDTO.trackedRace.startOfTracking) + " "
+ timeFormatter.render(selectedRaceDTO.trackedRace.startOfTracking));
} else {
raceDataGrid.setText(4, 1, "");
}
if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.endOfTracking != null) {
raceDataGrid.setText(5, 1, dateFormatter.render(selectedRaceDTO.trackedRace.endOfTracking) + " "
+ timeFormatter.render(selectedRaceDTO.trackedRace.endOfTracking));
} else {
raceDataGrid.setText(5, 1, "");
}
if (selectedRaceDTO.trackedRace != null) {
raceDataGrid.setText(5, 1, "" + selectedRaceDTO.trackedRace.delayToLiveInMs);
raceDataGrid.setText(6, 1, "" + selectedRaceDTO.trackedRace.delayToLiveInMs);
}
}
}
@@ -146,8 +146,9 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception;
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname, int port,
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception;
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname,
int port, boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm,
String updateURL, String updateUsername, String updatePassword) throws Exception;
void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable<SwissTimingReplayRaceDTO> replayRaces,
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm);
@@ -286,7 +287,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
SwissTimingEventRecordDTO getRacesOfSwissTimingEvent(String eventJsonURL) throws Exception;
void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port);
void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword);
String[] getCountryCodes();
@@ -163,7 +163,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs,
String hostname, int port, boolean trackWind, boolean correctWindByDeclination,
boolean useInternalMarkPassingAlgorithm, AsyncCallback<Void> asyncCallback);
boolean useInternalMarkPassingAlgorithm, String updateURL, String updateUsername, String updatePassword, AsyncCallback<Void> asyncCallback);
void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable<SwissTimingReplayRaceDTO> replayRaces,
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm,
@@ -334,7 +334,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getRacesOfSwissTimingEvent(String eventJsonUrl, AsyncCallback<SwissTimingEventRecordDTO> asyncCallback);
void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
AsyncCallback<Void> asyncCallback);
String updateURL, String updateUsername, String updatePassword, AsyncCallback<Void> asyncCallback);
void getCountryCodes(AsyncCallback<String[]> callback);
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff. Show more