diff --git a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF index 904e059fb74..84eb7093503 100644 --- a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF @@ -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, diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java index 82aabb4338f..e1d65f16d70 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java @@ -21,7 +21,7 @@ public class RibDashboardEntryPoint extends AbstractEntryPoint { 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); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java index 56c672e0951..bb4caa36a73 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java @@ -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; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java index 787fe700495..f5d3c783b83 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java @@ -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 starts; - private List pageChangeListener; + private final List starts; + private final List 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); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java index 497dc402680..6c2442ab7a4 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java @@ -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); diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties index 038c4898bc2..22d67d92adb 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties @@ -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 diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties index 6d9e3781cb4..3c9baf274ae 100755 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties @@ -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 diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties index 153c78f1b5f..af7b95d25a7 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties @@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=最高速度までの主曲線進 PercentageOfMainCurveProgressUntilMaxTurningRate=回転率最大速度までの主曲線進行の比率 (%) AbsTwaAtManeuverMiddle=マニューバー中間での絶対 TWA GpsSamplingRate=GPS サンプリング率 +RaceDuration=レース持続時間 diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties index 48c61759887..46904285579 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties @@ -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 diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties index bc36275620f..01ef682a6e3 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties @@ -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=Длительность гонки diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties index 3c6a3559da7..4156966e834 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties @@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=最高速度前主要曲线进度 PercentageOfMainCurveProgressUntilMaxTurningRate=最大回转率速度前主要曲线进度的百分比 AbsTwaAtManeuverMiddle=操作中间的绝对实际风向角 GpsSamplingRate=GPS 采样率 +RaceDuration=比赛轮次持续时间 diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index a5d8736439f..b20c3ac4b5a 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -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"), diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/MaxPointsReason.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/MaxPointsReason.java index 8feb72a69a2..b868a3623c3 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/MaxPointsReason.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/MaxPointsReason.java @@ -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; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java index 0af8c31849b..3b8053ea0b5 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java @@ -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 diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java index b200b547f9d..74818b821de 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java @@ -67,10 +67,8 @@ public abstract class PassAwareRaceLogAnalyzerTest T createEvent(Class type, long milliseconds) { - return createEvent(/* priority */ 0, type, milliseconds); + final T result = createEvent(/* priority */ 0, type, milliseconds); + return result; } protected static T createEvent(int priority, Class type, long milliseconds) { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java index 288fa7f2f0f..b564f94ac9c 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java @@ -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 { +public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest> { 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(event.getNextStatus(), now)); } @Override @@ -49,38 +65,46 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest(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 { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/state/test/RaceStateTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/state/test/RaceStateTest.java index a1d5062a69a..357ae765a9e 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/state/test/RaceStateTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/state/test/RaceStateTest.java @@ -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); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java index 5c7410afa56..bb5d74b37e9 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java @@ -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 { - +/** + * 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> { public interface Clock { TimePoint now(); } @@ -24,26 +36,27 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { 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 statusTypesToIgnoreIfEventNotValidYet = new HashSet<>(Arrays.asList( + new RaceLogRaceStatus[] { RaceLogRaceStatus.FINISHED, RaceLogRaceStatus.FINISHING })); @Override - protected RaceLogRaceStatus performAnalysis() { + protected Pair performAnalysis() { ArrayListNavigableSet statusEvents = new ArrayListNavigableSet<>( RaceLogRaceStatusEventComparator.INSTANCE); for (RaceLogEvent event : getPassEvents()) { @@ -51,21 +64,42 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { statusEvents.add((RaceLogRaceStatusEvent) event); } } + final TimePoint now = clock.now(); + final EventDispatcher eventDispatcher = new EventDispatcher(now, racingProcedure); + Set 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 { if (startTime == null) { nextStatus = RaceLogRaceStatus.PRESCHEDULED; } else { - TimePoint now = clock.now(); if (racingProcedure.isStartphaseActive(startTime, now)) { nextStatus = RaceLogRaceStatus.STARTPHASE; } else if (now.before(startTime)) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateEventScheduler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateEventScheduler.java index 0129123d77b..3637f3cdb3d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateEventScheduler.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateEventScheduler.java @@ -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 stateEvents); + void scheduleStateEvents(Iterable stateEvents); void unscheduleStateEvent(RaceStateEvents stateEventName); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java index a20fc0ad4ea..7d09c0c9ad5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java @@ -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.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.emptyMap(), /* update */ true); + Collections.emptyMap()); this.author = author; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java index 67d7140fbf9..accf15e0227 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java @@ -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> 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. emptyMap()); } + + public static ReadonlyRaceState getOrCreate(RaceLogResolver raceLogResolver, RaceLog raceLog, + SimpleRaceLogIdentifier forRaceLogIdentifier, + Map dependentRaceStates) { + WeakIdentityHashMap 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 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 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 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 true, the race state will be updated whenever the underlying raceLog - * changes. */ protected ReadonlyRaceStateImpl(RaceLogResolver raceLogResolver, RaceLog raceLog, SimpleRaceLogIdentifier forRaceLogIdentifier, Clock analyzersClock, RacingProcedureFactory procedureFactory, - final Map dependentRaceStates, boolean update) { + final Map 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 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 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 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 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); } - } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java index 92613aa02d7..323f21ea714 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java @@ -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 createStartStateEvents(TimePoint startTime); + /** * This method is called by the framework. You don't need to call it. */ diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java index ffea26c734a..da6f0efee9d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java @@ -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 createStartStateEvents(TimePoint startTime) { + public Iterable createStartStateEvents(TimePoint startTime) { return Arrays.asList(new RaceStateEventImpl(startTime, RaceStateEvents.START)); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java index ea009a6554f..7be1ec4160b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java @@ -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 createStartStateEvents(TimePoint startTime) { + public Iterable createStartStateEvents(TimePoint startTime) { return Arrays. asList(new RaceStateEventImpl(startTime.minus(startPhaseAPDownInterval), RaceStateEvents.ESS_AP_DOWN), new RaceStateEventImpl(startTime.minus(startPhaseESSThreeUpInterval), RaceStateEvents.ESS_THREE_UP), new RaceStateEventImpl(startTime.minus(startPhaseESSTwoUpInterval), diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java index fddb5aad31a..5daae5a52ec 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java @@ -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 createStartStateEvents(TimePoint startTime) { + public Iterable createStartStateEvents(TimePoint startTime) { return Arrays. asList( new RaceStateEventImpl(startTime.minus(startPhaseClassOverGolfUpInterval), RaceStateEvents.GATE_CLASS_OVER_GOLF_UP), diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java index 1bccfd7b31b..017d0065bf8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java @@ -276,8 +276,6 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i return configuration; } - protected abstract Collection createStartStateEvents(TimePoint startTime); - @Override public void onStartTimeChanged(ReadonlyRaceState state) { getChangedListeners().onActiveFlagsChanged(this); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java index a7098a8d0e5..7dab122f8fb 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java @@ -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 createStartStateEvents(TimePoint startTime) { + public Iterable 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. asList( diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/ConfigurableStartModeFlagRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/ConfigurableStartModeFlagRacingProcedureImpl.java index 1e85749df58..684fe233401 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/ConfigurableStartModeFlagRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/ConfigurableStartModeFlagRacingProcedureImpl.java @@ -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(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/RRS26RacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/RRS26RacingProcedureImpl.java index 198b00ecc63..bb728fda311 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/RRS26RacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/RRS26RacingProcedureImpl.java @@ -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 createStartStateEvents(TimePoint startTime) { + public Iterable createStartStateEvents(TimePoint startTime) { return Arrays. asList( new RaceStateEventImpl(startTime.minus(startPhaseClassUpInterval), RaceStateEvents.RRS26_CLASS_UP), new RaceStateEventImpl(startTime.minus(startPhaseStartModeUpInterval), RaceStateEvents.RRS26_STARTMODE_UP), diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/SWCRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/SWCRacingProcedureImpl.java index 2a72bcddb68..57f5428d08b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/SWCRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/line/impl/SWCRacingProcedureImpl.java @@ -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 createStartStateEvents(TimePoint startTime) { + public Iterable createStartStateEvents(TimePoint startTime) { return Arrays. 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), diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java index d90bffeb0ca..26a4a5053ff 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java @@ -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, diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java index 7d6b0d260f8..71d8b5d3a54 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java @@ -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); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java index a034257ea2b..5c8dca04bae 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java @@ -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 diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java index b8036a8adc0..c9a4979fa1f 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java @@ -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 deviceIds, MarkType markType); RaceDefinition createRaceDefinition(Regatta regatta, String swissTimingRaceID, Map competitorsAndBoats, List 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; } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java index 10f4669c7f5..cdeaf3a7368 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java @@ -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> 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 races); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java index e275b3573c8..bcb05fe84c4 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java @@ -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); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java index 293eff3c8be..09c500c6581 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java @@ -14,4 +14,10 @@ public interface SwissTimingConfiguration { String getHostname(); Integer getPort(); + + String getUpdateURL(); + + String getUpdateUsername(); + + String getUpdatePassword(); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java index 8badef20f87..33ea647eaa3 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java @@ -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); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 5c493877887..758369f0b5b 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -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); } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java index 9d386a29c6b..a7978a8d466 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java @@ -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); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java index c16ca7a5b71..8c1087e2d11 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java @@ -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; + } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java index 9d7b4b82cb1..70236e46404 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java @@ -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 diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index e963f7c5033..6d5bdcf9076 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -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(); - 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) { diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java index de24889e36e..e14df771fa1 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java @@ -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; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackedRaceStartTimeUpdateForDependentStartTimeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackedRaceStartTimeUpdateForDependentStartTimeTest.java index 4de312c268a..559d897a47a 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackedRaceStartTimeUpdateForDependentStartTimeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackedRaceStartTimeUpdateForDependentStartTimeTest.java @@ -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]); } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 1d6ccf2ae2b..da49ca9a085 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -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, diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java index bb816ebb05c..2efecd731d8 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java @@ -251,17 +251,19 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue sidelines, Consumer 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 sidelines, + Consumer 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; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java index c2167e8d888..18984574043 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java @@ -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 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 candidates = domainFactory.getControlsForCourseArea(tractracRace.getEvent(), tractracRace.getCourseArea()); final CourseBase result = new CourseDataImpl(courseDesign.getName()); int zeroBasedPosition = 0; diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index 6f6d9a68bbe..8d5cf1bb0a9 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -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 diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java index b81e8e1b1ff..be6b6a144c7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java @@ -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); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java index 1d268d47ff9..92807bed0c6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java @@ -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 raceIdentifiers; - private Map raceLogs; - + private ConcurrentMap raceLogs; + private transient RaceLogStore raceLogStore; private RegattaLikeIdentifier regattaLikeParent; public AbstractRaceColumn() { this.trackedRaces = new TrackedRaces(); this.raceIdentifiers = new HashMap(); - this.raceLogs = new HashMap(); + this.raceLogs = new ConcurrentHashMap(); } @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); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java index 01e73aeacfe..7444495aabc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java @@ -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 getCompetitorDTOList(List> competitors) { List result = new ArrayList<>(); for (Pair 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()); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index a9ba715c9e4..fd6580496be 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -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()); } /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseDesignUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseDesignUpdateHandler.java new file mode 100755 index 00000000000..1ad1a82c1c5 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseDesignUpdateHandler.java @@ -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 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; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index a9e4c107e5b..fc7e042729b 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -188,8 +188,9 @@ DynamicTrackedRace, GPSTrackListener { 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); } } }; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index aaa83b444ec..bcc66c88fa7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -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 diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracFinishTimeUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java similarity index 84% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracFinishTimeUpdateHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java index dfae3907cfa..e5c027ec562 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracFinishTimeUpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java @@ -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; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceAbortedHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java similarity index 78% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceAbortedHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java index 1963e3f4dc5..50b723a4405 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceAbortedHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java @@ -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 diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeResetHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java similarity index 73% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeResetHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java index dc46e1b23a9..941d74e20ee 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeResetHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java @@ -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 diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java similarity index 78% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeUpdateHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java index eaef290e628..b33650f08d4 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeUpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java @@ -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 additionalParameters = new HashMap(); additionalParameters.put(FIELD_RACE_START_TIME, String.valueOf(newStartTime.asMillis())); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 2760bf877ca..86501ccd376 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -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); } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java similarity index 83% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java index f87d3c8a814..97cd7a4a61d 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java @@ -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 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 getDefaultParametersAsNewList() { final List 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 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; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/UpdateResponse.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponse.java similarity index 63% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/UpdateResponse.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponse.java index 2a3a9193096..49ea7846f60 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/UpdateResponse.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponse.java @@ -1,7 +1,6 @@ -package com.sap.sailing.domain.tractracadapter; +package com.sap.sailing.domain.tracking.impl; public interface UpdateResponse { - String getStatus(); String getMessage(); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseDeserializer.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseDeserializer.java similarity index 86% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseDeserializer.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseDeserializer.java index 496f22b4f09..75bd2dabe92 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseDeserializer.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseDeserializer.java @@ -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; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseImpl.java similarity index 78% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseImpl.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseImpl.java index 9c1eef86ce1..109414350ec 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseImpl.java @@ -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 { diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index af633c042ae..74bfa2b601f 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -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; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java index 3debae7a209..2177bfcbf95 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java @@ -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) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java index b03b79dc71f..b24e97e74af 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java @@ -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; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListSteps.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListSteps.java index aba27539ffb..30d3ba92244 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListSteps.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListSteps.java @@ -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()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListStepsBody.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListStepsBody.java index f1ce515f0bb..a94a09658ef 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListStepsBody.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListStepsBody.java @@ -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(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/racestab/RegattaRacesPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/racestab/RegattaRacesPlace.java index 574760b72cb..4ae3f62a362 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/racestab/RegattaRacesPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/racestab/RegattaRacesPlace.java @@ -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 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 { @Override protected RegattaRacesPlace getRealPlace(EventContext context) { - return new RegattaRacesPlace(context); + return new RegattaRacesPlace(context, null); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java index 7a31ba34000..5bac0e34c98 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java @@ -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) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.gss index 282125f546d..e3911716fb0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.gss @@ -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; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.java index 53cc73e4a0e..a13a5fd4120 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventSteps.java @@ -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 { @@ -22,8 +25,10 @@ public class EventSteps extends Composite implements RefreshableWidget> racesNavigationFactory; - public EventSteps(RegattaMetadataDTO regatta) { + public EventSteps(RegattaMetadataDTO regatta, Function> 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 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)); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.java index 058d4bd75b9..9728319df95 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.java @@ -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 { } + @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()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.ui.xml index 7cece0b816b..2ac9004e380 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsPhase.ui.xml @@ -6,11 +6,13 @@ -
-
- -
-
-
+ +
+
+ +
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsResources.java index 1bc6a0dfbd2..46e49f87206 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventsteps/EventStepsResources.java @@ -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(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.java index 45a344513b3..9c0c0e3be8e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.java @@ -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 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)); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/RegattaRacesNavigationProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/RegattaRacesNavigationProvider.java index d4f921b1a54..8c3655e1abf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/RegattaRacesNavigationProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/RegattaRacesNavigationProvider.java @@ -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); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java index 0ac77756ff0..96e653ce512 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java @@ -247,6 +247,12 @@ public abstract class AbstractEventActivity 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); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaOverviewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaOverviewImpl.java index 83403e8d3a8..afe0f4b5e58 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaOverviewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaOverviewImpl.java @@ -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> racesNavigationFactory = prefSeriesName -> currentPresenter + .getRegattaRacesNavigation(getRegattaId(), prefSeriesName); + eventStepsUi = new EventSteps(currentPresenter.getRegatta(), racesNavigationFactory); refreshManager.add(eventStepsUi, new GetRegattaWithProgressAction(getEventId(), getRegattaId())); container.add(eventStepsUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesActivity.java index bac62f55092..52165919c9a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesActivity.java @@ -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 impl initQuickfinder(view, true); return view; } + + @Override + public Optional getPreferredSeriesName() { + return getPlace().getPreferredSeriesName(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesView.java index e457c2af812..eca0d41c55d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesView.java @@ -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 getPreferredSeriesName(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesViewImpl.java index 1aca7f4fea0..89bc76e0473 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesViewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/races/RacesViewImpl.java @@ -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 implements RacesView { @@ -71,6 +75,13 @@ public class RacesViewImpl extends AbstractEventView implem protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) { return currentPresenter.getRaceViewerURL(raceMetadata, mode); } + + @Override + public void setData(ListResult data) { + super.setData(data); + final Consumer scrollCommand = regattaCompetitionUi::scrollToSeries; + Scheduler.get().scheduleDeferred(() -> currentPresenter.getPreferredSeriesName().ifPresent(scrollCommand)); + } } private class RacesViewImplFilterPresenter extends FilterPresenter { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java index 0cda630cbc8..2e313562472 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java @@ -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() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardPanelLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardPanelLifecycle.java index f533c6d71e8..381b304b9c5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardPanelLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardPanelLifecycle.java @@ -12,7 +12,7 @@ public abstract class LeaderboardPanelLifecycle protected final Iterable availableDetailTypes; - public LeaderboardPanelLifecycle(StringMessages stringMessages, Iterable availableDetailTypes) { + protected LeaderboardPanelLifecycle(StringMessages stringMessages, Iterable availableDetailTypes) { this.stringMessages = stringMessages; this.availableDetailTypes = availableDetailTypes; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettings.java index 99ad8b70c2e..1409a1046d9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettings.java @@ -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); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java index 285bfba3f93..1b02fe8b583 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java @@ -46,7 +46,8 @@ public abstract class LeaderboardSettingsDialogComponent availableDetailTypes; - public LeaderboardSettingsDialogComponent(T initialSettings, StringMessages stringMessages, Iterable availableDetailTypes, boolean canBoatInfoBeShown) { + protected LeaderboardSettingsDialogComponent(T initialSettings, StringMessages stringMessages, + Iterable availableDetailTypes, boolean canBoatInfoBeShown) { this.initialSettings = initialSettings; this.stringMessages = stringMessages; this.canBoatInfoBeShown = canBoatInfoBeShown; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsFactory.java index 1dd0fb242a0..1fa2202485b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsFactory.java @@ -44,7 +44,7 @@ public class LeaderboardSettingsFactory { } public SingleRaceLeaderboardSettings createNewSettingsWithCustomRaceDetails(List raceDetailsToShow) { - SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(); + SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(false); return new SingleRaceLeaderboardSettings( defaultSettings.getManeuverDetailsToShow(), defaultSettings.getLegDetailsToShow(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardPanelLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardPanelLifecycle.java index 4e45d743d51..29a5b5a89e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardPanelLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardPanelLifecycle.java @@ -11,11 +11,13 @@ import com.sap.sse.common.settings.generic.support.SettingsUtil; public class MultiRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecycle { protected final List namesOfRaceColumns; + private final boolean canBoatInfoBeShown; public MultiRaceLeaderboardPanelLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages, Iterable availableDetailTypes) { super(stringMessages, availableDetailTypes); this.namesOfRaceColumns = leaderboard != null ? leaderboard.getNamesOfRaceColumns() : new ArrayList(); + 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 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java index e166d36801b..fc9714d71fd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java @@ -28,7 +28,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings { public MultiRaceLeaderboardSettings() { - super(); + super(false); } public MultiRaceLeaderboardSettings(Collection maneuverDetailsToShow, @@ -48,6 +48,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings { } public MultiRaceLeaderboardSettings(Iterable 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); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/OverallLeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/OverallLeaderboardSettingsDialogComponent.java index a66666974f9..be79914e79e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/OverallLeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/OverallLeaderboardSettingsDialogComponent.java @@ -9,8 +9,10 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class OverallLeaderboardSettingsDialogComponent extends MultiRaceLeaderboardSettingsDialogComponent { - public OverallLeaderboardSettingsDialogComponent(MultiRaceLeaderboardSettings initialSettings, List allRaceColumnNames, StringMessages stringMessages, Iterable availableDetailTypes) { - super(initialSettings, allRaceColumnNames, stringMessages, availableDetailTypes, false); + public OverallLeaderboardSettingsDialogComponent(MultiRaceLeaderboardSettings initialSettings, + List allRaceColumnNames, StringMessages stringMessages, Iterable availableDetailTypes) { + super(initialSettings, allRaceColumnNames, stringMessages, availableDetailTypes, + /* canBoatInfoBeShown */ false); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettings.java index edf6c10639a..5c85a5d8a37 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettings.java @@ -10,15 +10,16 @@ public class SingleRaceLeaderboardSettings extends LeaderboardSettings { private BooleanSetting showRaceRankColumn; - public SingleRaceLeaderboardSettings() { - super(); + public SingleRaceLeaderboardSettings(boolean showCompetitorBoatInfoColumnDefault) { + super(showCompetitorBoatInfoColumnDefault); } public SingleRaceLeaderboardSettings(Collection maneuverDetailsToShow, Collection legDetailsToShow, Collection raceDetailsToShow, Collection 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); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettingsDialogComponent.java index 3f40d626b88..e911ec0cc11 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/SingleRaceLeaderboardSettingsDialogComponent.java @@ -13,11 +13,11 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; public class SingleRaceLeaderboardSettingsDialogComponent extends LeaderboardSettingsDialogComponent { - protected CheckBox showRaceRankColumn; + private CheckBox showRaceRankColumn; public SingleRaceLeaderboardSettingsDialogComponent(SingleRaceLeaderboardSettings initialSettings, StringMessages stringMessages, Iterable 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; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index 54a122e1cce..8f4fd9b38d0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -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 availableSwissTimingRaces = new ArrayList(); @@ -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(); 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() { @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() { @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 selectedRaces = new ArrayList(); 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() { + useInternalMarkPassingAlgorithm, updateURL, updateUsername, updatePassword, new AsyncCallback() { @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()); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 5b7069b09a8..9864e5ac78b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -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 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; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java index 1432949153e..d7c70fc7ed6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java @@ -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); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index b4c9980a275..6557e7d7b5f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -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 rrs, String hostname, int port, - boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception; + void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, + int port, boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, + String updateURL, String updateUsername, String updatePassword) throws Exception; void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable 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(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 464119e1da5..3cfad95ef83 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -163,7 +163,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, boolean trackWind, boolean correctWindByDeclination, - boolean useInternalMarkPassingAlgorithm, AsyncCallback asyncCallback); + boolean useInternalMarkPassingAlgorithm, String updateURL, String updateUsername, String updatePassword, AsyncCallback asyncCallback); void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, @@ -334,7 +334,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void getRacesOfSwissTimingEvent(String eventJsonUrl, AsyncCallback asyncCallback); void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port, - AsyncCallback asyncCallback); + String updateURL, String updateUsername, String updatePassword, AsyncCallback asyncCallback); void getCountryCodes(AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java index 261db38393a..c4400b452d4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java @@ -2,14 +2,13 @@ package com.sap.sailing.gwt.ui.client; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.sap.sse.gwt.client.EntryPointHelper; import com.sap.sse.gwt.client.ServiceRoutingProvider; /** * Set of convenience factory methods that create a {@link SailingServiceAsync} instance. */ public abstract class SailingServiceHelper { - private final static String moduleBaseURL = GWT.getModuleBaseURL(); - private SailingServiceHelper() { } @@ -41,20 +40,20 @@ public abstract class SailingServiceHelper { * @param sameBundle {@code false} if using {@link SailingService} from a different OSGi bundle (e.g. dashboards), {@code true} otherwise */ public static SailingServiceAsync createSailingServiceInstance(boolean sameBundle, ServiceRoutingProvider routingProvider) { - SailingServiceAsync service = GWT.create(SailingService.class); - ServiceDefTarget serviceToRegister = (ServiceDefTarget) service; - StringBuilder baseURL = new StringBuilder(); - baseURL.append(moduleBaseURL.substring(0, moduleBaseURL.lastIndexOf('/', moduleBaseURL.length() - 2) + 1)); - if (!sameBundle) { - baseURL.append(RemoteServiceMappingConstants.WEB_CONTEXT_PATH).append("/"); - } - - baseURL.append(RemoteServiceMappingConstants.sailingServiceRemotePath); + final SailingServiceAsync service = GWT.create(SailingService.class); + final ServiceDefTarget serviceToRegister = (ServiceDefTarget) service; + final StringBuilder servicePath = new StringBuilder(RemoteServiceMappingConstants.sailingServiceRemotePath); if (routingProvider != null) { - baseURL.append(routingProvider.routingSuffixPath()); + servicePath.append(routingProvider.routingSuffixPath()); + } + + final String servicePathWithRoutingSuffix = servicePath.toString(); + if (sameBundle) { + EntryPointHelper.registerASyncService(serviceToRegister, servicePathWithRoutingSuffix); + } else { + EntryPointHelper.registerASyncService(serviceToRegister, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, servicePathWithRoutingSuffix); } - serviceToRegister.setServiceEntryPoint(baseURL.toString()); return service; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 525437b9511..85abbbf1053 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1442,7 +1442,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String courseGeometry(); String degreesToWind(String diff); String miniLeeaderboardLegendText(); - String currentOfTotal(int current, int total); + String currentOfTotalRaces(int current, @PluralCount(DefaultRule_1_0n.class) int total); String noFinishedRaces(); String racesOverview(); String listFormatLabel(); @@ -2097,6 +2097,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String manage2SailPort(); String allowResizing(); String windFinderSpotCollectionsList(); + String swissTimingUpdateURL(); + String swissTimingUpdateUsername(); + String swissTimingUpdatePassword(); String resizeSuccessfull(); String resizeUnsuccessfull(); String setUpStorageService(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 2f1f1aeae22..725bf262c13 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1429,7 +1429,8 @@ numberOfRacesShort=# Races courseGeometry=Course Geometry degreesToWind={0}° to wind miniLeeaderboardLegendText=Shows the points along with the number of races per competitor -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} races +currentOfTotalRaces[one]={0,number}/{1,number} race noFinishedRaces=There are no finished races yet racesOverview=Races overview listFormatLabel=List format @@ -2090,6 +2091,9 @@ manage2SailEventIdBox=Manage2Sail Event ID manage2SailEventURLBox=Manage2Sail EventURL (json) manage2SailEventIdBoxTooltip=Event ID or a URL that contains the event ID from Manage2Sail manage2SailPort=Port +swissTimingUpdateURL=Update URL (e.g., for start time and course change feedback) +swissTimingUpdateUsername=Username for sending updates +swissTimingUpdatePassword=Password for sending updates allowResizing=Allow resizing resizeSuccessfull=Resizing successfully finished resizeUnsuccessfull=Resizing not possible diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 525602d1956..acf464a41be 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1412,7 +1412,8 @@ numberOfRacesShort=# Rennen courseGeometry=Kursgeometrie degreesToWind={0}° zum Wind miniLeeaderboardLegendText=Es werden die Punkte und die Anzahl Rennen je Team angezeigt -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} Rennen +currentOfTotalRaces[one]={0,number}/{1,number} Rennen noFinishedRaces=Es wurden noch keine Wettfahrten beendet. racesOverview=Wettfahrtübersicht listFormatLabel=Listenansicht @@ -2085,6 +2086,9 @@ manage2SailEventId=Manage2Sail Event ID manage2SailEventURL=Manage2Sail Event URL (json) manage2SailEventIdBoxTooltip=Event ID von Manage 2 Sail oder URL die die Event ID beinhaltet manage2SailPort=Port +swissTimingUpdateURL=Update URL (z.B. für Startzeit-Übertragung) +swissTimingUpdateUsername=Benutzername zum Versenden der Updates +swissTimingUpdatePassword=Passwort zum Versenden der Updates allowResizing=Skalierung erlauben resizeSuccessfull=Skalierung erfolgreich abgeschlossen resizeUnsuccessfull=Skalierung nicht möglich diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties index e28ec429798..822a996829a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Puntuación alta, el ganador obtien scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Calificación en puntos. El ganador obtiene 12 puntos; el segundo, 11 puntos, etc. Si hay un empate, se resuelve a favor del competidor que tiene la mayor puntuación. Si aún persiste el empate, se resuelve mediante la posición obtenida en la última carrera puntuada. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Puntuación alta ESS 2017, el ganador obtiene 12 puntos (u 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Calificación en puntos. El ganador obtiene 12 puntos; el segundo, 11 puntos, etc. Si hay un empate, se resuelve a favor del competidor que tiene la mayor puntuación. Si aún persiste el empate, se resuelve mediante la posición obtenida en la última carrera puntuada. +scoringSchemeLowPointSystemFirstTwoWins=Sistema de puntuación baja, primero con dos victorias en medallas es ganador +scoringSchemeLowPointSystemFirstTwoWinsDescription=Sistema de puntuación baja. El primero en la serie de medallas que gane dos carreras, gana la serie de medallas. Ser el mejor en la serie premedallas cuenta como una victoria para este. scoringSystem=Sistema de puntuación createFlexibleLeaderboard=Crear tabla de clasificación flexible createRegattaLeaderboard=Crear tabla de clasificación de regata @@ -1276,13 +1278,11 @@ uploadSuccessful=Carga correcta fileUploadResult=Carga de archivos: {0} {1}. En caso de error, compruebe que su servicio de alojamiento de archivos está configurado correctamente. removeUploadedFile=Eliminar archivo showCompetitorBoatColumn=Embarcación del competidor -showCompetitorSailIdColumn=ID de vela -showCompetitorSailIdColumnTooltip=Muestra el ID de vela más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorShortNameColumn=Denominación breve de competidor showCompetitorShortNameColumnTooltip=Muestra el nombre breve más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorFullNameColumn=Nombre completo del competidor -alwaysShowCompetitorNationalityColumn=Siempre mostrar la nacionalidad del competidor -alwaysShowCompetitorNationalityColumnTooltip=Muestra ambos, las banderas de país así como las imágenes de competidor, si están disponibles. +showCompetitorNationalityColumn=Mostrar adicionalmente la nacionalidad del competidor +showCompetitorNationalityColumnTooltip=Mostrar las banderas de nacionalidad junto con las imágenes del competidor si están disponibles loadingDimensionValues=Cargando valores de dimensión inviteBuoyTenders=Invitar a balizadores orMultipleEmails=o varios correos electrónicos separados por una coma @@ -1424,7 +1424,6 @@ numberOfRacesShort=Nº regatas courseGeometry=Geometría de la regata degreesToWind={0}° respecto al viento miniLeeaderboardLegendText=Muestra los puntos y el número de carreras por competidor -currentOfTotal={0,number}/{1,number} noFinishedRaces=Todavía no ha finalizado ninguna carrera racesOverview=Resumen de carreras listFormatLabel=Formato de lista @@ -2059,6 +2058,9 @@ multiVideoIdle=Cola de trabajo inactiva multiVideoDoNoAdd=No añadir multiVideoOffsetInput=Offset de vídeo global en milisegundos: multiVideoDescription=El servidor web debe proporcionar una lista de índices (subcarpetas compatibles si están indexadas) para permitir la detección de ficheros. Tras un análisis inicial de los metadatos contenidos en los ficheros mp4, los vídeos que deben añadirse, deben seleccionarse mediante la columna de la casilla de selección a la izquierda. El botón "Añadir audio/vídeo" creará pistas multimedia para todos los ficheros seleccionados y los añadirá a todas las carreras seleccionadas en la columna de la derecha. +jibeAngle=Ángulo de trasluchada +tackAngle=Ángulo de bordo +maneuverAngle=Ángulo de maniobra multiUrlChangeMediaTrack=Ajustar URLs de rastreo múltiple de medios multiUrlChangeReplace=Reemplazar por multiUrlChangeFind=Buscar @@ -2070,6 +2072,11 @@ multiUrlChangeExplain=Este diálogo reemplazará en masa las partes comunes al i resetZoom=Reinicializar zoom lastEvent=Último evento: {0} teaserOverallLinkToolTip=Para visualizar las series generales, haga clic en la esquina amarilla +middleManeuverAngle=Ángulo medio de maniobra +extrapolatedManeuverStartPosition=Posición de inicio de maniobra extrapolada +projectedExtrapolatedManeuverStartPosition=Proyección de posición de inicio de maniobra extrapolada en línea de ángulo medio de maniobra +projectedManeuverEndPosition=Proyección de posición de fin de maniobra en línea de ángulo medio de maniobra +bearingAtManeuverEndPosition=Rumbo en posición de fin de maniobra gender=Sexo manage2SailEventIdBox=ID de evento Manage2Sail manage2SailEventURLBox=URL de evento Manage2Sail (json) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties index be10af53726..1a952f87a36 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Système de points avec bonus, le v scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Score en points. Le vainqueur totalise 12 points, le deuxième 11, etc. En cas d''égalité, la victoire est accordée au concurrent qui a totalisé le plus de points. Si l''égalité persiste, la victoire est accordée en fonction du classement dans la dernière course. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Système de points avec bonus - ESS 2017, le vainqueur obtient 12 points (ou 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Score en points. Le vainqueur totalise 12 points, le deuxième 11, etc. En cas d''égalité, la victoire est accordée au concurrent qui a totalisé le plus de points. Si l''égalité persiste, la victoire est accordée en fonction du classement dans la dernière course. +scoringSchemeLowPointSystemFirstTwoWins=Système de points a minima. Le premier ayant gagné deux médailles gagne la compétition. +scoringSchemeLowPointSystemFirstTwoWinsDescription=Système de points a minima. Le premier ayant gagné deux courses gagne la série médaillée. Si un concurrent a été le meilleur dans la série pré-médailles, cela compte comme une victoire pour la série médaillée. scoringSystem=Système d''attribution des points createFlexibleLeaderboard=Créer palmarès flexible createRegattaLeaderboard=Créer palmarès régate @@ -1276,13 +1278,11 @@ uploadSuccessful=Chargement réussi fileUploadResult=Chargement du fichier : {0} {1}.\nEn cas d''erreur, vérifiez que votre service de stockage de fichier est correctement configuré. removeUploadedFile=Supprimer fichier chargé showCompetitorBoatColumn=Bateau du concurrent -showCompetitorSailIdColumn=ID de voile -showCompetitorSailIdColumnTooltip=Affiche l''ID de voile ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorShortNameColumn=Nom court du concurrent showCompetitorShortNameColumnTooltip=Affiche le nom court ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorFullNameColumn=Nom complet du concurrent -alwaysShowCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent -alwaysShowCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que l''image du concurrent +showCompetitorNationalityColumn=Afficher en plus la nationalité du concurrent +showCompetitorNationalityColumnTooltip=Si disponible, afficher le drapeau national en plus de l''image du concurrent. loadingDimensionValues=Chargement des valeurs de dimension inviteBuoyTenders=Inviter navire baliseur orMultipleEmails=ou plusieurs adresses e-mails séparées par des virgules @@ -1424,7 +1424,6 @@ numberOfRacesShort=Nombre de courses courseGeometry=Géométrie du parcours degreesToWind={0}° au vent miniLeeaderboardLegendText=Affiche les points associés au nombre de courses par concurrent -currentOfTotal={0,number}/{1,number} noFinishedRaces=Aucune course terminée pour l''instant. racesOverview=Synthèse des courses listFormatLabel=Format de liste @@ -2059,6 +2058,9 @@ multiVideoIdle=La réserve de travail est inactive. multiVideoDoNoAdd=Ne pas ajouter multiVideoOffsetInput=Décalage vidéo global en millisecondes : multiVideoDescription=Le serveur Web doit fournir une liste des index (les sous-dossiers sont pris en charge s''ils sont répertoriés) pour permettre la détection de fichier. Après une analyse initiale des métadonnées contenues dans les fichiers MP4, les vidéos à ajouter doivent être sélectionnées en cochant les cases correspondantes dans la colonne de gauche. Le bouton "Ajouter audio/vidéo" va créer des pistes médias pour tous les fichiers sélectionnés, et ajouter ces fichiers à toutes les courses sélectionnées dans la colonne de droite. +jibeAngle=Angle d''amure +tackAngle=Angle de virement de bord +maneuverAngle=Angle de manœuvre multiUrlChangeMediaTrack=Ajuster plusieurs URL de piste média multiUrlChangeReplace=Remplacer par multiUrlChangeFind=Rechercher @@ -2070,6 +2072,11 @@ multiUrlChangeExplain=Cette boîte de dialogue remplacera en masse les élément resetZoom=Réinitialiser le zoom lastEvent=Dernière manifestation : {0} teaserOverallLinkToolTip=Pour afficher l''ensemble des séries, cliquez sur le coin jaune. +middleManeuverAngle=Angle de manœuvre (milieu) +extrapolatedManeuverStartPosition=Position extrapolée du début de la manœuvre +projectedExtrapolatedManeuverStartPosition=Projection de la position extrapolée du début de la manœuvre sur la ligne de l''angle de manœuvre (milieu) +projectedManeuverEndPosition=Projection de la position extrapolée de fin de la manœuvre sur la ligne de l''angle de manœuvre (milieu) +bearingAtManeuverEndPosition=Direction à la position de fin de la manœuvre gender=Sexe manage2SailEventIdBox=ID de manifestation Manage2Sail manage2SailEventURLBox=URL de la manifestation Manage2Sail (json) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index 18e9f91ca5a..6709ff37144 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=高得点方式、1 位が 12 点 ( scoringSchemeHighPointFirstGetsTwelveOrEightDescription=得点数によるスコアです。1 位が 12 点、2 位が 11 点などと得点します。同点となった場合は、(各競技者のレース得点を高い方から比較して) 最多得点の競技者を上位とします。それでも同順位となる場合には、最終レースでの得点でのランクで順位を決定します。 scoringSchemeHighPointFirstGetsTwelveOrEight2017=高得点方式 ESS 2017、1 位が 12 点 (または 8 点) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=得点数によるスコアです。1 位が 12 点、2 位が 11 点などと得点します。同点となった場合は、(各競技者のレース得点を高い方から比較して) 最多得点の競技者を上位とします。それでも同順位となる場合には、最終レースでの得点でのランクで順位を決定します。 +scoringSchemeLowPointSystemFirstTwoWins=低得点方式、メダルシリーズで最初に 2 勝した者が勝者 +scoringSchemeLowPointSystemFirstTwoWinsDescription=低得点方式。メダルシリーズで最初に 2 レースに勝利した者をメダルシリーズの勝者とします。プレメダルシリーズでの最上位をメダルシリーズの 1 勝としてカウントします。 scoringSystem=得点方式 createFlexibleLeaderboard=フレキシブルリーダーボード登録 createRegattaLeaderboard=レガッタリーダーボード登録 @@ -1276,13 +1278,11 @@ uploadSuccessful=正常にアップロード済 fileUploadResult=ファイルアップロード: {0} {1}\nエラー発生の場合は、ファイルストレージサービスが適切に設定されていることを確認してください。 removeUploadedFile=アップロード済ファイルの削除 showCompetitorBoatColumn=競技者の艇 -showCompetitorSailIdColumn=セール ID -showCompetitorSailIdColumnTooltip=セール ID に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorShortNameColumn=競技者の名前 (短) showCompetitorShortNameColumnTooltip=名前 (短) に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorFullNameColumn=競技者氏名 -alwaysShowCompetitorNationalityColumn=競技者国籍を常に表示 -alwaysShowCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示 +showCompetitorNationalityColumn=競技者国籍を追加的に表示 +showCompetitorNationalityColumnTooltip=競技者画像に加え国旗を表示 (利用可能な場合) loadingDimensionValues=次元値ロード中 inviteBuoyTenders=ブイ入札の招待 orMultipleEmails=または複数の電子メールをカンマで区切り @@ -1424,7 +1424,6 @@ numberOfRacesShort=# レース courseGeometry=コース配置 degreesToWind=風と {0}° miniLeeaderboardLegendText=競技者別にレース数とともに得点を表示 -currentOfTotal={0,number}/{1,number} noFinishedRaces=フィニッシュしたレースがまだありません。 racesOverview=レース概要 listFormatLabel=レース一覧 @@ -2059,6 +2058,9 @@ multiVideoIdle=ワークキューが無効です multiVideoDoNoAdd=追加しないでください multiVideoOffsetInput=グローバル動画オフセット (msec): multiVideoDescription=Web サーバは、ファイルディスカバリを可能にするため、インデックスリスト (インデックスが作成されている場合はサブフォルダがサポート済) を用意していることが必要です。MP4 ファイルに含まれているメタデータの初期解析後に、追加する動画を左側のチェックボックス列で選択する必要があります。"音声/動画追加" ボタンにより、選択されているすべてのファイルに対してメディアトラックが作成され、右側の列で選択されているすべてのレースに対してそれらのファイルが追加されます。 +jibeAngle=ジャイブ角 +tackAngle=タック角 +maneuverAngle=マニューバー角 multiUrlChangeMediaTrack=複数メディアトラック URL の調整 multiUrlChangeReplace=置換後の文字列 multiUrlChangeFind=検索 @@ -2070,6 +2072,11 @@ multiUrlChangeExplain=このダイアログはメディアトラック URL 先 resetZoom=拡大/縮小のリセット lastEvent=最終イベント: {0} teaserOverallLinkToolTip=シリーズ全体を参照するには、黄色になっている隅の部分をクリックしてください +middleManeuverAngle=マニューバー角の中心 +extrapolatedManeuverStartPosition=推定マニューバー開始位置 +projectedExtrapolatedManeuverStartPosition=マニューバー角の中心線上に推定マニューバー開始位置を投影 +projectedManeuverEndPosition=マニューバー角の中心線上にマニューバー終了位置を投影 +bearingAtManeuverEndPosition=マニューバー終了位置での方位 gender=性別 manage2SailEventIdBox=Manage2Sail イベント ID manage2SailEventURLBox=Manage2Sail イベント URL (JSON) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties index 78d242a2e15..df38c1bfe0b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Sistema de bônus, o vencedor receb scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Pontuação em pontos. O vencedor pontua 12 pontos, o 2º - 11 pontos, .... Um desempate é efetuado a favor do competidor com a pontuação mais alta, se o empate continuar, então o desempate é efetuado pela posição na última corrida pontuada. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Sistema de bônus ESS 2017, o vencedor recebe 12 pontos (ou 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Pontuação em pontos. O vencedor pontua 12 pontos, o 2º - 11 pontos, .... Um desempate é efetuado a favor do competidor com a pontuação mais alta, se o empate continuar, então o desempate é efetuado pela posição na última corrida pontuada. +scoringSchemeLowPointSystemFirstTwoWins=Sistema linear, o primeiro com duas vitórias com medalhas é o vencedor. +scoringSchemeLowPointSystemFirstTwoWinsDescription=Sistema linear. O primeiro na série para medalha a vencer duas corridas vence a série para medalha. Ser o melhor na série prévia para medalha conta como uma vitória para este efeito. scoringSystem=Sistema de pontuação createFlexibleLeaderboard=Criar painel de classificação flexível createRegattaLeaderboard=Criar painel de classificação de regata @@ -1276,13 +1278,11 @@ uploadSuccessful=Carregamento efetuado com êxito fileUploadResult=Carregamento de arquivo: {0} {1}.\nEm caso de erro, verifique se seu serviço de armazenamento de arquivos está configurado de modo apropriado. removeUploadedFile=Remover arquivo carregado showCompetitorBoatColumn=Barco do competidor -showCompetitorSailIdColumn=ID da vela -showCompetitorSailIdColumnTooltip=Exibe o ID da vela mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorShortNameColumn=Nome abreviado do competidor showCompetitorShortNameColumnTooltip=Exibe o nome abreviado mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorFullNameColumn=Nome completo do competidor -alwaysShowCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor -alwaysShowCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis +showCompetitorNationalityColumn=Exibir adicionalmente a nacionalidade do competidor +showCompetitorNationalityColumnTooltip=Exibir bandeiras de nacionalidade além das imagens do competidor, caso estejam disponíveis loadingDimensionValues=Carregando valores de dimensão inviteBuoyTenders=Convidar navios-balizadores orMultipleEmails=ou vários e-mails separados por vírgula @@ -1424,7 +1424,6 @@ numberOfRacesShort=Nº de corridas courseGeometry=Geometria do percurso degreesToWind={0}° para vento miniLeeaderboardLegendText=Exibe os pontos juntamente com o número de corridas por competidor -currentOfTotal={0,number}/{1,number} noFinishedRaces=Ainda não existem corridas concluídas. racesOverview=Síntese de corridas listFormatLabel=Formato de lista @@ -2059,6 +2058,9 @@ multiVideoIdle=A lista de trabalho está inativa multiVideoDoNoAdd=Não adicionar multiVideoOffsetInput=Deslocamento de vídeo global em milissegundos: multiVideoDescription=O servidor da web é necessário para fornecer a lista de índices (subpastas suportadas se indexadas) de modo a permitir a descoberta de arquivos. Após uma análise inicial dos metadados contidos nos arquivos mp4, os vídeos que deviam ser adicionados precisam ser selecionados mediante a coluna do campo de seleção à esquerda. O botão "Adicionar áudio/vídeo" irá criar faixas de mídia para todos os arquivos selecionados e adicionar esses arquivos a todas as corridas selecionadas na coluna à direita. +jibeAngle=Ângulo de jaibe +tackAngle=Ângulo de cambada +maneuverAngle=Ângulo de manobra multiUrlChangeMediaTrack=Ajustar vários URLs de faixa de mídia multiUrlChangeReplace=Substituir por multiUrlChangeFind=Procurar @@ -2070,6 +2072,11 @@ multiUrlChangeExplain=Este diálogo irá substituir em massa as partes comuns no resetZoom=Reinicializar zoom lastEvent=Último evento: {0} teaserOverallLinkToolTip=Para ver a série completa, clique no canto amarelo +middleManeuverAngle=Ângulo médio de manobra +extrapolatedManeuverStartPosition=Posição inicial de manobra extrapolada +projectedExtrapolatedManeuverStartPosition=Projeção da posição inicial de manobra extrapolada na linha de ângulo médio de manobra +projectedManeuverEndPosition=Projeção da posição final de manobra na linha de ângulo médio de manobra +bearingAtManeuverEndPosition=Rumo na posição final de manobra gender=Sexo manage2SailEventIdBox=ID de evento Manage2Sail manage2SailEventURLBox=URL de evento Manage2Sail (json) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index e9612321f5a..674df8274c5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Высокий балл, побед scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Оценка в баллах. Победитель получает 12 баллов, 2-й — 11 баллов, .... Равенство разрешается в пользу участника с наивысшей оценкой. Если равенство сохраняется, оно разрешается по рангу в последней гонке. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Высокий балл ESS 2017, победитель получает 12 баллов (или 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Оценка в баллах. Победитель получает 12 баллов, 2-й — 11 баллов, .... Равенство разрешается в пользу участника с наивысшей оценкой. Если равенство сохраняется, оно разрешается по рангу в последней гонке. +scoringSchemeLowPointSystemFirstTwoWins=Система низких баллов, первый с двумя победами в гонках на медали является победителем +scoringSchemeLowPointSystemFirstTwoWinsDescription=Система низких баллов. Первый в серии на медали, кто выиграл две гонки, выигрывает серию на медали. Получение статуса "Лучший" в серии, предшествующей медальной, считается одной победой. scoringSystem=Система оценок createFlexibleLeaderboard=Создать гибкую таблицу лидеров createRegattaLeaderboard=Создать таблицу лидеров регаты @@ -1276,13 +1278,11 @@ uploadSuccessful=Отправлено успешно fileUploadResult=Отправка файла: {0} {1}.\nВ случае ошибки проверьте правильность конфигурации службы хранилища файлов. removeUploadedFile=Удалить отправленный файл showCompetitorBoatColumn=Лодка участника -showCompetitorSailIdColumn=Ид. паруса -showCompetitorSailIdColumnTooltip=Показывает ид. паруса плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorShortNameColumn=Краткое имя участника showCompetitorShortNameColumnTooltip=Показывает краткое имя плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorFullNameColumn=Полное имя участника -alwaysShowCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника -alwaysShowCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии) +showCompetitorNationalityColumn=Дополнительно показывать государственную принадлежность участника +showCompetitorNationalityColumnTooltip=Показывать государственные флаги (если есть) дополнительно к фотографии участника loadingDimensionValues=Загрузка значений измерения inviteBuoyTenders=Пригласить лоцманов orMultipleEmails=или несколько адресов эл. почты через запятую @@ -1424,7 +1424,6 @@ numberOfRacesShort=Гонки courseGeometry=Геометрия курса degreesToWind={0}° к ветру miniLeeaderboardLegendText=Показывает баллы и число гонок каждого участника -currentOfTotal={0,number}/{1,number} noFinishedRaces=Завершенных гонок еще нет racesOverview=Обзор гонок listFormatLabel=Формат списка @@ -2059,6 +2058,9 @@ multiVideoIdle=Рабочий список неактивен multiVideoDoNoAdd=Не добавлять multiVideoOffsetInput=Смещение глобального видео в мс: multiVideoDescription=Веб-сервер необходим для предоставления списка индекса (подпапки поддерживаются, если проиндексированы) для поиска файлов. После первичного анализа метаданных, содержащихся в файлах mp4, видео, которые должны быть добавлены, необходимо выбрать в столбце с флажками слева. При нажатии кнопки "Добавить аудио/видео" будут созданы медиаканалы для всех выбранных файлов, а файлы будут добавлены ко всем гонкам, выбранным в правом столбце. +jibeAngle=Поворот фордевинд +tackAngle=Галсовый угол +maneuverAngle=Угол маневра multiUrlChangeMediaTrack=Адаптировать URL объектов мультимедиа multiUrlChangeReplace=Заменить на multiUrlChangeFind=Найти @@ -2070,6 +2072,11 @@ multiUrlChangeExplain=В этом диалоговом окне можно вы resetZoom=Сбросить увеличение lastEvent=Последнее событие: {0} teaserOverallLinkToolTip=Чтобы увидеть все серии, нажмите желтый треугольник +middleManeuverAngle=Угол в середине маневра +extrapolatedManeuverStartPosition=Расширенная начальная позиция маневра +projectedExtrapolatedManeuverStartPosition=Проекция расширенной начальной позиции маневра на линии угла в середине маневра +projectedManeuverEndPosition=Проекция конечной позиции маневра на линии угла в середине маневра +bearingAtManeuverEndPosition=Направление в конечной позиции маневра gender=Пол manage2SailEventIdBox=Ид. события Manage2Sail manage2SailEventURLBox=URL события Manage2Sail (json) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 16c5f949a2f..5b69e84fc19 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=高分,获胜者得 12 分(或 scoringSchemeHighPointFirstGetsTwelveOrEightDescription=得分。获胜者得 12 分、第 2 名 11 分、.... 得分最高的参赛队取得胜利,如果仍有平局,则以最后一个比赛轮次的名次打破平局。 scoringSchemeHighPointFirstGetsTwelveOrEight2017=2017 年极限帆船系列赛高分,获胜者得 12 分(或 8 分) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=得分。获胜者得 12 分、第 2 名 11 分、.... 得分最高的参赛队取得胜利,如果仍有平局,则以最后一个比赛轮次的名次打破平局。 +scoringSchemeLowPointSystemFirstTwoWins=低分系统,奖牌轮中取得两场胜利的第一名为获胜者 +scoringSchemeLowPointSystemFirstTwoWinsDescription=低分系统。在奖牌轮系列中,在两个比赛轮次中获胜的第一名在奖牌轮系列中获胜。奖牌轮系列预赛中的佼佼者记为获胜。 scoringSystem=记分系统 createFlexibleLeaderboard=创建灵活的积分榜 createRegattaLeaderboard=创建比赛积分榜 @@ -1276,13 +1278,11 @@ uploadSuccessful=上传成功 fileUploadResult=文件上传: {0} {1}。\n如果出错,请检查文件存储服务配置是否正确。 removeUploadedFile=移除上传的文件 showCompetitorBoatColumn=参赛队的船只 -showCompetitorSailIdColumn=帆号 -showCompetitorSailIdColumnTooltip=显示帆号和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorShortNameColumn=参赛队短名称 showCompetitorShortNameColumnTooltip=显示短名称和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorFullNameColumn=参赛队全名 -alwaysShowCompetitorNationalityColumn=始终显示参赛队国籍 -alwaysShowCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像 +showCompetitorNationalityColumn=另外显示参赛队国籍 +showCompetitorNationalityColumnTooltip=除参赛队图像外还显示国籍旗帜(如果有) loadingDimensionValues=正在加载尺寸值 inviteBuoyTenders=邀请浮标供应船 orMultipleEmails=或使用逗号隔开的多个电子邮件 @@ -1424,7 +1424,6 @@ numberOfRacesShort=比赛轮次数 courseGeometry=场地几何结构 degreesToWind={0}°(相对于风) miniLeeaderboardLegendText=显示分数以及各参赛队的比赛轮次数 -currentOfTotal={0,number}/{1,number} noFinishedRaces=没有结束的比赛轮次 racesOverview=比赛轮次总览 listFormatLabel=清单格式 @@ -2059,6 +2058,9 @@ multiVideoIdle=工作队列空闲 multiVideoDoNoAdd=请勿添加 multiVideoOffsetInput=全球视频偏移量(毫秒): multiVideoDescription=需要 Web 服务器提供索引清单(如果已编入索引,则支持的子文件夹),以允许发现文件。初始分析 mp4 文件中包含的元数据后,需要通过左侧的复选框列选择应添加的视频。“添加音频/视频”按钮将为所有选择的文件创建媒体轨道,并将这些文件添加到右列中选择的所有比赛轮次中。 +jibeAngle=顺风转向角度 +tackAngle=迎风转向角度 +maneuverAngle=操作角度 multiUrlChangeMediaTrack=调整多个媒体轨道 URL multiUrlChangeReplace=替换为 multiUrlChangeFind=查找 @@ -2070,6 +2072,11 @@ multiUrlChangeExplain=此对话框将在媒体轨道 URL 的起始处批量替 resetZoom=重置缩放 lastEvent=上次活动:{0} teaserOverallLinkToolTip=请查看整体系列赛,请点击黄色角落 +middleManeuverAngle=中间操作角度 +extrapolatedManeuverStartPosition=推断的操作开始位置 +projectedExtrapolatedManeuverStartPosition=推断的操作开始位置在中间操作角度线上的投影 +projectedManeuverEndPosition=操作结束位置在中间操作角度线上的投影 +bearingAtManeuverEndPosition=操作结束位置的航向角 gender=性别 manage2SailEventIdBox=Manage2Sail 活动编号 manage2SailEventURLBox=Manage2Sail 活动 URL (json) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiRaceLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiRaceLeaderboardPanel.java index 8c5dadc347a..42c94961eee 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiRaceLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiRaceLeaderboardPanel.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.Map; import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.LeaderboardType; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; @@ -96,8 +95,7 @@ public class MultiRaceLeaderboardPanel extends LeaderboardPanel getSettingsDialogComponent( MultiRaceLeaderboardSettings useTheseSettings) { - return new MultiRaceLeaderboardSettingsDialogComponent((MultiRaceLeaderboardSettings) useTheseSettings, - leaderboard.getNamesOfRaceColumns(), stringMessages, availableDetailTypes, !leaderboard.canBoatsOfCompetitorsChangePerRace); + return new MultiRaceLeaderboardSettingsDialogComponent(useTheseSettings, leaderboard.getNamesOfRaceColumns(), + stringMessages, availableDetailTypes, canShowCompetitorBoatInfo()); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java index 4b6cfbe7256..487d63cf647 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java @@ -2,6 +2,9 @@ package com.sap.sailing.gwt.ui.raceboard; import java.util.Collections; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; @@ -12,6 +15,7 @@ import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication; import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; @@ -26,13 +30,15 @@ import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsAndBoatsDTO; import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.settings.Settings; import com.sap.sse.gwt.client.EntryPointHelper; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.formfactor.DeviceDetector; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; -import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings; +import com.sap.sse.gwt.client.shared.settings.ComponentContext; import com.sap.sse.gwt.client.shared.settings.DefaultOnSettingsLoadedCallback; import com.sap.sse.gwt.settings.SettingsToUrlSerializer; import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication; @@ -40,7 +46,6 @@ import com.sap.sse.security.ui.settings.ComponentContextWithSettingsStorage; import com.sap.sse.security.ui.settings.StoredSettingsLocation; public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements ProvidesLeaderboardRouting { - private RaceWithCompetitorsAndBoatsDTO selectedRace; /** * Controls the predefined mode into which to switch or configure the race viewer. @@ -48,131 +53,83 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements Pr private final MediaServiceAsync mediaService = GWT.create(MediaService.class); private RaceboardContextDefinition raceboardContextDefinition; - - private String leaderBoardName; @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); - raceboardContextDefinition = new SettingsToUrlSerializer() - .deserializeFromCurrentLocation(new RaceboardContextDefinition()); - final RaceBoardModes finalMode; - if (raceboardContextDefinition.getMode() != null) { - finalMode = RaceBoardModes.valueOf(raceboardContextDefinition.getMode()); - } else { - finalMode = null; - } - if (raceboardContextDefinition.getRegattaName() == null || raceboardContextDefinition.getRegattaName().isEmpty() - || raceboardContextDefinition.getRaceName() == null - || raceboardContextDefinition.getRaceName().isEmpty() - || raceboardContextDefinition.getLeaderboardName() == null - || raceboardContextDefinition.getLeaderboardName().isEmpty()) { - createErrorPage(getStringMessages().requiresRegattaRaceAndLeaderboard()); - return; - } - - leaderBoardName = raceboardContextDefinition.getLeaderboardName(); - - AsyncCallback asyncCallback = new AsyncCallback() { - @Override - public void onSuccess(RaceboardDataDTO raceboardData) { - if (!raceboardData.isValidLeaderboard()) { - createErrorPage(getStringMessages().noSuchLeaderboard()); - return; - } - if (raceboardContextDefinition.getEventId() != null && !raceboardData.isValidEvent()) { - createErrorPage(getStringMessages().noSuchEvent()); - } - if (raceboardContextDefinition.getLeaderboardGroupName() != null) { - if (!raceboardData.isValidLeaderboardGroup()) { - createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup( - raceboardContextDefinition.getLeaderboardName(), - raceboardContextDefinition.getLeaderboardGroupName())); - return; - } - if (raceboardContextDefinition.getEventId() != null && raceboardData.isValidLeaderboardGroup() - && !raceboardData.isValidEvent()) { - createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent( - raceboardContextDefinition.getLeaderboardGroupName(), - raceboardContextDefinition.getEventId().toString())); - return; - } - } - if (raceboardData.getRace() == null) { - createErrorPage(getStringMessages().couldNotFindRaceInRegatta( - raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getRegattaName())); - return; - } - final boolean showChartMarkEditMediaButtonsAndVideo = !DeviceDetector.isMobile(); - final StoredSettingsLocation storageDefinition = StoredSettingsLocationFactory - .createStoredSettingsLocatorForRaceBoard(raceboardContextDefinition, - finalMode != null ? finalMode.name() : null); - // TODO bug2822: also determine regatta's boat change parameter and parameterize Leaderboard settings - // --> show boat info column yes/no? - getSailingService().determineDetailTypesForCompetitorChart( - raceboardContextDefinition.getLeaderboardGroupName(), - raceboardData.getRace().getRaceIdentifier(), new AsyncCallback>() { - - @Override - public void onFailure(Throwable caught) { - reportError("Error trying to create the raceboard: " + caught.getMessage()); - } - - @Override - public void onSuccess(Iterable chartAllowedTypes) { - getSailingService().getAvailableDetailTypesForLeaderboard( - raceboardContextDefinition.getLeaderboardName(), - raceboardData.getRace().getRaceIdentifier(), new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - reportError( - "Error trying to create the raceboard: " + caught.getMessage()); - } - - @Override - public void onSuccess(Iterable leaderboardAllowedTypes) { - final RaceBoardPerspectiveLifecycle lifeCycle = new RaceBoardPerspectiveLifecycle( - StringMessages.INSTANCE, chartAllowedTypes, getUserService(), - leaderboardAllowedTypes); - RaceBoardComponentContext componentContext = new RaceBoardComponentContext( - lifeCycle, getUserService(), storageDefinition); - - componentContext.getInitialSettings( - new DefaultOnSettingsLoadedCallback>() { - @Override - public void onSuccess( - PerspectiveCompositeSettings initialSettings) { - Timer timer = new Timer(PlayModes.Replay, 1000l); - final RaceBoardPanel raceBoardPanel = createPerspectivePage( - null, componentContext, initialSettings, - raceboardData, - showChartMarkEditMediaButtonsAndVideo, - lifeCycle, timer, leaderboardAllowedTypes); - if (finalMode != null) { - finalMode.getMode().applyTo(raceBoardPanel); - } - } - }); - } - }); - } + checkRaceboardContextPreConditions(new RaceboardContextDefinition()).ifPresent(raceboardContextDefinition -> { + RaceBoardEntryPoint.this.raceboardContextDefinition = raceboardContextDefinition; + final String modeString = raceboardContextDefinition.getMode(); + final RaceBoardModes mode = modeString == null ? null : RaceBoardModes.valueOf(modeString); + checkRaceboardDataConditions(raceboardData -> { + loadAvailableDetailTypes(raceboardData, (chartDetailTypes, lbDetailTypes) -> { + loadLeaderboardInformation(leaderboard -> { + initLifecycle(mode, chartDetailTypes, lbDetailTypes, leaderboard, (lifecycle, compContext) -> { + loadInitialSettings(compContext, settings -> { + createPerspective(mode, compContext, settings, raceboardData, lifecycle, lbDetailTypes); + }); }); - } + }); + }); + }); + }); + } + + private Optional checkRaceboardContextPreConditions(C settings) { + final C raceboardContext = new SettingsToUrlSerializer().deserializeFromCurrentLocation(settings); + if (raceboardContext.getRegattaName() == null || raceboardContext.getRegattaName().isEmpty() + || raceboardContext.getRaceName() == null || raceboardContext.getRaceName().isEmpty() + || raceboardContext.getLeaderboardName() == null || raceboardContext.getLeaderboardName().isEmpty()) { + createErrorPage(getStringMessages().requiresRegattaRaceAndLeaderboard()); + return Optional.empty(); + } + return Optional.of(raceboardContext); + } - @Override - public void onFailure(Throwable caught) { - reportError("Error trying to create the raceboard: " + caught.getMessage()); - } - }; + private void checkRaceboardDataConditions(final Consumer callback) { getSailingService().getRaceboardData(raceboardContextDefinition.getRegattaName(), raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getLeaderboardName(), raceboardContextDefinition.getLeaderboardGroupName(), raceboardContextDefinition.getEventId(), - asyncCallback); + new AbstractRaceBoardAsyncCallback() { + + @Override + public void onSuccess(RaceboardDataDTO raceboardData) { + if (!raceboardData.isValidLeaderboard()) { + createErrorPage(getStringMessages().noSuchLeaderboard()); + return; + } + if (raceboardContextDefinition.getEventId() != null && !raceboardData.isValidEvent()) { + createErrorPage(getStringMessages().noSuchEvent()); + } + if (raceboardContextDefinition.getLeaderboardGroupName() != null) { + if (!raceboardData.isValidLeaderboardGroup()) { + createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup( + raceboardContextDefinition.getLeaderboardName(), + raceboardContextDefinition.getLeaderboardGroupName())); + return; + } + if (raceboardContextDefinition.getEventId() != null + && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) { + createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent( + raceboardContextDefinition.getLeaderboardGroupName(), + raceboardContextDefinition.getEventId().toString())); + return; + } + } + if (raceboardData.getRace() == null) { + createErrorPage(getStringMessages().couldNotFindRaceInRegatta( + raceboardContextDefinition.getRaceName(), + raceboardContextDefinition.getRegattaName())); + return; + } + callback.accept(raceboardData); + } + }); } - + private void createErrorPage(String message) { final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX); final SAPHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(); @@ -183,31 +140,99 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements Pr infoText.getElement().getStyle().setMargin(1, Unit.EM); vp.add(infoText); // TODO: Styling of error page slightly differs from the other usages of SAPSailingHeaderWithAuthentication - // because of the root font-size. Adjustments are postponed because they might affect the hole page content. + // because of the root font-size. Adjustments are postponed because they might affect the whole page content. } - private RaceBoardPanel createPerspectivePage(Component parent, + private void loadAvailableDetailTypes(final RaceboardDataDTO raceboardData, + final BiConsumer, Iterable> callback) { + final RegattaAndRaceIdentifier raceIdentifier = raceboardData.getRace().getRaceIdentifier(); + getSailingService().determineDetailTypesForCompetitorChart(raceboardContextDefinition.getLeaderboardGroupName(), + raceIdentifier, new AbstractRaceBoardAsyncCallback>() { + + @Override + public void onSuccess(Iterable allowedChartDetailTypes) { + final String leaderboardName = raceboardContextDefinition.getLeaderboardName(); + getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, raceIdentifier, + new AbstractRaceBoardAsyncCallback>() { + + @Override + public void onSuccess(Iterable allowedLeaderboardDetailTypes) { + callback.accept(allowedChartDetailTypes, allowedLeaderboardDetailTypes); + } + }); + } + }); + } + + private void loadLeaderboardInformation(final Consumer callback) { + getSailingService().getLeaderboard(raceboardContextDefinition.getLeaderboardName(), + new AbstractRaceBoardAsyncCallback() { + + @Override + public void onSuccess(StrippedLeaderboardDTO result) { + callback.accept(result); + } + }); + } + + private void initLifecycle(final RaceBoardModes raceBoardMode, final Iterable chartDetailTypes, + final Iterable leaderboardDetailTypes, final StrippedLeaderboardDTO leaderboard, + final BiConsumer callback) { + final StoredSettingsLocation storageDefinition = StoredSettingsLocationFactory + .createStoredSettingsLocatorForRaceBoard(raceboardContextDefinition, + raceBoardMode != null ? raceBoardMode.name() : null); + final RaceBoardPerspectiveLifecycle lifeCycle = new RaceBoardPerspectiveLifecycle(leaderboard, + StringMessages.INSTANCE, chartDetailTypes, getUserService(), leaderboardDetailTypes); + RaceBoardComponentContext componentContext = new RaceBoardComponentContext(lifeCycle, getUserService(), + storageDefinition); + callback.accept(lifeCycle, componentContext); + } + + private void loadInitialSettings(final ComponentContext ctx, final Consumer callback) { + ctx.getInitialSettings(new DefaultOnSettingsLoadedCallback() { + + @Override + public void onSuccess(S settings) { + callback.accept(settings); + } + }); + } + + private void createPerspective(final RaceBoardModes raceBoardMode, ComponentContextWithSettingsStorage> context, PerspectiveCompositeSettings settings, RaceboardDataDTO raceboardData, - boolean showChartMarkEditMediaButtonsAndVideo, RaceBoardPerspectiveLifecycle raceLifeCycle, Timer timer, - Iterable availableDetailTypes) { - selectedRace = raceboardData.getRace(); + RaceBoardPerspectiveLifecycle raceLifeCycle, Iterable availableDetailTypes) { + final Timer timer = new Timer(PlayModes.Replay, 1000l); + final boolean showChartMarkEditMediaButtonsAndVideo = !DeviceDetector.isMobile(); + + final RaceWithCompetitorsAndBoatsDTO selectedRace = raceboardData.getRace(); Window.setTitle(selectedRace.getName()); AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), asyncActionsExecutor, this, - Collections.singletonList(selectedRace.getRaceIdentifier()), 5000l /* requestInterval*/); - RaceBoardPanel raceBoardPerspective = new RaceBoardPanel(parent, context, raceLifeCycle, settings, + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), + asyncActionsExecutor, this, Collections.singletonList(selectedRace.getRaceIdentifier()), + 5000l /* requestInterval */); + RaceBoardPanel raceBoardPerspective = new RaceBoardPanel(/* parent */ null, context, raceLifeCycle, settings, getSailingService(), mediaService, getUserService(), asyncActionsExecutor, raceboardData.getCompetitorAndTheirBoats(), timer, selectedRace.getRaceIdentifier(), raceboardContextDefinition.getLeaderboardName(), raceboardContextDefinition.getLeaderboardGroupName(), raceboardContextDefinition.getEventId(), RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showChartMarkEditMediaButtonsAndVideo, true, availableDetailTypes); RootLayoutPanel.get().add(raceBoardPerspective.getEntryWidget()); - return raceBoardPerspective; + + if (raceBoardMode != null) { + raceBoardMode.getMode().applyTo(raceBoardPerspective); + } } @Override public String getLeaderboardName() { - return leaderBoardName; + return raceboardContextDefinition.getLeaderboardName(); + } + + private abstract class AbstractRaceBoardAsyncCallback implements AsyncCallback { + @Override + public final void onFailure(Throwable caught) { + reportError("Error trying to create the raceboard: " + caught.getMessage()); + } } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java index 7125da14d0b..f0f860d7cd0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java @@ -28,13 +28,6 @@ public class RaceBoardPerspectiveLifecycle extends AbstractPerspectiveLifecycle< public static final String ID = "rb"; - //constructor used by Standalone RaceBoard - public RaceBoardPerspectiveLifecycle(StringMessages stringMessages, - Iterable competitorChartAllowedDetailTypes, UserService userService, - Iterable availableDetailTypes) { - this(null, stringMessages, competitorChartAllowedDetailTypes, userService, availableDetailTypes); - } - public RaceBoardPerspectiveLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages, Iterable competitorChartAllowedDetailTypes, UserService userService, Iterable availableDetailTypes) { @@ -42,7 +35,8 @@ public class RaceBoardPerspectiveLifecycle extends AbstractPerspectiveLifecycle< raceMapLifecycle = new RaceMapLifecycle(stringMessages); windChartLifecycle = new WindChartLifecycle(stringMessages); maneuverTableLifecycle = new ManeuverTableLifecycle(stringMessages); - leaderboardPanelLifecycle = new SingleRaceLeaderboardPanelLifecycle(stringMessages, availableDetailTypes); + leaderboardPanelLifecycle = new SingleRaceLeaderboardPanelLifecycle(stringMessages, availableDetailTypes, + leaderboard.canBoatsOfCompetitorsChangePerRace); multiCompetitorRaceChartLifecycle = new MultiCompetitorRaceChartLifecycle(stringMessages, competitorChartAllowedDetailTypes); mediaPlayerLifecycle = new MediaPlayerLifecycle(stringMessages); raceTimePanelLifecycle = new RaceTimePanelLifecycle(stringMessages, userService); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java index d75f0c8245f..6e06ca8f3eb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java @@ -16,10 +16,13 @@ public class SingleRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecyc private static final long DEFAULT_REFRESH_INTERVAL = 1000L; private final boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard; + private final boolean canBoatsOfCompetitorsChangePerRace; - public SingleRaceLeaderboardPanelLifecycle(StringMessages stringMessages, Iterable availableDetailTypes) { + public SingleRaceLeaderboardPanelLifecycle(final StringMessages stringMessages, + final Iterable availableDetailTypes, final boolean canBoatsOfCompetitorsChangePerRace) { super(stringMessages, availableDetailTypes); this.isScreenLargeEnoughToInitiallyDisplayLeaderboard = Document.get().getClientWidth() >= 1024; + this.canBoatsOfCompetitorsChangePerRace = canBoatsOfCompetitorsChangePerRace; } @Override @@ -31,27 +34,24 @@ public class SingleRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecyc raceDetails.add(DetailType.NUMBER_OF_MANEUVERS); raceDetails.add(DetailType.RACE_DISPLAY_LEGS); List overallDetails = new ArrayList<>(); - SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(); + SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(canBoatsOfCompetitorsChangePerRace); + // don't show competitor fullName column if even leaderboard isn't shown initially + final boolean showCompetitorFullNameColumn = isScreenLargeEnoughToInitiallyDisplayLeaderboard; SingleRaceLeaderboardSettings settings = new SingleRaceLeaderboardSettings( defaultSettings.getManeuverDetailsToShow(), defaultSettings.getLegDetailsToShow(), defaultSettings.getRaceDetailsToShow(), overallDetails, DEFAULT_REFRESH_INTERVAL, defaultSettings.isShowAddedScores(), /* showCompetitorShortNameColumn */ true, - /* - * don't showCompetitorFullNameColumn in case screen is so small that we don't even display the - * leaderboard initially - */ isScreenLargeEnoughToInitiallyDisplayLeaderboard, - /* showCompetitorBoatInfoColumn */ true, false, false); + showCompetitorFullNameColumn, + /* showCompetitorBoatInfoColumn */ canBoatsOfCompetitorsChangePerRace, + /* isCompetitorNationalityColumnVisible */ false, + /* showRaceRankColumn */ false); SettingsUtil.copyDefaultsFromValues(settings, settings); return settings; } - public boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard() { - return isScreenLargeEnoughToInitiallyDisplayLeaderboard; - } - @Override public SingleRaceLeaderboardSettings extractDocumentSettings(SingleRaceLeaderboardSettings currentLeaderboardSettings) { SingleRaceLeaderboardSettings defaultLeaderboardSettings = createDefaultSettings(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index d96aba06aac..e3ee250c576 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1064,7 +1064,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); raceInfoDTO.isTracked = trackedRace != null ? true : false; if (raceLog != null) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(getService(), raceLog); + ReadonlyRaceState state = ReadonlyRaceStateImpl.getOrCreate(getService(), raceLog); TimePoint startTime = state.getStartTime(); if (startTime != null) { raceInfoDTO.startTime = startTime.asDate(); @@ -2092,6 +2092,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceTimesInfo.newestTrackingEvent = trackedRace.getTimePointOfNewestEvent() == null ? null : trackedRace.getTimePointOfNewestEvent().asDate(); raceTimesInfo.endOfTracking = trackedRace.getEndOfTracking() == null ? null : trackedRace.getEndOfTracking().asDate(); raceTimesInfo.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate(); + raceTimesInfo.raceFinishedTime = trackedRace.getFinishedTime() == null ? null : trackedRace.getFinishedTime().asDate(); raceTimesInfo.delayToLiveInMs = trackedRace.getDelayToLiveInMillis(); Iterable>> markPassingsTimes = trackedRace.getMarkPassingsTimes(); @@ -2846,7 +2847,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Iterable configs = swissTimingAdapterPersistence.getSwissTimingConfigurations(); List result = new ArrayList(); for (SwissTimingConfiguration stConfig : configs) { - result.add(new SwissTimingConfigurationDTO(stConfig.getName(), stConfig.getJsonURL(), stConfig.getHostname(), stConfig.getPort())); + result.add(new SwissTimingConfigurationDTO(stConfig.getName(), stConfig.getJsonURL(), + stConfig.getHostname(), stConfig.getPort(), stConfig.getUpdateURL(), stConfig.getUpdateUsername(), + stConfig.getUpdatePassword())); } return result; } @@ -2884,9 +2887,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port) { + public void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) { if (!jsonURL.equalsIgnoreCase("test")) { - swissTimingAdapterPersistence.storeSwissTimingConfiguration(swissTimingFactory.createSwissTimingConfiguration(configName, jsonURL, hostname, port)); + swissTimingAdapterPersistence.storeSwissTimingConfiguration(swissTimingFactory.createSwissTimingConfiguration(configName, jsonURL, hostname, port, updateURL, updateUsername, updatePassword)); } } @@ -2902,7 +2905,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, - boolean trackWind, final boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception { + boolean trackWind, final boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, + String updateURL, String updateUsername, String updatePassword) throws InterruptedException, ParseException, Exception { logger.info("tracWithSwissTiming for regatta " + regattaToAddTo + " for race records " + rrs + " with hostname " + hostname + " and port " + port); Map cachedRegattaEntriesLists = new HashMap(); @@ -2927,7 +2931,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S getSwissTimingAdapter().addSwissTimingRace(getService(), regattaToAddTo, rr.raceId, rr.getName(), raceDescription, boatClass, hostname, port, startList, getRaceLogStore(), getRegattaLogStore(), - RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, useInternalMarkPassingAlgorithm, trackWind, correctWindByDeclination); + RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, useInternalMarkPassingAlgorithm, trackWind, + correctWindByDeclination, updateURL, updateUsername, updatePassword); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java index 8926b48cdd5..eab41710b60 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java @@ -19,6 +19,7 @@ public class RaceTimesInfoDTO implements IsSerializable { public Date startOfTracking; public Date endOfTracking; public Date endOfRace; + public Date raceFinishedTime; public Date newestTrackingEvent; /** @@ -83,6 +84,10 @@ public class RaceTimesInfoDTO implements IsSerializable { public Date getEndOfRace() { return endOfRace; } + + public Date getFinishedTime() { + return raceFinishedTime; + } public void setEndOfRace(Date endOfRace) { this.endOfRace = endOfRace; @@ -118,7 +123,7 @@ public class RaceTimesInfoDTO implements IsSerializable { + ", legInfos=" + legInfos + ", markPassingTimes=" + markPassingTimes + ", startOfRace=" + startOfRace + ", startOfTracking=" + startOfTracking + ", endOfTracking=" - + endOfTracking + ", endOfRace=" + endOfRace + + endOfTracking + ", endOfRace=" + endOfRace + ", finishedTime=" + raceFinishedTime + ", newestTrackingEvent=" + newestTrackingEvent + ", currentServerTime=" + currentServerTime + ", delayToLiveInMs=" + delayToLiveInMs + "]"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java index 45ebd1fb69b..5d95cc9e2d3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java @@ -8,14 +8,33 @@ public class SwissTimingConfigurationDTO implements IsSerializable { private String hostname; private Integer port; + /** + * The URL to use for updates fed back to the SwissTiming server, such as changes of race timings + * or course updates. + */ + private String updateURL; + + /** + * The username to use as part of the credentials for requests to the {@link #updateURL}. + */ + private String updateUsername; + + /** + * The password to use as part of the credentials for requests to the {@link #updateURL}. + */ + private String updatePassword; + public SwissTimingConfigurationDTO() {} - public SwissTimingConfigurationDTO(String name, String jsonURL, String hostname, Integer port) { + public SwissTimingConfigurationDTO(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; } public String getName() { @@ -33,5 +52,16 @@ public class SwissTimingConfigurationDTO implements IsSerializable { public Integer getPort() { return port; } - + + public String getUpdateURL() { + return updateURL; + } + + public String getUpdateUsername() { + return updateUsername; + } + + public String getUpdatePassword() { + return updatePassword; + } } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java index 9200abde2d4..bb521d61c82 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java @@ -53,7 +53,8 @@ public class SwissTimingConnectivityParamsLoadAndStoreTest extends AbstractConne final SwissTimingTrackingConnectivityParameters stParams = new SwissTimingTrackingConnectivityParameters( hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, SwissTimingFactory.INSTANCE, new SwissTimingAdapterFactoryImpl().getOrCreateSwissTimingAdapter(domainObjectFactory.getBaseDomainFactory()).getSwissTimingDomainFactory(), - /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination); + /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, + /* updateURL */ null, /* updateUsername */ null, /* updatePassword */ null); // store mongoObjectFactory.addConnectivityParametersForRaceToRestore(stParams); // load @@ -103,7 +104,8 @@ public class SwissTimingConnectivityParamsLoadAndStoreTest extends AbstractConne final SwissTimingTrackingConnectivityParameters stParams = new SwissTimingTrackingConnectivityParameters( hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, SwissTimingFactory.INSTANCE, new SwissTimingAdapterFactoryImpl().getOrCreateSwissTimingAdapter(domainObjectFactory.getBaseDomainFactory()).getSwissTimingDomainFactory(), - /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination); + /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, + /* updateURL */ null, /* updateUsername */ null, /* updatePassword */ null); // store mongoObjectFactory.addConnectivityParametersForRaceToRestore(stParams); // load diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java index 5ad592fbc2b..0b87b9c86e4 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java @@ -45,7 +45,7 @@ public class RaceStateSerializer implements JsonSerializer result; if (leaderboard instanceof HasRegattaLike && raceLog != null) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog); + ReadonlyRaceState state = ReadonlyRaceStateImpl.getOrCreate(/* race log resolver */ this, raceLog); result = new com.sap.sse.common.Util.Triple(state.getStartTime(), raceLog.getCurrentPassId(), state.getRacingProcedure().getType()); } else { @@ -3652,7 +3652,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes Leaderboard leaderboard = getLeaderboardByName(leaderboardName); final Triple result; if (leaderboard instanceof HasRegattaLike && raceLog != null) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog); + ReadonlyRaceState state = ReadonlyRaceStateImpl.getOrCreate(/* race log resolver */ this, raceLog); result = new com.sap.sse.common.Util.Triple<>(state.getFinishingTime(), state.getFinishedTime(), raceLog.getCurrentPassId()); } else { diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index aff77239787..f5a4d1f5b0d 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -25,6 +25,10 @@

July 2018

  • The mark passing editor now uses a full date/time format for the mark passing time points.
  • +
  • The TracTrac connector panel no longer shows a password visibly but uses a password text field.
  • +
  • The SwissTiming connector can now handle an "Update URL" together with username/password access + credentials which are used, if present, to send feedback requests about start times, race status + and course changes to that URL.

July 2018

diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DOMUtils.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DOMUtils.java new file mode 100644 index 00000000000..0e0f8ef8183 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DOMUtils.java @@ -0,0 +1,40 @@ +package com.sap.sse.gwt.client; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.Widget; + +/** + * Utility class contained convenience methods regarding DOM operations. + */ +public final class DOMUtils { + + private DOMUtils() { + } + + /** + * Scrolls the top of the provided {@link Widget widget} to the topmost possible position in the viewport. + * + * @param widget + * {@link Widget} to scroll to the topmost possible position + */ + public static void scrollToTop(final Widget widget) { + getViewportElement().setScrollTop(widget.getAbsoluteTop()); + } + + /** + * Scrolls the top of the provided {@link Element element} to the topmost possible position in the viewport. + * + * @param element + * {@link Element} to scroll to the topmost possible position + */ + public static void scrollToTop(final Element element) { + getViewportElement().setScrollTop(element.getAbsoluteTop()); + } + + private static Element getViewportElement() { + final Document doc = Document.get(); + return doc.isCSS1Compat() ? doc.getDocumentElement() : doc.getBody(); + } + +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java index 36c11588310..7535306d4e4 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java @@ -64,7 +64,6 @@ public class FileStoragePanel extends FlowPanel { public FileStoragePanel(FileStorageManagementGwtServiceAsync sailingService, ErrorReporter errorReporter) { this.sailingService = sailingService; this.errorReporter = errorReporter; - Button refreshButton = new Button(stringMessages.refresh()); refreshButton.addClickHandler(new ClickHandler() { @Override @@ -73,12 +72,10 @@ public class FileStoragePanel extends FlowPanel { } }); add(refreshButton); - CaptionPanel activeServicePanel = new CaptionPanel(stringMessages.active()); activeServiceLabel = new Label(); activeServicePanel.add(activeServiceLabel); add(activeServicePanel); - CaptionPanel editServicePanel = new CaptionPanel(stringMessages.edit()); VerticalPanel editServicePanelContent = new VerticalPanel(); editServicePanel.add(editServicePanelContent); @@ -90,16 +87,13 @@ public class FileStoragePanel extends FlowPanel { } }); editServicePanelContent.add(servicesListBox); - serviceDescriptionLabel = new Label(); editServicePanelContent.add(serviceDescriptionLabel); - propertiesTable = new BaseCelltable<>(); propertiesTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED); //allow for default tabbing behaviour propertiesListDataProvider = new ListDataProvider<>(new ArrayList()); properties = propertiesListDataProvider.getList(); propertiesListDataProvider.addDataDisplay(propertiesTable); - TextColumn nameColumn = new TextColumn() { @Override public String getValue(FileStorageServicePropertyDTO p) { @@ -107,7 +101,6 @@ public class FileStoragePanel extends FlowPanel { } }; propertiesTable.addColumn(nameColumn, stringMessages.name()); - Column inputColumn = new Column( new TabbingTextInputCell()) { @Override @@ -122,7 +115,6 @@ public class FileStoragePanel extends FlowPanel { } }); propertiesTable.addColumn(inputColumn, stringMessages.value()); - TextColumn descriptionColumn = new TextColumn() { @Override public String getValue(FileStorageServicePropertyDTO p) { @@ -130,7 +122,6 @@ public class FileStoragePanel extends FlowPanel { } }; propertiesTable.addColumn(descriptionColumn, stringMessages.description()); - TextColumn errorColumn = new TextColumn() { @Override public String getValue(FileStorageServicePropertyDTO p) { @@ -140,15 +131,11 @@ public class FileStoragePanel extends FlowPanel { }; errorColumn.setCellStyleNames("errorLabel"); propertiesTable.addColumn(errorColumn, stringMessages.error()); - editServicePanelContent.add(propertiesTable); - propertiesErrorLabel = new Label(); propertiesErrorLabel.setStyleName("errorLabel"); editServicePanelContent.add(propertiesErrorLabel); - HorizontalPanel buttonsPanel = new HorizontalPanel(); - Button saveAndTestPropertiesButton = new Button(stringMessages.save()); saveAndTestPropertiesButton.addClickHandler(new ClickHandler() { @Override @@ -157,7 +144,6 @@ public class FileStoragePanel extends FlowPanel { } }); buttonsPanel.add(saveAndTestPropertiesButton); - Button setAsActiveServiceButton = new Button(stringMessages.setAsActive()); setAsActiveServiceButton.addClickHandler(new ClickHandler() { @Override @@ -166,11 +152,8 @@ public class FileStoragePanel extends FlowPanel { } }); buttonsPanel.add(setAsActiveServiceButton); - editServicePanelContent.add(buttonsPanel); - add(editServicePanel); - refresh(); } @@ -273,12 +256,10 @@ public class FileStoragePanel extends FlowPanel { perPropertyErrors.clear(); serviceDescriptionLabel.setText(""); FileStorageServiceDTO selected = availableServices.get(getSelectedServiceName()); - if (selected == null) { - return; + if (selected != null) { + serviceDescriptionLabel.setText(selected.description); + properties.addAll(Arrays.asList(selected.properties)); } - - serviceDescriptionLabel.setText(selected.description); - properties.addAll(Arrays.asList(selected.properties)); } private String getLocaleInfo() { @@ -287,14 +268,12 @@ public class FileStoragePanel extends FlowPanel { private void refresh() { String oldSelectedService = getSelectedServiceName(); - servicesListBox.clear(); servicesListBox.addItem(""); availableServices.clear(); propertiesErrorLabel.setText(""); perPropertyErrors.clear(); onServiceSelectionChanged(); - sailingService.getActiveFileStorageServiceName(new AsyncCallback() { @Override public void onSuccess(String result) { @@ -306,7 +285,6 @@ public class FileStoragePanel extends FlowPanel { errorReporter.reportError(stringMessages.couldNotLoadActiveService() + ": " + caught.getMessage()); } }); - sailingService.getAvailableFileStorageServices(getLocaleInfo(), new AsyncCallback() { @Override public void onSuccess(FileStorageServiceDTO[] result) { @@ -321,14 +299,12 @@ public class FileStoragePanel extends FlowPanel { errorReporter.reportError(stringMessages.couldNotLoadAvailableServices() + ": " + caught.getMessage()); } }); - for (int i = 0; i < servicesListBox.getItemCount(); i++) { if (servicesListBox.getItemText(i).equals(oldSelectedService)) { servicesListBox.setSelectedIndex(i); } } onServiceSelectionChanged(); - testProperties(null); } } diff --git a/java/com.sap.sse.security.ui/GWT Security SDM.launch b/java/com.sap.sse.security.ui/GWT Security SDM.launch index e3402b6144b..4f18a99815c 100644 --- a/java/com.sap.sse.security.ui/GWT Security SDM.launch +++ b/java/com.sap.sse.security.ui/GWT Security SDM.launch @@ -7,7 +7,7 @@ - + @@ -38,7 +38,6 @@ - @@ -105,7 +104,7 @@ - + diff --git a/java/com.sap.sse.security/META-INF/MANIFEST.MF b/java/com.sap.sse.security/META-INF/MANIFEST.MF index ef37424199c..994486340bc 100644 --- a/java/com.sap.sse.security/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security/META-INF/MANIFEST.MF @@ -6,8 +6,7 @@ Bundle-Version: 1.0.0.qualifier Bundle-Activator: com.sap.sse.security.impl.Activator Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: com.sap.sse.util, - javax.mail;version="1.4.0", +Import-Package: javax.mail;version="1.4.0", javax.mail.internet;version="1.4.0", javax.ws.rs;version="1.1.1", javax.ws.rs.core;version="1.1.1", @@ -26,6 +25,7 @@ Require-Bundle: javax.servlet;bundle-version="3.1.0", org.apache.shiro.web;bundle-version="1.2.2", org.apache.servicemix.bundles.ehcache;bundle-version="2.6.9", org.apache.servicemix.bundles.scribe;bundle-version="1.3.3", + com.sap.sse, com.sap.sse.common, com.sap.sse.replication, com.sap.sse.operationaltransformation, diff --git a/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF b/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF index 7a9750199c9..35321b45a2f 100644 --- a/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Export-Package: com.sap.sse.concurrent, com.sap.sse.shared.media, com.sap.sse.shared.media.impl, com.sap.sse.shared.settings, + com.sap.sse.util, com.sap.sse.util.impl Import-Package: com.sap.sse.common.settings, com.sap.sse.common.settings.generic, diff --git a/java/com.sap.sse/src/com/sap/sse/util/WeakIdentityHashMap.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakIdentityHashMap.java similarity index 100% rename from java/com.sap.sse/src/com/sap/sse/util/WeakIdentityHashMap.java rename to java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakIdentityHashMap.java diff --git a/java/com.sap.sse/src/com/sap/sse/util/WeakReferenceWithKey.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakReferenceWithKey.java similarity index 100% rename from java/com.sap.sse/src/com/sap/sse/util/WeakReferenceWithKey.java rename to java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakReferenceWithKey.java diff --git a/java/com.sap.sse/src/com/sap/sse/util/WeakValueCache.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakValueCache.java similarity index 100% rename from java/com.sap.sse/src/com/sap/sse/util/WeakValueCache.java rename to java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakValueCache.java diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateEventSchedulerOnService.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateEventSchedulerOnService.java index 29ac1d61ec8..0a22ca93ba4 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateEventSchedulerOnService.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateEventSchedulerOnService.java @@ -1,7 +1,5 @@ package com.sap.sailing.racecommittee.app.services; -import java.util.Collection; - import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent; import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventScheduler; import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateEvents; @@ -18,7 +16,7 @@ public class RaceStateEventSchedulerOnService implements RaceStateEventScheduler } @Override - public void scheduleStateEvents(Collection stateEvents) { + public void scheduleStateEvents(Iterable stateEvents) { for (RaceStateEvent stateEvent : stateEvents) { service.setAlarm(race, stateEvent); }