diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 diff --git a/configuration/installPluginsForEclipse2020-06.sh b/configuration/installPluginsForEclipse2020-06.sh new file mode 100644 index 00000000000..0d62f5fe5d1 --- /dev/null +++ b/configuration/installPluginsForEclipse2020-06.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# You need an installation of the 2020-06 release of "Eclipse IDE for Eclipse Committers" matching you OS and JDK (32 vs 64 Bit): +# https://www.eclipse.org/downloads/packages/release/2020-06/r/eclipse-ide-eclipse-committers + +if [[ $1 == "" ]]; then + echo "You need to specify the Eclipse installation directory" + exit 1 +fi + +installPath="$1" + +installPlugins() { + "$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -installIU $2 + echo "" +} + +updatePlugins() { + "$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -uninstallIU $2 -installIU $2 + echo "" +} + +uninstallPlugins() { + "$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -uninstallIU $1 + echo "" +} + +# Not necessary if using Eclipse for JEE developers +echo "Installing webtools (WTP/WST/JPT) and m2e that are required by the GWT plugin..." +installPlugins http://download.eclipse.org/releases/2020-06/ org.eclipse.wst.web_ui.feature.feature.group,org.eclipse.jst.web_ui.feature.feature.group,org.eclipse.wst.xml_ui.feature.feature.group,org.eclipse.jpt.common.feature.feature.group,org.eclipse.jpt.jpa.feature.feature.group,org.eclipse.m2e.feature.feature.group,org.eclipse.m2e.wtp.feature.feature.group + +echo "Installing GWT plugin..." +installPlugins http://storage.googleapis.com/gwt-eclipse-plugin/v3/release com.gwtplugins.eclipse.suite.v3.feature.feature.group + +echo "Installing Mission Control plugin..." +installPlugins http://download.oracle.com/technology/products/missioncontrol/updatesites/base/6.0.0/eclipse com.oracle.jmc.feature.ide.feature.group,com.oracle.jmc.feature.flightrecorder.feature.group,com.oracle.jmc.feature.core.feature.group,com.oracle.jmc.feature.console.feature.group,com.oracle.jmc.feature.core.feature.group,com.oracle.jmc.feature.core.feature.group + +echo "Installing GWT SDM debug bridge..." +installPlugins http://p2.sapsailing.com/p2/sdbg com.github.sdbg.feature.feature.group + +echo "Installing EasyShell..." +installPlugins http://anb0s.github.io/EasyShell de.anbos.eclipse.easyshell.feature.feature.group + +echo "Installing Memory Analyzer..." +installPlugins http://download.eclipse.org/releases/2020-06 org.eclipse.mat.feature.feature.group,org.eclipse.mat.chart.feature.feature.group + +echo "Installing SAP JVM Tools (profiler) ..." +installPlugins https://tools.hana.ondemand.com/oxygen com.sap.jvm.profiling.feature.group + +echo "Installing UMLet ..." +installPlugins https://www.umlet.com/umlet_latest/repository/ umlet-eclipse-feature.feature.group + +echo "Installing javax.xml.bind for news feed polling (see https://stackoverflow.com/questions/52528693/eclipse-internal-error-polling-news-feeds)..." +installPlugins http://download.eclipse.org/tools/orbit/downloads/drops/R20200224183213/repository javax.xml.bind + +echo "Installing Enhanced Class Decompiler" +installPlugins https://ecd-plugin.github.io/update org.sf.feeling.decompiler.feature.group,org.sf.feeling.decompiler.cfr.feature.group,org.sf.feeling.decompiler.jad.feature.group,org.sf.feeling.decompiler.jd.feature.group,org.sf.feeling.decompiler.procyon.feature.group + +echo "Uninstalling Wild Web Developer" +uninstallPlugins org.eclipse.wildwebdeveloper.feature.feature.group + +echo "Installation completed!" diff --git a/docker/Dockerfile.tpl b/docker/Dockerfile.tpl index deb26fe8027..67e689c4d12 100755 --- a/docker/Dockerfile.tpl +++ b/docker/Dockerfile.tpl @@ -1,4 +1,4 @@ -FROM donaldduck70/sapjvm8:8.1.062 +FROM donaldduck70/sapjvm8:8.1.064 LABEL maintainer=axel.uhl@sap.com # Download and extract the release WORKDIR /home/sailing/servers/server diff --git a/docker/Dockerfile_sapjvm b/docker/Dockerfile_sapjvm index 3b8a44845ad..d0ea26e4f45 100644 --- a/docker/Dockerfile_sapjvm +++ b/docker/Dockerfile_sapjvm @@ -6,7 +6,7 @@ ENV JAVA_HOME=/opt/sapjvm_8 WORKDIR /opt RUN apt-get update \ && apt-get install -y wget unzip \ - && curl --cookie eula_3_1_agreed=tools.hana.ondemand.com/developer-license-3_1.txt "https://tools.hana.ondemand.com/additional/sapjvm-8.1.062-linux-x64.zip" --output sapjvm8-linux-x64.zip \ + && curl --cookie eula_3_1_agreed=tools.hana.ondemand.com/developer-license-3_1.txt "https://tools.hana.ondemand.com/additional/sapjvm-8.1.064-linux-x64.zip" --output sapjvm8-linux-x64.zip \ && unzip sapjvm8-linux-x64.zip \ && rm sapjvm8-linux-x64.zip \ && echo "export JAVA_HOME=/opt/sapjvm_8; export PATH=\${PATH}:/opt/sapjvm_8/bin" > /etc/profile.d/javahome.sh diff --git a/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/InfoPlist.strings index dd326d34bc3..ef1aa91e709 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/InfoPlist.strings @@ -1,8 +1,4 @@ -/* InfoPlist.strings - SAPTracker - - Created by Raimund Wege on 04.11.16. - Copyright © 2016 com.sap.sailing. All rights reserved. */ +/* InfoPlist.strings\n SAPTracker\n \n Created by Raimund Wege on 04.11.16.\n Copyright © 2016 com.sap.sailing. All rights reserved. */ "NSLocationAlwaysUsageDescription" = "Para poder realizar el rastreo de toda una carrera, es necesario que su teléfono continúe rastreando cuando apague o bloquee la pantalla, lo que ayuda a ahorrar batería y evita tener que parar el proceso de rastreo de forma involuntaria."; "NSLocationWhenInUseUsageDescription" = "Para poder realizar el rastreo de toda una carrera, es necesario que su teléfono continúe rastreando cuando apague o bloquee la pantalla, lo que ayuda a ahorrar batería y evita tener que parar el proceso de rastreo de forma involuntaria."; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/Localizable.strings index f4c2c786fe2..c618e30608e 100644 Binary files a/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/es.lproj/Localizable.strings differ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/InfoPlist.strings index 4ee4315505f..87423a3df63 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/InfoPlist.strings @@ -1,8 +1,4 @@ -/* InfoPlist.strings - SAPTracker - - Created by Raimund Wege on 04.11.16. - Copyright © 2016 com.sap.sailing. All rights reserved. */ +/* InfoPlist.strings\n SAPTracker\n \n Created by Raimund Wege on 04.11.16.\n Copyright © 2016 com.sap.sailing. All rights reserved. */ "NSLocationAlwaysUsageDescription" = "Pour pouvoir suivre l'intégralité de votre course, votre téléphone doit continuer le suivi même si vous l'éteignez ou verrouillez l'écran (pour économiser de l'énergie). Ainsi, vous évitez un arrêt non voulu du suivi."; "NSLocationWhenInUseUsageDescription" = "Pour pouvoir suivre l'intégralité de votre course, votre téléphone doit continuer le suivi même si vous l'éteignez ou verrouillez l'écran (pour économiser de l'énergie). Ainsi, vous évitez un arrêt non voulu du suivi."; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/Localizable.strings index 64680cfa2bc..8b7e1e469dc 100644 Binary files a/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/fr.lproj/Localizable.strings differ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/InfoPlist.strings index 34c93305755..a959aa61afd 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/InfoPlist.strings @@ -1,8 +1,4 @@ -/* InfoPlist.strings - SAPTracker - - Created by Raimund Wege on 04.11.16. - Copyright © 2016 com.sap.sailing. All rights reserved. */ +/* InfoPlist.strings\n SAPTracker\n \n Created by Raimund Wege on 04.11.16.\n Copyright © 2016 com.sap.sailing. All rights reserved. */ "NSLocationAlwaysUsageDescription" = "Per tracciare l'intera gara il tuo telefono deve continuare il tracciamento anche quando disattivi o blocchi lo schermo per risparmiare la batteria ed evitare di arrestare involontariamente il processo di tracciamento."; "NSLocationWhenInUseUsageDescription" = "Per tracciare l'intera gara il tuo telefono deve continuare il tracciamento anche quando disattivi o blocchi lo schermo per risparmiare la batteria ed evitare di arrestare involontariamente il processo di tracciamento."; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/Localizable.strings index 959de9374a2..96314c662c9 100644 Binary files a/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/it.lproj/Localizable.strings differ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/InfoPlist.strings index 53d83affa8f..308d324df52 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/InfoPlist.strings @@ -1,8 +1,4 @@ -/* InfoPlist.strings - SAPTracker - - Created by Raimund Wege on 04.11.16. - Copyright © 2016 com.sap.sailing. All rights reserved. */ +/* InfoPlist.strings\n SAPTracker\n \n Created by Raimund Wege on 04.11.16.\n Copyright © 2016 com.sap.sailing. All rights reserved. */ "NSLocationAlwaysUsageDescription" = "レース全体を追跡するには、画面のオフ時またはロック時にも電話で追跡が続行される必要があり、これは電池の節約や追跡プロセスの意図しない停止の回避に役立ちます。"; "NSLocationWhenInUseUsageDescription" = "レース全体を追跡するには、画面のオフ時またはロック時にも電話で追跡が続行される必要があり、これは電池の節約や追跡プロセスの意図しない停止の回避に役立ちます。"; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/Localizable.strings index f150d48ae33..2b460c7032a 100644 Binary files a/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/ja.lproj/Localizable.strings differ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/InfoPlist.strings index 302f0ffba3a..50289d72d92 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/InfoPlist.strings @@ -1,8 +1,4 @@ -/* InfoPlist.strings - SAPTracker - - Created by Raimund Wege on 04.11.16. - Copyright © 2016 com.sap.sailing. All rights reserved. */ +/* InfoPlist.strings\n SAPTracker\n \n Created by Raimund Wege on 04.11.16.\n Copyright © 2016 com.sap.sailing. All rights reserved. */ "NSLocationAlwaysUsageDescription" = "Para rastrear a corrida completa, seu telefone precisa continuar o rastreamento mesmo quando você desativa ou bloqueia a tela, o que ajuda a poupar bateria e evita a paragem acidental do processo de rastreamento."; "NSLocationWhenInUseUsageDescription" = "Para rastrear a corrida completa, seu telefone precisa continuar o rastreamento mesmo quando você desativa ou bloqueia a tela, o que ajuda a poupar bateria e evita a paragem acidental do processo de rastreamento."; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/Localizable.strings index eab9baa8131..c45dda2bd3c 100644 Binary files a/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/pt.lproj/Localizable.strings differ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/InfoPlist.strings index 2f297d544b9..080556e82fa 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/InfoPlist.strings @@ -1,8 +1,4 @@ -/* InfoPlist.strings - SAPTracker - - Created by Raimund Wege on 04.11.16. - Copyright © 2016 com.sap.sailing. All rights reserved. */ +/* InfoPlist.strings\n SAPTracker\n \n Created by Raimund Wege on 04.11.16.\n Copyright © 2016 com.sap.sailing. All rights reserved. */ "NSLocationAlwaysUsageDescription" = "Для отслеживания всей гонки ваш телефон должен продолжать отслеживание даже при выключении или блокировке экрана, что помогает экономить заряд аккумулятора и избегать непреднамеренной остановки процесса отслеживания."; "NSLocationWhenInUseUsageDescription" = "Для отслеживания всей гонки ваш телефон должен продолжать отслеживание даже при выключении или блокировке экрана, что помогает экономить заряд аккумулятора и избегать непреднамеренной остановки процесса отслеживания."; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/Localizable.strings index 73a1866fc34..7a6f9148612 100644 Binary files a/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/ru.lproj/Localizable.strings differ diff --git a/java/com.sap.sailing.competitorimport/src/com/sap/sailing/competitorimport/CompetitorProvider.java b/java/com.sap.sailing.competitorimport/src/com/sap/sailing/competitorimport/CompetitorProvider.java index 662bdfa9765..b66f9df075c 100644 --- a/java/com.sap.sailing.competitorimport/src/com/sap/sailing/competitorimport/CompetitorProvider.java +++ b/java/com.sap.sailing.competitorimport/src/com/sap/sailing/competitorimport/CompetitorProvider.java @@ -1,6 +1,7 @@ package com.sap.sailing.competitorimport; import java.io.IOException; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -44,4 +45,11 @@ public interface CompetitorProvider extends Named { * eventName as returned by {@link #getHasCompetitorsForRegattasInEvent()}. */ Iterable getCompetitorDescriptors(String eventName, String regattaName) throws JAXBException, IOException; + + /** + * A hint for users about this competitor provider; ideally mapped to the user's locale with i18n support. + * + * @return may return {@code null} + */ + String getHint(Locale locale); } 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 270a5396d55..b648f90a8ce 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 @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=Apéndices de foil FoilingSegmentName=Nombre de apéndice de foil WindFixSailingDomainRetrieverChain=Puntos de viento MarkPassingSailingDomainRetrieverChain=Superaciones de marca +TrackedRaceResultOfCompetitorRetrieverChain=Carreras RaceResultSailingDomainRetrieverChain=Resultados de la carrera RaceOfCompetitorSailingDomainRetrieverChain=Carreras de los competidores ManeuverSailingDomainRetrieverChain=Maniobra 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 1ba52525795..89e6f873dde 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 @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=Tronçons en planing FoilingSegmentName=Nom du tronçon en planing WindFixSailingDomainRetrieverChain=Points vent MarkPassingSailingDomainRetrieverChain=Passages des marques +TrackedRaceResultOfCompetitorRetrieverChain=Courses RaceResultSailingDomainRetrieverChain=Résultats de course RaceOfCompetitorSailingDomainRetrieverChain=Courses des concurrents ManeuverSailingDomainRetrieverChain=Manœuvre diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_it.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_it.properties index a6585a97b28..b6e7d7980b3 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_it.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_it.properties @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=Segmenti percorsi su foil FoilingSegmentName=Nome del segmento percorso su foil WindFixSailingDomainRetrieverChain=Punti nave vento MarkPassingSailingDomainRetrieverChain=Passaggi di segnali +TrackedRaceResultOfCompetitorRetrieverChain=Gare RaceResultSailingDomainRetrieverChain=Risultati gara RaceOfCompetitorSailingDomainRetrieverChain=Gare di concorrenti ManeuverSailingDomainRetrieverChain=Manovra 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 5caf71bb7ab..aac33ebb56b 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 @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=フォイリングしたセグメン FoilingSegmentName=フォイリングしたセグメント名 WindFixSailingDomainRetrieverChain=風フィックス MarkPassingSailingDomainRetrieverChain=マーク通過 +TrackedRaceResultOfCompetitorRetrieverChain=レース RaceResultSailingDomainRetrieverChain=レース結果 RaceOfCompetitorSailingDomainRetrieverChain=競技者のレース ManeuverSailingDomainRetrieverChain=マニューバー 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 37e746b7e8a..407c479ec64 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 @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=Segmentos com hidrofólio FoilingSegmentName=Nome do segmento com hidrofólio WindFixSailingDomainRetrieverChain=Pontos fixos de vento MarkPassingSailingDomainRetrieverChain=Passagens na marca +TrackedRaceResultOfCompetitorRetrieverChain=Corridas RaceResultSailingDomainRetrieverChain=Resultados da corrida RaceOfCompetitorSailingDomainRetrieverChain=Corridas de competidores ManeuverSailingDomainRetrieverChain=Manobra 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 9a4ff656295..c9816e6b229 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 @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=Сегменты в крыльево FoilingSegmentName=Имя сегмента в крыльевом режиме WindFixSailingDomainRetrieverChain=Замеры ветра MarkPassingSailingDomainRetrieverChain=Прохождения отметок +TrackedRaceResultOfCompetitorRetrieverChain=Гонки RaceResultSailingDomainRetrieverChain=Результаты гонки RaceOfCompetitorSailingDomainRetrieverChain=Гонки участников ManeuverSailingDomainRetrieverChain=Маневр 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 3b9137a9bc5..98ff4a27200 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 @@ -29,6 +29,7 @@ FoilingSegmentsSailingDomainRetrieverChain=水翼腾空段 FoilingSegmentName=水翼腾空段名称 WindFixSailingDomainRetrieverChain=风力修复 MarkPassingSailingDomainRetrieverChain=绕标 +TrackedRaceResultOfCompetitorRetrieverChain=比赛轮次 RaceResultSailingDomainRetrieverChain=比赛轮次结果 RaceOfCompetitorSailingDomainRetrieverChain=参赛队的比赛轮次 ManeuverSailingDomainRetrieverChain=操作 diff --git a/java/com.sap.sailing.declination/META-INF/MANIFEST.MF b/java/com.sap.sailing.declination/META-INF/MANIFEST.MF index f9c043d0a37..2d4a2661c84 100644 --- a/java/com.sap.sailing.declination/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.declination/META-INF/MANIFEST.MF @@ -8,5 +8,6 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain.common, - com.sap.sse.common + com.sap.sse.common, + com.sap.sse Export-Package: com.sap.sailing.declination diff --git a/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationImporter.java b/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationImporter.java index c7d978fc894..0b0eff3ed15 100644 --- a/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationImporter.java +++ b/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationImporter.java @@ -2,6 +2,10 @@ package com.sap.sailing.declination.impl; import java.io.IOException; import java.text.ParseException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -12,6 +16,7 @@ import org.xml.sax.SAXException; import com.sap.sailing.declination.Declination; import com.sap.sailing.domain.common.Position; import com.sap.sse.common.TimePoint; +import com.sap.sse.util.ThreadPoolUtil; public abstract class DeclinationImporter { private static final Logger logger = Logger.getLogger(DeclinationImporter.class.getName()); @@ -33,34 +38,21 @@ public abstract class DeclinationImporter { */ public Declination getDeclination(final Position position, final TimePoint timePoint, long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException { - final Declination[] result = new Declination[1]; - Thread fetcher = new Thread("Declination fetcher for "+position+"@"+timePoint) { - @Override - public void run() { - try { - Declination fetched = importRecord(position, timePoint); - synchronized (result) { - result[0] = fetched; - result.notifyAll(); - } - } catch (IOException | ParserConfigurationException | SAXException e) { - logger.log(Level.INFO, "Exception while trying to load magnetic declination online", e); - synchronized (result) { - result.notifyAll(); // wake up waiter; no result will show up anymore - } - } - } - }; - fetcher.start(); - synchronized (result) { - if (result[0] == null) { - try { - result.wait(timeoutForOnlineFetchInMilliseconds); - } catch (InterruptedException e) { - // ignore; simply use value from file in this case - } - } + ExecutorService executorService = ThreadPoolUtil.INSTANCE. + createBackgroundTaskThreadPoolExecutor(1, "DeclinationImporterThreadPoolExecutor"); + try { + return executorService.submit( + () -> importRecord(position, timePoint) + ).get(timeoutForOnlineFetchInMilliseconds, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + logger.log(Level.INFO, "Timeout while getting declination.", e); + // ignore; simply use value from file in this case + } catch (InterruptedException e) { + logger.log(Level.INFO, "InterruptedException while getting declination", e); + // ignore; simply use value from file in this case + } catch (ExecutionException e) { + logger.log(Level.INFO, "Exception while trying to load magnetic declination online", e); } - return result[0]; + return null; } } 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 8bc9dd00cb3..eaa14f24e6d 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 @@ -68,6 +68,8 @@ public enum BoatClassMasterdata { J70 ("J/70", true, 6.93, 2.25, BoatHullType.MONOHULL, true, "J70", "J-70"), J80 ("J/80", true, 8.0, 2.51, BoatHullType.MONOHULL, true, "J80", "J-80"), J88 ("J/88", true, 8.9, 1.89, BoatHullType.MONOHULL, true, "J88", "J-88"), + J92 ("J/92", true, 9.14, 3.05, BoatHullType.MONOHULL, true, "J92", "J-92"), + J92S ("J/92S", true, 9.14, 3.05, BoatHullType.MONOHULL, true, "J92S", "J-92S"), J105 ("J/105", true, 10.52, 3.35, BoatHullType.MONOHULL, true, "J105", "J-105"), J111 ("J/111", true, 11.1, 3.29, BoatHullType.MONOHULL, true, "J111", "J-111"), JK_20 ("20qm Jollenkreuzer", true, 7.75, 2.50, BoatHullType.MONOHULL, true, "JK 20", "JK20", "20er"), @@ -136,6 +138,7 @@ public enum BoatClassMasterdata { VX_ONE ("VX ONE", true, 5.79, 2.19, BoatHullType.MONOHULL, true, "VX-ONE"), WAYFARER ("Wayfarer", true, 4.82, 1.85, BoatHullType.MONOHULL, false), WETA ("Weta", true, 4.4, 3.5, BoatHullType.TRIMARAN, true, "Weta Trimaran"), + X_332 ("X-332", true, 10.06, 3.30, BoatHullType.MONOHULL, true, "X332"), X_99 ("X-99", true, 9.96, 2.95, BoatHullType.MONOHULL, true, "X99"), // multi-class "boat classes"; to be replaced at some later point in time by something like a RegattaClass 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 b868a3623c3..c6084020a99 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 @@ -1,5 +1,7 @@ package com.sap.sailing.domain.common; +import java.util.Arrays; + /** * The reasons why a competitor may get the maximum number of points, usually equaling the * number of competitors enlisted for the regatta plus one. @@ -80,4 +82,10 @@ public enum MaxPointsReason { public boolean isAppliesAtStartOfRace() { return appliesAtStartOfRace; } + + public static MaxPointsReason[] getLexicographicalValues() { + MaxPointsReason[] result = MaxPointsReason.values(); + Arrays.sort(result, (o1, o2) -> o1.name().compareToIgnoreCase(o2.name())); + return result; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java index 86980e5f00b..5620d53d81c 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java @@ -13,5 +13,14 @@ package com.sap.sailing.domain.common; * */ public interface WindFinderReviewedSpotsCollectionIdProvider { - Iterable getWindFinderReviewedSpotsCollectionIds(); + Iterable getAllWindFinderReviewedSpotsCollectionIds(); + + /** + * If WindFinder spot collections are provided scoped to events, regattas, or races, this method + * allows for a qualified search for WindFinder spot collections, using a regatta identifier as a + * qualifier. For example, if events specify WindFinder spot collection IDs, the regatta identifier + * can be traced to the events to which this regatta belongs, and then only those events' WindFinder + * spot collection IDs will be considered. + */ + Iterable getWindFinderReviewedSpotsCollectionIdsByRegatta(RegattaIdentifier regattaIdentifier); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/TrackingConnectorType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/TrackingConnectorType.java deleted file mode 100644 index 272e15e15c1..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/TrackingConnectorType.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.sap.sailing.domain.common.tracking; - -/** - * Identifies the tracking technology used for a TrackedRace. - */ -public enum TrackingConnectorType { - TracTrac("https://www.tractrac.com/"), SwissTiming(null), RaceLog(null); - - private final String defaultUrl; - - private TrackingConnectorType(String defaultUrl) { - this.defaultUrl = defaultUrl; - } - - public String getDefaultUrl() { - return defaultUrl; - } -} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF index d698ba8aba0..50dcf15136a 100644 --- a/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF @@ -13,4 +13,5 @@ Require-Bundle: org.junit;bundle-version="4.8.2", com.sap.sse.common, com.sap.sse.security.testsupport, org.mockito.mockito-core;bundle-version="1.10.14", - org.hamcrest;bundle-version="1.1.0" + org.hamcrest;bundle-version="1.1.0", + org.objenesis;bundle-version="2.1.0" diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java index 79a83f4a64e..bfb0048afe6 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java @@ -43,22 +43,24 @@ public class FixFactory { JSONArray timePointsMillis = (JSONArray) fixesJson.get("t"); int fixIndex = 0; for (Object timePointMillis : timePointsMillis) { - TimePoint timePoint = new MillisecondsTimePoint(((Number) timePointMillis).longValue()); - Map valuesPerSubindex = new HashMap<>(); - int i=1; - JSONArray values; - while ((values=(JSONArray) fixesJson.get(""+i)) != null) { - valuesPerSubindex.put(i, values.get(fixIndex)); - i++; - } - Sensor sensor = new SensorImpl(deviceSerialNumber, - fixTypeAndOptionalColonSeparatedSensorsSubId.length < 2 ? 0 - : Long.valueOf(fixTypeAndOptionalColonSeparatedSensorsSubId[1])); - final Type ft = Type.valueOf(fixType); - if (ft != null) { - Fix fix = createFix(sensor, ft, timePoint, valuesPerSubindex); - result.add(fix); - fixIndex++; + if (timePointMillis != null) { + TimePoint timePoint = new MillisecondsTimePoint(((Number) timePointMillis).longValue()); + Map valuesPerSubindex = new HashMap<>(); + int i=1; + JSONArray values; + while ((values=(JSONArray) fixesJson.get(""+i)) != null) { + valuesPerSubindex.put(i, values.get(fixIndex)); + i++; + } + Sensor sensor = new SensorImpl(deviceSerialNumber, + fixTypeAndOptionalColonSeparatedSensorsSubId.length < 2 ? 0 + : Long.valueOf(fixTypeAndOptionalColonSeparatedSensorsSubId[1])); + final Type ft = Type.valueOf(fixType); + if (ft != null) { + Fix fix = createFix(sensor, ft, timePoint, valuesPerSubindex); + result.add(fix); + fixIndex++; + } } } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java index 96f16429274..5ac7b577f35 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -32,6 +32,8 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.mail.MailException; public interface RaceLogTrackingAdapter { + String NAME = "RaceLog"; + String DEFAULT_URL = null; /** * Performs the necessary steps to ensure that the race is tracked (aka that a {@link TrackedRace} is created from * the data in this {@code RaceLog}). diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index d476f76c453..b0eb321c059 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -64,8 +64,8 @@ import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLo import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; import com.sap.sailing.domain.common.racelog.tracking.RaceNotCreatedException; -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; import com.sap.sailing.domain.regattalike.IsRegattaLike; import com.sap.sailing.domain.tracking.AbstractRaceTrackerBaseImpl; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -369,7 +369,7 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { params.getDelayToLiveInMillis(), WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, boatClass.getApproximateManeuverDurationInMilliseconds(), null, /*useMarkPassingCalculator*/ true, raceLogResolver, /* Not needed because the RaceTracker is not active on a replica */ Optional.empty(), - new TrackingConnectorInfoImpl(TrackingConnectorType.RaceLog, /* webUrl */ null)); + new TrackingConnectorInfoImpl(RaceLogTrackingAdapter.NAME, RaceLogTrackingAdapter.DEFAULT_URL, /* no webUrl */ null)); notifyRaceCreationListeners(); logger.info(String.format("Started tracking race-log race (%s)", raceLog)); // this wakes up all waiting race handles diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java index d2b3b276790..8a9551a0fd0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java @@ -13,7 +13,7 @@ public interface RaceStateChangedListener { * TODO: We need to recheck the AbortFinder to enable these interface methods * void onAborted(RaceState2 state); * void onGeneralRecall(RaceState2 state);*/ - void onFinishingPositioningsChanged(ReadonlyRaceState state); + void onFinishingPositionsChanged(ReadonlyRaceState state); void onFinishingPositionsConfirmed(ReadonlyRaceState state); void onCourseDesignChanged(ReadonlyRaceState state); void onWindFixChanged(ReadonlyRaceState state); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java index 5d7db3578c0..3dc7a0fc842 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java @@ -44,7 +44,7 @@ public abstract class BaseRaceStateChangedListener implements RaceStateChangedLi } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + public void onFinishingPositionsChanged(ReadonlyRaceState state) { } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java index c03f3845e01..c9bbd7dcc7c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java @@ -85,9 +85,9 @@ public class RaceStateChangedListeners extends HashSet } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + public void onFinishingPositionsChanged(ReadonlyRaceState state) { for (RaceStateChangedListener listener : getWorkingCopyOfListeners()) { - listener.onFinishingPositioningsChanged(state); + listener.onFinishingPositionsChanged(state); } } 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 7ef2f5ecdde..aa85034cc1d 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 @@ -557,7 +557,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL CompetitorResults positionedCompetitors = finishPositioningListAnalyzer.analyze().getCompetitorResults(); if (!Util.equalsWithNull(cachedPositionedCompetitors, positionedCompetitors)) { cachedPositionedCompetitors = positionedCompetitors; - changedListeners.onFinishingPositioningsChanged(this); + changedListeners.onFinishingPositionsChanged(this); } CompetitorResultsAndTheirCreationTimePoints confirmedPositionedCompetitors = confirmedFinishPositioningListAnalyzer.analyze(); if (!Util.equalsWithNull(cachedConfirmedPositionedCompetitors, confirmedPositionedCompetitors)) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java index edf551c1cee..0615a692a7c 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java @@ -6,9 +6,8 @@ import com.sap.sse.common.NamedWithID; import com.sap.sse.common.Renamable; import com.sap.sse.security.shared.WithQualifiedObjectIdentifier; -public interface LeaderboardGroupBase extends Renamable, NamedWithID, WithQualifiedObjectIdentifier { +public interface LeaderboardGroupBase extends Renamable, NamedWithID, WithQualifiedObjectIdentifier, WithDescription { UUID getId(); - String getDescription(); void setDescriptiom(String description); boolean hasOverallLeaderboard(); String getDisplayName(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/TrackingConnectorInfo.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/TrackingConnectorInfo.java index 936bfa77c91..6c8871e631f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/TrackingConnectorInfo.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/TrackingConnectorInfo.java @@ -1,9 +1,7 @@ + package com.sap.sailing.domain.tracking; import java.io.Serializable; -import java.net.URL; - -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; /** * Identifies the tracking connector that was used to create a TrackedRace. @@ -12,13 +10,19 @@ import com.sap.sailing.domain.common.tracking.TrackingConnectorType; public interface TrackingConnectorInfo extends Serializable { /** - * gets the {@link TrackingConnectorType} associated with the tracking technology used for the Race + * gets the name associated with the tracking technology used for the Race */ - TrackingConnectorType getTrackingConnectorType(); + String getTrackingConnectorName(); /** - * gets a {@link URL} representation of the web-URL associated with the Event; - * may be {@code null} if the API of the respective Tracking-Service does not provide a URL; + * gets a {@link String} representation of the default web-URL associated with the tracking technology used for the Race. + * may be {@code null} if there is none provided in the adapter. */ - URL getWebUrl(); + String getTrackingConnectorDefaultUrl(); + + /** + * gets a {@link String} representation of the web-URL associated with the Event. + * may be {@code null} if the API of the respective Tracking-Service does not provide a URL. + */ + String getWebUrl(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackingConnectorInfoImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackingConnectorInfoImpl.java index 65a8a27434c..d3a162bdcd0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackingConnectorInfoImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackingConnectorInfoImpl.java @@ -1,26 +1,29 @@ package com.sap.sailing.domain.tracking.impl; -import java.net.URL; - -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; import com.sap.sailing.domain.tracking.TrackingConnectorInfo; public class TrackingConnectorInfoImpl implements TrackingConnectorInfo { private static final long serialVersionUID = 7970268841592389145L; - private final TrackingConnectorType trackingConnectorType; - private final URL webUrl; + private final String trackingConnectorName; + private final String TtackingConnectorDefaultUrl; + private final String webUrl; - public TrackingConnectorInfoImpl(TrackingConnectorType trackingConnectorType, URL webUrl) { + public TrackingConnectorInfoImpl(String trackingConnectorName, String trackingConnectorDefaultUrl, String webUrl) { super(); - this.trackingConnectorType = trackingConnectorType; + this.trackingConnectorName = trackingConnectorName; + TtackingConnectorDefaultUrl = trackingConnectorDefaultUrl; this.webUrl = webUrl; } - public TrackingConnectorType getTrackingConnectorType() { - return trackingConnectorType; + public String getTrackingConnectorDefaultUrl() { + return TtackingConnectorDefaultUrl; } - public URL getWebUrl() { + public String getTrackingConnectorName() { + return trackingConnectorName; + } + + public String getWebUrl() { return webUrl; } @@ -28,7 +31,8 @@ public class TrackingConnectorInfoImpl implements TrackingConnectorInfo { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((trackingConnectorType == null) ? 0 : trackingConnectorType.hashCode()); + result = prime * result + ((TtackingConnectorDefaultUrl == null) ? 0 : TtackingConnectorDefaultUrl.hashCode()); + result = prime * result + ((trackingConnectorName == null) ? 0 : trackingConnectorName.hashCode()); result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode()); return result; } @@ -42,10 +46,15 @@ public class TrackingConnectorInfoImpl implements TrackingConnectorInfo { if (getClass() != obj.getClass()) return false; TrackingConnectorInfoImpl other = (TrackingConnectorInfoImpl) obj; - if (trackingConnectorType == null) { - if (other.trackingConnectorType != null) + if (TtackingConnectorDefaultUrl == null) { + if (other.TtackingConnectorDefaultUrl != null) return false; - } else if (!trackingConnectorType.equals(other.trackingConnectorType)) + } else if (!TtackingConnectorDefaultUrl.equals(other.TtackingConnectorDefaultUrl)) + return false; + if (trackingConnectorName == null) { + if (other.trackingConnectorName != null) + return false; + } else if (!trackingConnectorName.equals(other.trackingConnectorName)) return false; if (webUrl == null) { if (other.webUrl != null) diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/RPDMessageParsingTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/RPDMessageParsingTest.java index 08117ab5e2d..5435493ccbd 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/RPDMessageParsingTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/RPDMessageParsingTest.java @@ -34,7 +34,7 @@ public class RPDMessageParsingTest { private void assertRPDMessageCanBeParsed(final String rpdMessage) throws InterruptedException, ParseException, IOException { final SailMasterMessage message = new SailMasterMessageImpl(rpdMessage); - final SailMasterConnectorImpl connector = new SailMasterConnectorImpl(/* host */ "8.8.8.8", /* port */ 443, /* raceId */ null, /* raceName */ null, /* raceDescription */ null, /* boatClass */ null, /* SwissTimingRaceTracker */ null); + final SailMasterConnectorForSocket connector = new SailMasterConnectorForSocket(/* host */ "8.8.8.8", /* port */ 443, /* raceId */ null, /* raceName */ null, /* raceDescription */ null, /* boatClass */ null, /* SwissTimingRaceTracker */ null); final boolean[] receivedPositionData = new boolean[1]; final SailMasterListener listener = new SailMasterAdapter() { @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java index 77b709747df..b405473b5a8 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java @@ -24,6 +24,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Fix; @@ -64,9 +65,9 @@ public class SailMasterConnectivityTest { Race race4702 = new RaceImpl("W4702", "R2", "470 Women Race 2"); Race race4711 = new RaceImpl("4711", "R2", "A wonderful test race"); Race race4712 = new RaceImpl("4712", "R2", "Not such a wonderful race"); - connector4702 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4702.getRaceID(), race4702.getRaceName(), race4702.getDescription(), null /* boat class */, /* SwissTimingRaceTracker */ null); - connector4711 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4711.getRaceID(), race4702.getRaceName(), race4711.getDescription(), null /* boat class */, /* SwissTimingRaceTracker */ null); - connector4712 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4712.getRaceID(), race4702.getRaceName(), race4712.getDescription(), null /* boat class */, /* SwissTimingRaceTracker */ null); + connector4702 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4702.getRaceID(), /* raceDataUrl */ null, race4702.getRaceName(), race4702.getDescription(), null /* boat class */, /* SwissTimingRaceTracker */ null); + connector4711 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4711.getRaceID(), /* raceDataUrl */ null, race4702.getRaceName(), race4711.getDescription(), null /* boat class */, /* SwissTimingRaceTracker */ null); + connector4712 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4712.getRaceID(), /* raceDataUrl */ null, race4702.getRaceName(), race4712.getDescription(), null /* boat class */, /* SwissTimingRaceTracker */ null); Thread.sleep(1000); // wait until all connectors are really connected to the SailMasterDummy } @@ -111,7 +112,7 @@ public class SailMasterConnectivityTest { @Override public void receivedAvailableRaces(Iterable races) {} @Override - public void storedDataProgress(String raceID, double progress) {} + public void storedDataProgress(String raceID, double progress, TrackedRaceStatusEnum statusAfterLoadingComplete) {} @Override public void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, double windSpeedInKnots) {} @Override @@ -153,7 +154,7 @@ public class SailMasterConnectivityTest { @Override public void receivedAvailableRaces(Iterable races) {} @Override - public void storedDataProgress(String raceID, double progress) {} + public void storedDataProgress(String raceID, double progress, TrackedRaceStatusEnum statusAfterLoadingComplete) {} @Override public void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, double windSpeedInKnots) {} @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java index eba4b5c719c..9f59030a1d6 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java @@ -18,6 +18,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Fix; @@ -41,7 +42,7 @@ public class SwissTimingSailMasterLiveTest implements SailMasterListener { @Before public void connect() throws InterruptedException, ParseException { - connector = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("gps.sportresult.com", 40300, "W4702", "R2", "Women 470 Race 2", null /* boat class*/, /* SwissTimingRaceTracker */ null); + connector = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("gps.sportresult.com", 40300, "W4702", /* raceDataUrl */ null, "R2", "Women 470 Race 2", null /* boat class*/, /* SwissTimingRaceTracker */ null); } @After @@ -195,7 +196,7 @@ public class SwissTimingSailMasterLiveTest implements SailMasterListener { } @Override - public void storedDataProgress(String raceID, double progress) { + public void storedDataProgress(String raceID, double progress, TrackedRaceStatusEnum statusAfterLoadingComplete) { } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java index 8e1bd6023f8..c750c421e9d 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.swisstimingadapter; import java.util.Collection; import java.util.List; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -38,7 +39,7 @@ public abstract class SailMasterAdapter implements SailMasterListener { } @Override - public void storedDataProgress(String raceID, double progress) { + public void storedDataProgress(String raceID, double progress, TrackedRaceStatusEnum statusAfterLoadingComplete) { } @Override 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 cdeaf3a7368..30cf13c4407 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 @@ -4,6 +4,7 @@ import java.net.URISyntaxException; import java.util.Collection; import java.util.List; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -26,8 +27,12 @@ public interface SailMasterListener { /** * Announces the progress of data loaded from a local persistent store (as opposed to receiving it live from a * SailMaster instance). 0.0 means no progress so far, 1.0 means loading from persistent store completed. + * + * @param statusAfterLoadingComplete + * the status into which to put the race if 100% loading status have been reached; usually one of + * TRACKING or FINISHED */ - void storedDataProgress(String raceID, double progress); + void storedDataProgress(String raceID, double progress, TrackedRaceStatusEnum statusAfterLoadingComplete); void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, double windSpeedInKnots); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterTransceiver.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterTransceiver.java index b279a9cfa67..445cad0c27f 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterTransceiver.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterTransceiver.java @@ -9,6 +9,8 @@ public interface SailMasterTransceiver { /** * Sends a message, prefixing it with a start byte and suffixing it with an end byte, as required by the * SwissTiming SailMaster protocol. + * + * @param os if {@code null}, this method is a no-op */ void sendMessage(String message, OutputStream os) throws IOException; @@ -16,6 +18,8 @@ public interface SailMasterTransceiver { * Sends the message, prefixing it with a start byte and suffixing it with an end byte, as required by the * SwissTiming SailMaster protocol. Before the start byte, the ASCII-encoded * {@link SailMasterMessage#getSequenceNumber() sequence number} is transmitted. + * + * @param os if {@code null}, this method is a no-op */ void sendMessage(SailMasterMessage message, OutputStream os) throws IOException; 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 1851b882945..1d25396a8df 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 @@ -16,6 +16,9 @@ import com.sap.sailing.domain.tracking.TrackerManager; import com.sap.sailing.xrr.schema.RegattaResults; public interface SwissTimingAdapter { + String NAME = "SwissTiming"; + String DEFAULT_URL = null; + List getSwissTimingRaceRecords(String hostname, int port) throws InterruptedException, UnknownHostException, IOException, ParseException; /** @@ -42,5 +45,5 @@ public interface SwissTimingAdapter { SwissTimingFactory getSwissTimingFactory(); com.sap.sailing.domain.swisstimingadapter.DomainFactory getSwissTimingDomainFactory(); - + } 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 0558afdf23b..2e33ef015ad 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 @@ -2,6 +2,7 @@ package com.sap.sailing.domain.swisstimingadapter; import java.io.IOException; import java.net.URISyntaxException; +import java.net.URL; import java.net.UnknownHostException; import java.text.ParseException; @@ -42,10 +43,13 @@ public interface SwissTimingFactory { * Generally, the connector needs to be instructed for which races it shall handle events using calls to the * {@link SailMasterConnector#trackRace} and {@link SailMasterConnector#stopTrackingRace} operations. * {@link MessageType#isRaceSpecific() Race-specific messages} for other races are ignored and not forwarded to any - * listener. + * listener.

+ * + * When the {@code hostname} is {@code null}, this is assumed to request a URL-based connection, and {@code raceDataUrl} has + * to provide a valid URL leading to a downloadable log file. */ - SailMasterConnector getOrCreateSailMasterConnector(String hostname, int port, String raceId, String raceName, - String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) + SailMasterConnector getOrCreateSailMasterConnector(String hostname, int port, String raceId, URL raceDataUrl, + String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException; SailMasterConnector getOrCreateSailMasterLiveSimulatorConnector(String host, int port, String raceId, String raceName, diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/AbstractSailMasterConnector.java similarity index 92% rename from java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java rename to java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/AbstractSailMasterConnector.java index b6cfaa0ba84..09a8eb908a5 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/AbstractSailMasterConnector.java @@ -3,7 +3,6 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.text.DateFormat; @@ -30,6 +29,7 @@ import java.util.stream.Collectors; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; @@ -83,12 +83,9 @@ import com.sap.sse.util.impl.UUIDHelper; * @author Axel Uhl (d043530) * */ -public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implements SailMasterConnector, Runnable { - private static final Logger logger = Logger.getLogger(SailMasterConnectorImpl.class.getName()); +public abstract class AbstractSailMasterConnector extends SailMasterTransceiverImpl implements SailMasterConnector, Runnable { + private static final Logger logger = Logger.getLogger(AbstractSailMasterConnector.class.getName()); - private final String host; - private final int port; - private Socket socket; private final DateFormat dateFormat; private final Set listeners; private final Thread receiverThread; @@ -131,7 +128,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private Long numberOfStoredMessages; - public SailMasterConnectorImpl(String host, int port, String raceId, String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { + /** + * Subclasses must invoke {@link #startReceiverThread()} in their constructor before the constructor completes, but + * usually after finishing the initialization of all their fields. + */ + protected AbstractSailMasterConnector(String raceId, String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { super(); maxSequenceNumber = -1l; this.raceId = raceId; // from this time on, the connector interprets messages for raceID @@ -139,12 +140,13 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement this.raceDescription = raceDescription; this.boatClass = boatClass; dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - this.host = host; - this.port = port; - this.listeners = new HashSet(); - this.unprocessedMessagesByType = new HashMap>(); + this.listeners = new HashSet<>(); + this.unprocessedMessagesByType = new HashMap<>(); this.addSailMasterListener(swissTimingRaceTracker); receiverThread = new Thread(this, "SwissTiming SailMaster Receiver"); + } + + protected void startReceiverThread() { receiverThread.start(); } @@ -152,15 +154,15 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement try { while (!stopped) { try { - ensureSocketIsOpen(); // the result of this may be that the connector w\s stopped in between, so another check is required - if (!stopped && socket != null) { - String receivedMessage = receiveMessage(socket.getInputStream()); + ensureConnected(); // the result of this may be that the connector w\s stopped in between, so another check is required + if (!stopped && isConnected()) { + String receivedMessage = receiveMessage(getInputStream()); if (receivedMessage == null) { + logger.info("Reached EOF for "+this+"; disconnecting"); // reached EOF; this means the socket is or can be closed - if (socket != null && !socket.isClosed()) { - socket.close(); + if (isConnected()) { + disconnect(); } - socket = null; } else { SailMasterMessage message = new SailMasterMessageImpl(receivedMessage); // drop race-specific messages for non-tracked races @@ -188,12 +190,12 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } catch (SocketException se) { // This occurs if the socket was closed which may mean the connector was stopped. Check in while logger.info("Caught exception "+se+" during socket operation; setting socket to null"); - socket = null; + disconnect(); Thread.sleep(1000); // try again in 1s } } } catch (Exception e) { - logger.log(Level.SEVERE, "Exception in sail master connector "+SailMasterConnectorImpl.class.getName()+".run for "+this, e); + logger.log(Level.SEVERE, "Exception in sail master connector "+AbstractSailMasterConnector.class.getName()+".run for "+this, e); } logger.info("Stopping Sail Master connector thread for "+this); stopped = true; @@ -261,13 +263,15 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private void notifyListenersStoredDataProgress(String raceID, double progress) { for (SailMasterListener listener : getListeners()) { try { - listener.storedDataProgress(raceID, progress); + listener.storedDataProgress(raceID, progress, getStatusAfterLoadingIsComplete()); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about progress "+progress); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyStoredDataProgress", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyStoredDataProgress", e); } } } + + protected abstract TrackedRaceStatusEnum getStatusAfterLoadingIsComplete(); private void notifyListenersWND(SailMasterMessage message) { // example message: WND|W4702|1|320|5.4 @@ -280,7 +284,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement listener.receivedWindData(raceID, zeroBasedMarkIndex, windDirectionTrueDegrees, windSpeedInKnots); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersWND", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersWND", e); } } } @@ -304,7 +308,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement listener.receivedTimingData(raceID, competitorIdAsString, markIndicesRanksAndTimesSinceStartInMilliseconds); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersTMD", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersTMD", e); } } } @@ -316,7 +320,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement listener.receivedClockAtMark(message.getSections()[1], clockAtMarkResults); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCAM", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersCAM", e); } } } @@ -328,7 +332,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement listener.receivedStartList(message.getSections()[1], startListMessage); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersSTL", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersSTL", e); } } } @@ -340,7 +344,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement listener.receivedCourseConfiguration(message.getSections()[1], course); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCCG", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersCCG", e); } } } @@ -352,7 +356,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement listener.receivedAvailableRaces(races); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRAC", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersRAC", e); } } // not race specific; no need to notify any listener from raceSpecificListeners @@ -447,7 +451,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement millisecondsSinceRaceStart, nextMarkIndexForLeader, distanceToNextMarkForLeader, fixes); } catch (Exception e) { logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); - logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRPD", e); + logger.throwing(AbstractSailMasterConnector.class.getName(), "notifyListenersRPD", e); } } } @@ -458,19 +462,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } } - @Override - public String toString() { - return "SailMasterConnector listening on port "+port+" with socket "+socket+" for race "+raceName+" with ID "+raceId+" ("+raceDescription+")"; - } - @Override public void stop() throws IOException { - logger.info("Stopping "+this); stopped = true; - if (socket != null) { - socket.close(); - socket = null; - } + logger.info("Stopping "+this); + disconnect(); synchronized (this) { notifyAll(); } @@ -499,13 +495,13 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } public SailMasterMessage sendRequestAndGetResponse(MessageType messageType, String... args) throws UnknownHostException, IOException, InterruptedException { - ensureSocketIsOpen(); + ensureConnected(); return sendRequestAndGetResponseAssumingSocketIsOpen(messageType, args); } private SailMasterMessage sendRequestAndGetResponseAssumingSocketIsOpen(MessageType messageType, String... args) throws IOException, InterruptedException { - OutputStream os = socket.getOutputStream(); + OutputStream os = getOutputStream(); final SailMasterMessage sailMasterMessage = createSailMasterMessage(messageType, args); sendMessage(sailMasterMessage, os); return receiveMessage(messageType); @@ -525,17 +521,18 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement final SailMasterMessage sailMasterMessage = new SailMasterMessageImpl(requestMessage.toString()); return sailMasterMessage; } + + protected abstract OutputStream getOutputStream() throws IOException; + protected abstract InputStream getInputStream() throws IOException; private final Object ensureSocketIsOpenSemaphor = new Object(); - private void ensureSocketIsOpen() throws InterruptedException { + private void ensureConnected() throws InterruptedException, NumberFormatException, IOException { synchronized (ensureSocketIsOpenSemaphor) { - while (!stopped && socket == null) { + while (!stopped && !isConnected()) { try { - logger.info("Opening socket to " + host + ":" + port + " and sending " + MessageType.OPN.name() - + " message..."); - socket = new Socket(host, port); - final OutputStream os = socket.getOutputStream(); - final InputStream is = socket.getInputStream(); + connect(); + final OutputStream os = getOutputStream(); + final InputStream is = getInputStream(); final SailMasterMessage opnRequest = createSailMasterMessage(MessageType.OPN, raceId); sendMessage(opnRequest, os); SailMasterMessage opnResponse = new SailMasterMessageImpl(receiveMessage(is)); @@ -543,7 +540,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement logger.info("Recevied non-OK response " + opnResponse + " in "+this+" for our request " + opnRequest + ". Closing socket and stopping because we have no hope for recovery"); stopped = true; - closeSocket(); + disconnect(); } else { logger.info("Received " + opnResponse + " in "+this+" which seems OK. Continuing with " + MessageType.LSN.name() + " request..."); @@ -565,7 +562,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement if (lsnResponse.getType() != MessageType.LSN || !"OK".equals(lsnResponse.getSections()[1])) { logger.info("Received non-OK response " + lsnResponse + " for our request " + lsnRequest + " in "+this+". Closing socket and trying again in 1s..."); - closeAndNullSocketAndWaitABit(); + disconnectAndWaitABit(); } else { logger.info("Received " + lsnResponse + " which seems to be OK. I think we're connected in "+this+"!"); @@ -573,28 +570,23 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } } catch (IOException e) { logger.log(Level.INFO, "Exception trying to establish connection in "+this+". Trying again in 1s.", e); - closeAndNullSocketAndWaitABit(); + disconnectAndWaitABit(); } } } } - private void closeAndNullSocketAndWaitABit() throws InterruptedException { - closeSocket(); + protected abstract void connect() throws IOException; + + protected abstract boolean isConnected() throws IOException; + + protected abstract void disconnect() throws IOException; + + private void disconnectAndWaitABit() throws InterruptedException, IOException { + disconnect(); Thread.sleep(1000); } - - private void closeSocket() { - if (socket != null) { - try { - socket.close(); - } catch (IOException e) { - logger.log(Level.INFO, "Exception trying to close socket. Maybe already closed. Continuing", e); - } - socket = null; - } - } - + @Override public Race getRace() { return new RaceImpl(raceId, raceName, raceDescription, boatClass); @@ -857,5 +849,5 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement public void disableRacePositionData() throws UnknownHostException, IOException, InterruptedException { sendRequestAndGetResponse(MessageType.RPD, "0"); } - + } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForSocket.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForSocket.java new file mode 100755 index 00000000000..3c3bd519e51 --- /dev/null +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForSocket.java @@ -0,0 +1,96 @@ +package com.sap.sailing.domain.swisstimingadapter.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.text.ParseException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.swisstimingadapter.MessageType; + +/** + * Implements the connector to the SwissTiming Sail Master system. It uses a host name and port number to establish the + * connection via TCP. The connector offers a number of explicit service request methods. Additionally, the connector + * can receive "spontaneous" events sent by the sail master system. Clients can register for those spontaneous events + * (see {@link #addSailMasterListener}). + *

+ * + * When the connector is used with SailMaster instances hidden behind a "bridge" / firewall, no explicit requests are + * possible, and the connector has to rely solely on the events it receives. It may, though, load recorded race-specific + * messages through a {@link RaceSpecificMessageLoader} object. If a non-null {@link RaceSpecificMessageLoader} + * is provided to the constructor, the connector will fetch the {@link #getRace() race} from that loader. + * Additionally, the connector will use the loader upon each {@link #trackRace(String)} to load all messages recorded + * by the loader for the race requested so far. + *

+ * + * Generally, the connector needs to be instructed for which races it shall handle events using calls to the + * {@link #trackRace} and {@link #stopTrackingRace} operations. {@link MessageType#isRaceSpecific() Race-specific + * messages} for other races are ignored and not forwarded to any listener.

+ * + * Clients that want to wait until the connector changes to {@link #isStopped()} can {@link Object#wait()} on this + * object because it notifies all waiters when changing from !{@link #isStopped()} to {@link #isStopped()}. + * + * @author Axel Uhl (d043530) + * + */ +public class SailMasterConnectorForSocket extends AbstractSailMasterConnector { + private static final Logger logger = Logger.getLogger(SailMasterConnectorForSocket.class.getName()); + + private final String host; + private final int port; + + /** + * Will have a socket connecting to {@link #host}:{@link #port} + */ + private Socket socket; + + public SailMasterConnectorForSocket(String host, int port, String raceId, String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { + super(raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); + this.host = host; + this.port = port; + startReceiverThread(); + } + + @Override + protected void disconnect() { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + logger.log(Level.INFO, "Exception trying to close socket. Maybe already closed. Continuing", e); + } + socket = null; + } + } + + @Override + protected OutputStream getOutputStream() throws IOException { + return socket.getOutputStream(); + } + + @Override + protected InputStream getInputStream() throws IOException { + return socket.getInputStream(); + } + + @Override + protected void connect() throws IOException { + logger.info("Opening socket to " + host + ":" + port + " and sending " + MessageType.OPN.name() + + " message..."); + socket = new Socket(host, port); + } + + @Override + protected boolean isConnected() { + return socket != null && socket.isConnected(); + } + + @Override + protected TrackedRaceStatusEnum getStatusAfterLoadingIsComplete() { + return TrackedRaceStatusEnum.TRACKING; // more stuff can come through the socket + } +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForUrlDownload.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForUrlDownload.java new file mode 100755 index 00000000000..e9e439b9090 --- /dev/null +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorForUrlDownload.java @@ -0,0 +1,95 @@ +package com.sap.sailing.domain.swisstimingadapter.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.text.ParseException; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.swisstimingadapter.MessageType; +import com.sap.sse.util.HttpUrlConnectionHelper; + +/** + * Implements the connector to the SwissTiming Sail Master system. It uses a host name and port number to establish the + * connection via TCP. The connector offers a number of explicit service request methods. Additionally, the connector + * can receive "spontaneous" events sent by the sail master system. Clients can register for those spontaneous events + * (see {@link #addSailMasterListener}). + *

+ * + * When the connector is used with SailMaster instances hidden behind a "bridge" / firewall, no explicit requests are + * possible, and the connector has to rely solely on the events it receives. It may, though, load recorded race-specific + * messages through a {@link RaceSpecificMessageLoader} object. If a non-null {@link RaceSpecificMessageLoader} + * is provided to the constructor, the connector will fetch the {@link #getRace() race} from that loader. + * Additionally, the connector will use the loader upon each {@link #trackRace(String)} to load all messages recorded + * by the loader for the race requested so far. + *

+ * + * Generally, the connector needs to be instructed for which races it shall handle events using calls to the + * {@link #trackRace} and {@link #stopTrackingRace} operations. {@link MessageType#isRaceSpecific() Race-specific + * messages} for other races are ignored and not forwarded to any listener.

+ * + * Clients that want to wait until the connector changes to {@link #isStopped()} can {@link Object#wait()} on this + * object because it notifies all waiters when changing from !{@link #isStopped()} to {@link #isStopped()}. + * + * @author Axel Uhl (d043530) + * + */ +public class SailMasterConnectorForUrlDownload extends AbstractSailMasterConnector { + private static final Logger logger = Logger.getLogger(SailMasterConnectorForUrlDownload.class.getName()); + + private final URL raceDataUrl; + + /** + * If the {@link #socket} is not being used, a {@link HttpURLConnection} is expected to provide the + * {@link InputStream} from which this connector reads. + */ + private URLConnection urlConnection; + + public SailMasterConnectorForUrlDownload(String raceId, URL raceDataUrl, String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { + super(raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); + this.raceDataUrl = raceDataUrl; + startReceiverThread(); + } + + @Override + protected OutputStream getOutputStream() throws IOException { + return null; // we don't write to the URL connection's output stream + } + + @Override + protected InputStream getInputStream() throws IOException { + return urlConnection.getInputStream(); + } + + @Override + protected void connect() throws IOException { + logger.info("Connecting to "+raceDataUrl); + urlConnection = HttpUrlConnectionHelper.redirectConnection(raceDataUrl); + } + + @Override + protected boolean isConnected() throws IOException { + return urlConnection != null && getInputStream() != null; + } + + @Override + protected void disconnect() throws IOException { + if (urlConnection != null) { + if (urlConnection.getInputStream() != null) { + urlConnection.getInputStream().close(); + } + urlConnection = null; + stop(); // disconnecting from the URL also means to stop the connector + } + } + + @Override + protected TrackedRaceStatusEnum getStatusAfterLoadingIsComplete() { + return TrackedRaceStatusEnum.FINISHED; // nothing more to come + } +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java index 3ee62dbcced..69fd38d80d3 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java @@ -1,33 +1,41 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.text.ParseException; -import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; -public class SailMasterLiveSimulatorConnectorImpl extends SailMasterConnectorImpl { +/** + * Delivers SwissTiming messages with a constant delay (default 250ms) between them. This produces a simulated "live feed" + * that may be used for testing purposes. + */ +public class SailMasterLiveSimulatorConnectorImpl extends SailMasterConnectorForSocket { + private final static Logger logger = Logger.getLogger(SailMasterLiveSimulatorConnectorImpl.class.getName()); + private final List bufferedMessageList; private long messageDeliveryIntervalInMs = Long.valueOf(System.getProperty("simulateLiveMode.delayInMillis", "250")); public SailMasterLiveSimulatorConnectorImpl(String host, int port, String raceId, String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { - super(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); - bufferedMessageList = Collections.synchronizedList(new ArrayList()); + super(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); // causes original delivery to this.notifyListeners(...) + bufferedMessageList = Collections.synchronizedList(new LinkedList<>()); Thread messageDeliveryThread = new Thread("SailMasterLiveSimulatorConnector") { public void run() { - while(true) { + while (true) { try { - if(!bufferedMessageList.isEmpty()) { - SailMasterMessage message = bufferedMessageList.get(0); + if (!bufferedMessageList.isEmpty()) { + SailMasterMessage message = bufferedMessageList.remove(0); notifyParentListeners(message); - bufferedMessageList.remove(0); - } + } Thread.sleep(messageDeliveryIntervalInMs); } catch (Exception e) { + logger.log(Level.SEVERE, "Error trying to simulate SwissTiming messages", e); } } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterTransceiverImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterTransceiverImpl.java index 6663131b088..0a6a215f225 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterTransceiverImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterTransceiverImpl.java @@ -27,23 +27,27 @@ public class SailMasterTransceiverImpl implements SailMasterTransceiver { */ @Override public synchronized void sendMessage(String message, OutputStream os) throws IOException { - os.write(STX); - os.write(message.getBytes(Charset.forName("cp1252"))); - os.write(ETX); + if (os != null) { + os.write(STX); + os.write(message.getBytes(Charset.forName("cp1252"))); + os.write(ETX); + } } @Override public synchronized void sendMessage(SailMasterMessage message, OutputStream os) throws IOException { - if (message.getSequenceNumber() == null) { - sendMessage(message.getMessage(), os); - } else { - StringBuilder sb = new StringBuilder(); - sb.append(message.getSequenceNumber()); - sb.append('|'); - sb.append(message.getMessage()); - sendMessage(sb.toString(), os); + if (os != null) { + if (message.getSequenceNumber() == null) { + sendMessage(message.getMessage(), os); + } else { + StringBuilder sb = new StringBuilder(); + sb.append(message.getSequenceNumber()); + sb.append('|'); + sb.append(message.getMessage()); + sendMessage(sb.toString(), os); + } + os.flush(); } - os.flush(); } /** 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 9ad31a1ba29..55fbfc44b36 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 @@ -34,6 +34,7 @@ import com.sap.sailing.xrr.schema.Person; import com.sap.sailing.xrr.schema.RaceResult; import com.sap.sailing.xrr.schema.RegattaResults; import com.sap.sailing.xrr.schema.Team; +import com.sap.sse.util.HttpUrlConnectionHelper; public class SwissTimingAdapterImpl implements SwissTimingAdapter { private final SwissTimingFactory swissTimingFactory; @@ -145,7 +146,7 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { public RegattaResults readRegattaEntryListFromXrrUrl(String xrrEntryListUrl) throws IOException, JAXBException { // try to read the entry list from manage2sail URL regattaEntryListUrl = new URL(xrrEntryListUrl); - URLConnection regattaEntryListConn = regattaEntryListUrl.openConnection(); + URLConnection regattaEntryListConn = HttpUrlConnectionHelper.redirectConnection(regattaEntryListUrl); RegattaResults regattaResults = ParserFactory.INSTANCE .createParser((InputStream) regattaEntryListConn.getContent(), xrrEntryListUrl).parse(); return regattaResults; 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 3552de93e27..fa265d5e7a1 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 @@ -2,6 +2,7 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.io.IOException; import java.net.URISyntaxException; +import java.net.URL; import java.net.UnknownHostException; import java.text.ParseException; import java.util.HashMap; @@ -44,12 +45,12 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory { } @Override - public SailMasterConnector getOrCreateSailMasterConnector(String host, int port, String raceId, String raceName, - String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { + public SailMasterConnector getOrCreateSailMasterConnector(String host, int port, String raceId, URL raceDataUrl, + String raceName, String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { if (Boolean.valueOf(System.getProperty("simulateLiveMode", "false"))) { return getOrCreateSailMasterLiveSimulatorConnector(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); } else { - Util.Triple key = new Util.Triple(host, port, raceId); + Util.Triple key = new Util.Triple<>(host, port, raceId); SailMasterConnector result = connectors.get(key); if (result == null || result.isStopped()) { if (result == null) { @@ -57,7 +58,14 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory { } else { logger.info("Creating a new connector for "+key+" because the old one was stopped"); } - result = new SailMasterConnectorImpl(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); + if (host == null) { + if (raceDataUrl == null) { + throw new IllegalArgumentException("raceDataUrl must be provided if no host/port is provided"); + } + result = new SailMasterConnectorForUrlDownload(raceId, raceDataUrl, raceName, raceDescription, boatClass, swissTimingRaceTracker); + } else { + result = new SailMasterConnectorForSocket(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); + } connectors.put(key, result); // TODO how do connectors get stopped, terminated and removed from the connectors map again? } else { @@ -72,13 +80,10 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory { String raceDescription, BoatClass boatClass, SwissTimingRaceTrackerImpl swissTimingRaceTracker) throws InterruptedException, ParseException { Util.Triple key = new Util.Triple<>(host, port, raceId); SailMasterConnector result = connectors.get(key); - if (result == null) { + if (result == null || result.isStopped()) { result = new SailMasterLiveSimulatorConnectorImpl(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); connectors.put(key, result); // TODO how do connectors get stopped, terminated and removed from the connectors map again? - } else if (result.isStopped()) { - result = new SailMasterLiveSimulatorConnectorImpl(host, port, raceId, raceName, raceDescription, boatClass, swissTimingRaceTracker); - connectors.put(key, result); } return result; } 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 6d61ce4a071..621d89a566b 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 @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.Serializable; import java.net.MalformedURLException; import java.net.URISyntaxException; +import java.net.URL; import java.net.UnknownHostException; import java.text.ParseException; import java.util.Collection; @@ -36,7 +37,6 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.regattalog.RegattaLogStore; @@ -49,6 +49,7 @@ import com.sap.sailing.domain.swisstimingadapter.RacingStatus; import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector; import com.sap.sailing.domain.swisstimingadapter.SailMasterListener; import com.sap.sailing.domain.swisstimingadapter.StartList; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; import com.sap.sailing.domain.swisstimingadapter.SwissTimingRaceTracker; import com.sap.sailing.domain.tracking.AbstractRaceTrackerImpl; @@ -60,13 +61,13 @@ import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.RaceTrackingHandler; +import com.sap.sailing.domain.tracking.RaceTrackingHandler.DefaultRaceTrackingHandler; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; 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.RaceTrackingHandler.DefaultRaceTrackingHandler; import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl; import com.sap.sailing.domain.tracking.impl.UpdateHandler; @@ -187,8 +188,32 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } // Ensure this is called last, otherwise the connector starts running without having a complete racetracker this.connector = factory.getOrCreateSailMasterConnector(connectivityParams.getHostname(), - connectivityParams.getPort(), connectivityParams.getRaceID(), connectivityParams.getRaceName(), - connectivityParams.getRaceDescription(), connectivityParams.getBoatClass(), this); + connectivityParams.getPort(), connectivityParams.getRaceID(), getRaceDataUrl(connectivityParams), + connectivityParams.getRaceName(), connectivityParams.getRaceDescription(), connectivityParams.getBoatClass(), this); + } + + /** + * If a valid hostname/port combination is set in the connectivity parameters, {@code null} is returned. Otherwise, + * a {@link URL} is constructed from the {@link SwissTimingTrackingConnectivityParameters#getManage2SailEventUrl()} + * by stripping all URL parameters and replacing the last path element which is expected to be a file name for the + * {@code .json} file by the {race-id}.log combination, expecting the race data to reside in the same folder as + * the JSON file describing the Manage2Sail event. + */ + private URL getRaceDataUrl(SwissTimingTrackingConnectivityParameters connectivityParams) throws MalformedURLException { + final URL result; + if (connectivityParams.getHostname() == null) { + final java.net.URL manage2SailEventURL = new URL(connectivityParams.getManage2SailEventUrl()); + final String path = manage2SailEventURL.getPath(); + final String logFile = path.substring(0, path.lastIndexOf('/')+1) + connectivityParams.getRaceID()+".log"; + if (manage2SailEventURL.getPort() != -1) { + result = new java.net.URL(manage2SailEventURL.getProtocol(), manage2SailEventURL.getHost(), manage2SailEventURL.getPort(), logFile); + } else { + result = new java.net.URL(manage2SailEventURL.getProtocol(), manage2SailEventURL.getHost(), logFile); + } + } else { + result = null; + } + return result; } @Override @@ -415,18 +440,25 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } @Override - public void storedDataProgress(String raceID, double progress) { + public void storedDataProgress(String raceID, double progress, TrackedRaceStatusEnum statusAfterLoadingComplete) { assert this.raceID.equals(raceID); if (isTrackedRaceStillReachable()) { final TrackedRaceStatusImpl newStatus; if (progress == 0.0) { newStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0); } else if (progress == 1.0) { - newStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, progress); + newStatus = new TrackedRaceStatusImpl(statusAfterLoadingComplete, progress); } else { newStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, progress); } trackedRace.onStatusChanged(this, newStatus); + if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { + try { + trackedRegattaRegistry.stopTracker(getRegatta(), this); + } catch (IOException | InterruptedException e) { + logger.log(Level.SEVERE, "Error trying to stop the tracker "+this, e); + } + } } } @@ -508,7 +540,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } }, useInternalMarkPassingAlgorithm, raceLogResolver, /* Not needed because the RaceTracker is not active on a replica */ Optional.empty(), - new TrackingConnectorInfoImpl(TrackingConnectorType.SwissTiming, /*no api connection to query the webUrl*/ null)); + new TrackingConnectorInfoImpl(SwissTimingAdapter.NAME, SwissTimingAdapter.DEFAULT_URL,/*no api connection to query the webUrl*/ null)); addUpdateHandlers(); notifyRaceCreationListeners(); logger.info("Created SwissTiming RaceDefinition and TrackedRace for "+race.getName()); diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java index ab9c45b1a98..dbfa4fb84f1 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java @@ -17,7 +17,6 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.TrackerManager; public interface SwissTimingReplayService { - List parseJSONObject(InputStream inputStream, String swissTimingUrlText) throws IOException, ParseException, org.json.simple.parser.ParseException; diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java index fec9e9f6755..55681df8b58 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java @@ -36,12 +36,12 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.regattalog.RegattaLogStore; import com.sap.sailing.domain.swisstimingadapter.DomainFactory; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingreplayadapter.CompetitorStatus; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayParser; @@ -417,7 +417,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter i /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), /* raceDefinitionSetToUpdate */ null, useInternalMarkPassingAlgorithm, raceLogResolver, /* Not needed because the RaceTracker is not active on a replica */ Optional.empty(), - new TrackingConnectorInfoImpl(TrackingConnectorType.SwissTiming, /*no api connection to query the webUrl*/ null)); + new TrackingConnectorInfoImpl(SwissTimingAdapter.NAME, SwissTimingAdapter.DEFAULT_URL,/*no api connection to query the webUrl*/ null)); trackedRace.onStatusChanged(this, new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0)); TimePoint bestStartTimeKnownSoFar = bestStartTimePerRaceID.get(currentRaceID); if (bestStartTimeKnownSoFar != null) { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java index 249e99480fd..9394f380580 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java @@ -24,6 +24,9 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; public interface TracTracAdapter { + static final String NAME = "TracTrac"; + static final String DEFAULT_URL = "https://www.tractrac.com/"; + DomainFactory getTracTracDomainFactory(); /** @@ -105,5 +108,5 @@ public interface TracTracAdapter { TracTracConfiguration createTracTracConfiguration(String creatorName, String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword); - + } 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 b499de8dcce..0c0b43e5683 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 @@ -56,7 +56,6 @@ import com.sap.sailing.domain.common.RankingMetrics; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -90,6 +89,7 @@ import com.sap.sailing.domain.tractracadapter.MetadataParser.BoatMetaData; import com.sap.sailing.domain.tractracadapter.MetadataParser.ControlPointMetaData; import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.ReceiverType; +import com.sap.sailing.domain.tractracadapter.TracTracAdapter; import com.sap.sailing.domain.tractracadapter.TracTracConfiguration; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker; @@ -633,11 +633,13 @@ public class DomainFactoryImpl implements DomainFactory { @Override public DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId, - String raceName, BoatClass boatClass, Map competitorsAndBoats, Course course, Iterable sidelines, WindStore windStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI tracTracUpdateURI, UUID tracTracEventUuid, - String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogAndTrackedRaceResolver raceLogResolver, - Consumer runBeforeExposingRace, IRace tractracRace, RaceTrackingHandler raceTrackingHandler) { + String raceName, BoatClass boatClass, Map competitorsAndBoats, Course course, + Iterable sidelines, WindStore windStore, long delayToLiveInMillis, + long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, + URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername, String tracTracPassword, + boolean ignoreTracTracMarkPassings, RaceLogAndTrackedRaceResolver raceLogResolver, + Consumer runBeforeExposingRace, IRace tractracRace, + RaceTrackingHandler raceTrackingHandler) { synchronized (raceCache) { RaceDefinition raceDefinition = raceCache.get(raceId); if (raceDefinition == null) { @@ -661,7 +663,10 @@ public class DomainFactoryImpl implements DomainFactory { trackedRegatta.getRegatta().addRace(raceDefinition); TrackingConnectorInfo trackingConnectorInfo = null; if (tractracRace != null) { - trackingConnectorInfo = new TrackingConnectorInfoImpl(TrackingConnectorType.TracTrac, tractracRace.getEvent().getWebURL()); + final URL webUrl = tractracRace.getEvent().getWebURL(); + final String webUrlString = webUrl == null ? null : webUrl.toString(); + trackingConnectorInfo = new TrackingConnectorInfoImpl(TracTracAdapter.NAME, + TracTracAdapter.DEFAULT_URL, webUrlString); } trackedRace = createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate, ignoreTracTracMarkPassings, 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 366cb2236bd..e7efca55991 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 @@ -1,6 +1,7 @@ package com.sap.sailing.domain.tractracadapter.impl; import java.net.URI; +import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -20,7 +21,6 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.tracking.TrackingConnectorType; import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; @@ -31,6 +31,7 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl; import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sailing.domain.tractracadapter.TracTracAdapter; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -269,14 +270,17 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue sidelines, Consumer runAfterCreatingTrackedRace) { - DynamicTrackedRace trackedRace = raceTrackingHandler.createTrackedRace(getTrackedRegatta(), race, sidelines, windStore, - delayToLiveInMillis, millisecondsOverWhichToAverageWind, + final URL webUrl = tractracRace.getEvent().getWebURL(); + final String webUrlString = webUrl == null ? null : webUrl.toString(); + DynamicTrackedRace trackedRace = raceTrackingHandler.createTrackedRace(getTrackedRegatta(), race, sidelines, + windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, /* time over which to average speed: */ race.getBoatClass() .getApproximateManeuverDurationInMilliseconds(), raceDefinitionSetToUpdate, useInternalMarkPassingAlgorithm, raceLogResolver, /* ThreadLocalTransporter not needed because the RaceTracker is not active on a replica */ Optional .empty(), - new TrackingConnectorInfoImpl(TrackingConnectorType.TracTrac, tractracRace.getEvent().getWebURL())); + new TrackingConnectorInfoImpl(TracTracAdapter.NAME, TracTracAdapter.DEFAULT_URL, + webUrlString)); if (runAfterCreatingTrackedRace != null) { runAfterCreatingTrackedRace.accept(trackedRace); } diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java b/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java index 500d7970fd1..396271b9fdf 100644 --- a/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java +++ b/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java @@ -18,6 +18,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; @@ -34,8 +35,11 @@ public class WindFinderPollerTest { factory.addReviewedSpotCollection(new ReviewedSpotsCollectionImpl("schilksee")); final List> wind = Collections.synchronizedList(new ArrayList<>()); final RaceDefinition mockedRaceDefinition = mock(RaceDefinition.class); + final Regatta mockedRegatta = mock(Regatta.class); final DynamicTrackedRegatta mockedTrackedRegatta = mock(DynamicTrackedRegatta.class); final DynamicTrackedRace mockedTrackedRace = mock(DynamicTrackedRace.class); + when(mockedTrackedRace.getTrackedRegatta()).thenReturn(mockedTrackedRegatta); + when(mockedTrackedRegatta.getRegatta()).thenReturn(mockedRegatta); when(mockedTrackedRegatta.getExistingTrackedRace(mockedRaceDefinition)).thenReturn(mockedTrackedRace); when(mockedTrackedRegatta.getTrackedRace(mockedRaceDefinition)).thenReturn(mockedTrackedRace); when(mockedTrackedRace.recordWind(any(Wind.class), any(WindSourceWithAdditionalID.class))).thenAnswer(new Answer() { diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java index d93ab6ddf45..63fce69766b 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java @@ -104,10 +104,15 @@ public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { private Iterable loadSpots() throws IOException, ParseException, MalformedURLException { final Iterable result; - JSONArray spotsAsJson = (JSONArray) new JSONParser().parse(new InputStreamReader( - (InputStream) new URL(Activator.BASE_URL_FOR_JSON_DOCUMENTS+"/"+getId()+SPOT_LIST_DOCUMENT_SUFFIX).getContent())); - result = parser.parseSpots(spotsAsJson, this); - return result; + final InputStreamReader in = new InputStreamReader( + (InputStream) new URL(Activator.BASE_URL_FOR_JSON_DOCUMENTS+"/"+getId()+SPOT_LIST_DOCUMENT_SUFFIX).getContent()); + try { + JSONArray spotsAsJson = (JSONArray) new JSONParser().parse(in); + result = parser.parseSpots(spotsAsJson, this); + return result; + } finally { + in.close(); + } } @Override diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java index 5cbf86a8081..6a26d719caf 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java @@ -7,8 +7,13 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import org.json.simple.JSONArray; import org.json.simple.parser.JSONParser; @@ -18,34 +23,83 @@ import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sailing.domain.windfinder.WindFinderSpotListener; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; +import com.sap.sse.util.ThreadPoolUtil; public class SpotImpl extends SpotDTO implements Spot { private static final long serialVersionUID = -7793334775486020313L; + + private static final Logger logger = Logger.getLogger(SpotImpl.class.getName()); private final WindFinderReportParser parser; private final ReviewedSpotsCollection collection; + private final ConcurrentMap listeners; + private TimePoint latestMeasurement; + private static final Duration POLL_EVERY = Duration.ONE_MINUTE; + private ScheduledFuture poller; public SpotImpl(String name, String id, String keyword, String englishCountryName, Position position, WindFinderReportParser parser, ReviewedSpotsCollection collection) { super(name, id, keyword, englishCountryName, position); this.parser = parser; this.collection = collection; + listeners = new ConcurrentHashMap<>(); } @Override - public Wind getLatestMeasurement() throws NumberFormatException, ParseException, org.json.simple.parser.ParseException, MalformedURLException, IOException { - final Iterable measurements = getAllMeasurements(); - final Wind result; - if (measurements != null && !Util.isEmpty(measurements)) { - final List measurementsSortedByTimepoint = new ArrayList<>(); - Util.addAll(measurements, measurementsSortedByTimepoint); - Collections.sort(measurementsSortedByTimepoint, (w1, w2)->w1.getTimePoint().compareTo(w2.getTimePoint())); - result = Util.last(measurementsSortedByTimepoint); - } else { - result = null; + public void addListener(WindFinderSpotListener listener) { + if (listeners.isEmpty()) { + ensurePollerRunning(); + } + listeners.put(listener, true); + } + + private synchronized void ensurePollerRunning() { + if (poller == null) { + logger.info("Starting to poll from WindFinder spot "+getId()+" as the first listener started listening."); + poller = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor().scheduleAtFixedRate(this::poll, + /* initialDelay */ 0, /* period */ POLL_EVERY.asMillis(), TimeUnit.MILLISECONDS); + } + } + + /** + * Fetches wind measurements from this spot and notifies new ones to the {@link #listeners}. + */ + private void poll() { + try { + final Iterable windFixes = getAllMeasurementsAfter(latestMeasurement); + logger.fine("Received "+Util.size(windFixes)+" wind fixes from WindFinder spot "+getId()+". Notifying "+listeners.size()+" listeners."); + for (final Wind wind : windFixes) { + if (latestMeasurement == null || wind.getTimePoint().after(latestMeasurement)) { + latestMeasurement = wind.getTimePoint(); + } + } + notifyListeners(windFixes); + } catch (IOException | ParseException | org.json.simple.parser.ParseException e) { + logger.log(Level.SEVERE, "Error trying to obtain WindFinder measurements from spot "+getId(), e); + } + } + + private void notifyListeners(Iterable windFixes) { + for (final WindFinderSpotListener listener : listeners.keySet()) { + listener.windDataReceived(windFixes, this); + } + } + + private synchronized void stopPoller() { + logger.info("Stopping polling from WindFinder spot "+getId()+" as last listener stopped listening."); + poller.cancel(/* mayInterruptIfRunning */ false); + poller = null; + } + + @Override + public void removeListener(WindFinderSpotListener listener) { + listeners.remove(listener); + if (listeners.isEmpty()) { + stopPoller(); } - return result; } @Override diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java index 35420c335c2..cae65c2f2e6 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java @@ -20,6 +20,7 @@ import org.json.simple.parser.ParseException; import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.WindFinderReviewedSpotsCollectionIdProvider; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; @@ -47,7 +48,7 @@ public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { /** * Can hold reviewed spots collections. Starts out valid but empty. Useful if we're running outside of an OSGi * environment such as a test set-up. The collections provided here will be added to the set returned by - * {@link #getReviewedSpotsCollections(boolean)}. + * {@link #getAllReviewedSpotsCollections(boolean)}. */ private Set reviewedSpotsCollections; private final ServiceTracker reviewedSpotsCollectionIdProvider; @@ -63,9 +64,8 @@ public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { reviewedSpotsCollectionsByIdCache = executor.schedule( ()->{ final ConcurrentMap result = new ConcurrentHashMap<>(); - reviewedSpotsCollectionIdProvider.getService(); try { - for (final ReviewedSpotsCollection c : loadSpotCollectionsFromProvider(/* lookupInCache */ false)) { + for (final ReviewedSpotsCollection c : loadAllSpotCollectionsFromProvider(/* lookupInCache */ false)) { result.put(c.getId(), c); } } catch (Exception e) { @@ -119,13 +119,26 @@ public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { } @Override - public Iterable getReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException { + public Iterable getReviewedSpotsCollections(RegattaIdentifier regattaIdentifier) throws InterruptedException, ExecutionException { + final Set result = new HashSet<>(); + result.addAll(reviewedSpotsCollections); + final WindFinderReviewedSpotsCollectionIdProvider provider; + if (reviewedSpotsCollectionIdProvider != null && (provider = reviewedSpotsCollectionIdProvider.getService()) != null) { + for (String id : provider.getWindFinderReviewedSpotsCollectionIdsByRegatta(regattaIdentifier)) { + result.add(getReviewedSpotsCollectionById(id, /* lookupInCache, re-use existing spot collection objects */ true)); + } + } + return result; + } + + @Override + public Iterable getAllReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException { final Set result = new HashSet<>(); if (cached) { result.addAll(reviewedSpotsCollectionsByIdCache.get().values()); } else { result.addAll(reviewedSpotsCollections); - Util.addAll(loadSpotCollectionsFromProvider(/* lookupInCache */ true), result); + Util.addAll(loadAllSpotCollectionsFromProvider(/* lookupInCache */ true), result); for (final ReviewedSpotsCollection c : result) { reviewedSpotsCollectionsByIdCache.get().putIfAbsent(c.getId(), c); } @@ -133,11 +146,11 @@ public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { return result; } - private Iterable loadSpotCollectionsFromProvider(boolean lookupInCache) throws InterruptedException, ExecutionException { + private Iterable loadAllSpotCollectionsFromProvider(boolean lookupInCache) throws InterruptedException, ExecutionException { final Set result = new HashSet<>(); final WindFinderReviewedSpotsCollectionIdProvider provider; if (reviewedSpotsCollectionIdProvider != null && (provider = reviewedSpotsCollectionIdProvider.getService()) != null) { - for (String id : provider.getWindFinderReviewedSpotsCollectionIds()) { + for (String id : provider.getAllWindFinderReviewedSpotsCollectionIds()) { result.add(getReviewedSpotsCollectionById(id, lookupInCache)); } } @@ -158,7 +171,7 @@ public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { @Override public Spot getSpotById(String spotId, boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { Spot result = null; - for (final ReviewedSpotsCollection coll : getReviewedSpotsCollections(/* cached */ cached)) { + for (final ReviewedSpotsCollection coll : getAllReviewedSpotsCollections(/* cached */ cached)) { for (final Spot spot : coll.getSpots(/* cached */ cached)) { if (Util.equalsWithNull(spot.getId(), spotId)) { result = spot; @@ -178,4 +191,5 @@ public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { reviewedSpotsCollections.remove(collection); reviewedSpotsCollectionsByIdCache.get().remove(collection.getId()); } + } diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java index 07cad18abb5..574047393e8 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java @@ -6,10 +6,6 @@ import java.util.HashSet; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import org.json.simple.parser.ParseException; @@ -21,10 +17,8 @@ import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindTracker; import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; import com.sap.sailing.domain.windfinder.Spot; -import com.sap.sse.common.Duration; +import com.sap.sailing.domain.windfinder.WindFinderSpotListener; import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.util.ThreadPoolUtil; /** * When an object of this tracker type is created and there are WindFinder stations available, it schedules regular @@ -42,74 +36,53 @@ import com.sap.sse.util.ThreadPoolUtil; * @author Axel Uhl (D043530) * */ -public class WindFinderWindTracker implements WindTracker, Runnable { - private static final Logger logger = Logger.getLogger(WindFinderWindTracker.class.getName()); - private static final Duration POLL_EVERY = Duration.ONE_MINUTE; - +public class WindFinderWindTracker implements WindTracker, WindFinderSpotListener { private final DynamicTrackedRace trackedRace; private final WindFinderTrackerFactoryImpl factory; - - private final ScheduledFuture poller; - private WeakHashMap timePointOfLastMeasurement; + private final Iterable spots; - /** - * The set of all {@link ReviewedSpotsCollection}s delivered by the {@link #factory} when this tracker - * was created. This is the basis for {@link #getUsefulSpots()} when evaluating, e.g., based on the - * {@link #trackedRace race's} location, which of the spots is actually useful for this tracker. - */ - private final Iterable allSpotCollections; - - public WindFinderWindTracker(DynamicTrackedRace trackedRace, WindFinderTrackerFactoryImpl factory) throws InterruptedException, ExecutionException { + public WindFinderWindTracker(DynamicTrackedRace trackedRace, WindFinderTrackerFactoryImpl factory) + throws InterruptedException, ExecutionException, MalformedURLException, IOException, ParseException { this.trackedRace = trackedRace; this.factory = factory; - this.allSpotCollections = factory.getReviewedSpotsCollections(/* cached */ false); // obtain fresh copy of all spots, updating cache this.timePointOfLastMeasurement = new WeakHashMap<>(); - this.poller = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor().scheduleAtFixedRate(this, - /* initialDelay */ 0, /* period */ POLL_EVERY.asMillis(), TimeUnit.MILLISECONDS); + spots = subscribeToUsefulSpots(); } - /** - * This method is executed in order to poll the WindFinder spots for new measurements - */ @Override - public void run() { - try { - final Iterable usefulSpots = getUsefulSpots(); - for (final Spot usefulSpot : usefulSpots) { - final Iterable windFixes = usefulSpot.getAllMeasurementsAfter(timePointOfLastMeasurement.get(usefulSpot)); - if (!Util.isEmpty(windFixes)) { - final WindSourceWithAdditionalID windSource = new WindSourceWithAdditionalID(WindSourceType.WINDFINDER, usefulSpot.getId()); - final WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); - for (final Wind wind : windFixes) { - final Wind existingFix = windTrack==null?null/*test scenario*/:windTrack.getFirstRawFixAtOrAfter(wind.getTimePoint()); - if (existingFix == null || !existingFix.getTimePoint().equals(wind.getTimePoint())) { - // avoid duplicates by adding the same fix again - trackedRace.recordWind(wind, windSource); - if (!timePointOfLastMeasurement.containsKey(usefulSpot) || wind.getTimePoint().after(timePointOfLastMeasurement.get(usefulSpot))) { - timePointOfLastMeasurement.put(usefulSpot, wind.getTimePoint()); - } - } - } + public void windDataReceived(Iterable windFixes, Spot spot) { + final WindSourceWithAdditionalID windSource = new WindSourceWithAdditionalID(WindSourceType.WINDFINDER, spot.getId()); + final WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); + for (final Wind wind : windFixes) { + final Wind existingFix = windTrack==null?null/*test scenario*/:windTrack.getFirstRawFixAtOrAfter(wind.getTimePoint()); + if (existingFix == null || !existingFix.getTimePoint().equals(wind.getTimePoint())) { + // avoid duplicates by adding the same fix again + trackedRace.recordWind(wind, windSource); + if (!timePointOfLastMeasurement.containsKey(spot) || wind.getTimePoint().after(timePointOfLastMeasurement.get(spot))) { + timePointOfLastMeasurement.put(spot, wind.getTimePoint()); } } - } catch (Exception e) { - logger.log(Level.WARNING, "Exception trying to obtain WindFinder data for race "+trackedRace.getRace().getName(), e); } } - - private Iterable getUsefulSpots() throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { - final Set spots = new HashSet<>(); - for (final ReviewedSpotsCollection collection : allSpotCollections) { - // TODO bug1301 judge each spot's usefulness given the location of trackedRace - Util.addAll(collection.getSpots(/* cached */ false), spots); + + public Iterable subscribeToUsefulSpots() + throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + final Set result = new HashSet<>(); + for (final ReviewedSpotsCollection spotCollection : factory.getReviewedSpotsCollections(trackedRace.getTrackedRegatta().getRegatta().getRegattaIdentifier())) { + for (final Spot spot : spotCollection.getSpots(/* cached */ true)) { + result.add(spot); + spot.addListener(this); + } } - return spots; + return result; } @Override public void stop() { - poller.cancel(/* mayInterruptIfRunning */ false); + for (final Spot spot : spots) { + spot.removeListener(this); + } factory.trackerStopped(trackedRace.getRace()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java index 46bd2013682..e063568b46d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java @@ -481,43 +481,49 @@ public class CandidateChooserImpl implements CandidateChooser { @Override public void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t) { - LockUtil.lockForWrite(perCompetitorLocks.get(c)); - try { - Candidate fixedCan = new CandidateForFixedMarkPassingImpl(zeroBasedIndexOfWaypoint + 1, t, 1, Util.get(race.getRace().getCourse().getWaypoints(), zeroBasedIndexOfWaypoint)); - NavigableSet fixed = fixedPassings.get(c); - if (fixed != null) { // can only set the mark passing if the competitor is still part of this race - if (!fixed.add(fixedCan)) { - Candidate old = fixed.ceiling(fixedCan); - fixed.remove(old); - removeCandidates(c, Collections.singleton(old)); - fixed.add(fixedCan); + final NamedReentrantReadWriteLock lock = perCompetitorLocks.get(c); + if (lock != null) { // otherwise this is a phony competitor and we aren't interested in fixed mark passings for it + LockUtil.lockForWrite(lock); + try { + Candidate fixedCan = new CandidateForFixedMarkPassingImpl(zeroBasedIndexOfWaypoint + 1, t, 1, Util.get(race.getRace().getCourse().getWaypoints(), zeroBasedIndexOfWaypoint)); + NavigableSet fixed = fixedPassings.get(c); + if (fixed != null) { // can only set the mark passing if the competitor is still part of this race + if (!fixed.add(fixedCan)) { + Candidate old = fixed.ceiling(fixedCan); + fixed.remove(old); + removeCandidates(c, Collections.singleton(old)); + fixed.add(fixedCan); + } + addCandidates(c, Collections.singleton(fixedCan)); + findShortestPath(c); } - addCandidates(c, Collections.singleton(fixedCan)); - findShortestPath(c); + } finally { + LockUtil.unlockAfterWrite(lock); } - } finally { - LockUtil.unlockAfterWrite(perCompetitorLocks.get(c)); } } @Override public void removeFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint) { - LockUtil.lockForWrite(perCompetitorLocks.get(c)); - try { - Candidate toRemove = null; - for (Candidate can : fixedPassings.get(c)) { - if (can.getOneBasedIndexOfWaypoint() - 1 == zeroBasedIndexOfWaypoint) { - toRemove = can; - break; + final NamedReentrantReadWriteLock lock = perCompetitorLocks.get(c); + if (lock != null) { // otherwise this is a phony competitor and we aren't interested in fixed mark passings for it + LockUtil.lockForWrite(lock); + try { + Candidate toRemove = null; + for (Candidate can : fixedPassings.get(c)) { + if (can.getOneBasedIndexOfWaypoint() - 1 == zeroBasedIndexOfWaypoint) { + toRemove = can; + break; + } } + if (toRemove != null) { + fixedPassings.get(c).remove(toRemove); + removeCandidates(c, Arrays.asList(toRemove)); + findShortestPath(c); + } + } finally { + LockUtil.unlockAfterWrite(lock); } - if (toRemove != null) { - fixedPassings.get(c).remove(toRemove); - removeCandidates(c, Arrays.asList(toRemove)); - findShortestPath(c); - } - } finally { - LockUtil.unlockAfterWrite(perCompetitorLocks.get(c)); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/TimeRangeWithNullStartMeaningEmpty.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/TimeRangeWithNullStartMeaningEmpty.java index 48d9af3d562..b22b4c84e88 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/TimeRangeWithNullStartMeaningEmpty.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/TimeRangeWithNullStartMeaningEmpty.java @@ -20,7 +20,10 @@ public class TimeRangeWithNullStartMeaningEmpty { private final boolean toIsInclusive; public TimeRangeWithNullStartMeaningEmpty(TimePoint from, TimePoint to, boolean toIsInclusive) { - timeRangeOrNull = from == null ? null : new TimeRangeImpl(from, to, toIsInclusive); + // the time range will be adjusted to an empty time range starting at "to" in case "from" is after + // "to"; this to avoid an IllegalArgumentException should "from" be after "to"; see bug 5362 + timeRangeOrNull = from == null ? null : new TimeRangeImpl( + to==null || from.before(to) ? from : to, to, toIsInclusive); this.from = from; this.to = to; this.toIsInclusive = toIsInclusive; @@ -54,6 +57,11 @@ public class TimeRangeWithNullStartMeaningEmpty { return getWithNewTo(toExclusive, /* toIsInclusive */ false); } + /** + * @return {@code null} if {@link #from} is {@code null}, representing a time range that is open at its start; + * should {@link #from} have been set to a time point later than {@link #to}, the time range returned is + * {@link TimeRange#isEmpty() empty} and positioned at {@link #to}. + */ public TimeRange getTimeRangeOrNull() { return timeRangeOrNull; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java index 5cdf0d3ce53..2200e4e384f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java @@ -16,8 +16,10 @@ import com.sap.sse.common.TimePoint; * */ public interface Spot extends SpotBase { - Wind getLatestMeasurement() throws NumberFormatException, ParseException, org.json.simple.parser.ParseException, MalformedURLException, IOException; - + void addListener(WindFinderSpotListener listener); + + void removeListener(WindFinderSpotListener listener); + Iterable getAllMeasurements() throws IOException, MalformedURLException, ParseException, org.json.simple.parser.ParseException; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderSpotListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderSpotListener.java new file mode 100755 index 00000000000..8b40be0d214 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderSpotListener.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.windfinder; + +import com.sap.sailing.domain.common.Wind; + +public interface WindFinderSpotListener { + void windDataReceived(Iterable windFixes, Spot spot); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java index e48885bb85c..c82bb73bc18 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java @@ -6,14 +6,15 @@ import java.util.concurrent.ExecutionException; import org.json.simple.parser.ParseException; +import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.tracking.WindTrackerFactory; public interface WindFinderTrackerFactory extends WindTrackerFactory { /** * Obtains the reviewed spot collections that this factory knows about. This set is constructed from the collections - * provided explicitly using {@link #addReviewedSpotCollection(ReviewedSpotCollection)} and - * {@link #removeReviewedSpotCollection(ReviewedSpotsCollection)}, and is extended by the collections obtained + * provided explicitly using {@code addReviewedSpotCollection(ReviewedSpotCollection)} and + * {@code removeReviewedSpotCollection(ReviewedSpotsCollection)}, and is extended by the collections obtained * through {@link #reviewedSpotsCollectionIdProvider} if a corresponding service can be resolved. * * @param cached @@ -23,7 +24,13 @@ public interface WindFinderTrackerFactory extends WindTrackerFactory { * * @return a non-live set of spots collections known by this factory at this point in time */ - Iterable getReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException; + Iterable getAllReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException; + + /** + * Like {@link #getAllReviewedSpotsCollections(boolean)}, but never using caching, always asking the underlying provider for + * a fresh copy of spot collections, qualified by the regatta to which they shall apply. + */ + Iterable getReviewedSpotsCollections(RegattaIdentifier regattaIdentifier) throws InterruptedException, ExecutionException; ReviewedSpotsCollection getReviewedSpotsCollectionById(String spotsCollectionId, boolean lookupInCache) throws InterruptedException, ExecutionException; diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.html b/java/com.sap.sailing.gwt.ui/AdminConsole.html index db0e90a4034..1eca6e0137e 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.html +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.html @@ -8,7 +8,11 @@ - + + + + diff --git a/java/com.sap.sailing.gwt.ui/AutoPlay.html b/java/com.sap.sailing.gwt.ui/AutoPlay.html index 622fe347c41..8e8cdbe0456 100644 --- a/java/com.sap.sailing.gwt.ui/AutoPlay.html +++ b/java/com.sap.sailing.gwt.ui/AutoPlay.html @@ -9,6 +9,11 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/DataMining.css b/java/com.sap.sailing.gwt.ui/DataMining.css index 1818050740f..be6f4c49aee 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.css +++ b/java/com.sap.sailing.gwt.ui/DataMining.css @@ -317,11 +317,11 @@ input.magnifier { .dataMiningListBox { min-width: 120px; + margin-right: 5px; } .queryDefinitionViewerContent { background-color: white; - padding: 5px; } .filterDimensionsTitleLabel { diff --git a/java/com.sap.sailing.gwt.ui/DataMining.html b/java/com.sap.sailing.gwt.ui/DataMining.html index cea6a7dfbbd..5b8c0fe86d4 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.html +++ b/java/com.sap.sailing.gwt.ui/DataMining.html @@ -7,6 +7,11 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/Home.html b/java/com.sap.sailing.gwt.ui/Home.html index 3014189d5ef..11e7626d524 100644 --- a/java/com.sap.sailing.gwt.ui/Home.html +++ b/java/com.sap.sailing.gwt.ui/Home.html @@ -3,7 +3,11 @@ - + + + + ${SAP}Sailing - Homepage @@ -14,8 +18,7 @@ href="/sailing-fontface-1.0.cache.css"> - - + @@ -24,7 +27,7 @@