diff --git a/configuration/sailing b/configuration/sailing index 2764e008d49..82d4c09b9a4 100755 --- a/configuration/sailing +++ b/configuration/sailing @@ -4,6 +4,18 @@ # # chkconfig: 2345 95 10 # description: Sailing contains all sailing services +# +### BEGIN INIT INFO +# Provides: sailing +# Required-Start: $local_fs $network $named $remote_fs +# Should-Start: +# Required-Stop: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: The sailing service +# Description: Start all sailing services required for this instance +### END INIT INFO # Source function library. . /etc/init.d/functions @@ -14,8 +26,8 @@ JAVA_START_INSTANCES="server" SERVERS_DIR=/home/sailing/servers GIT_REPOSITORY=/home/sailing/code APACHE_CONFIG_DIR=/etc/httpd/conf.d -rm /var/log/sailing.out -rm /var/log/sailing.err + +echo "Executing with $1" >>/var/log/sailing.out start_tmux() { su - sailing -c "/home/sailing/bin/tmuxConsole.sh unattended" @@ -63,13 +75,21 @@ start_servers() { stop_servers() { for conf in $JAVA_START_INSTANCES; do - echo "Stopping Java server $conf" + echo "Stopping Java server $conf" >> /var/log/sailing.out su - sailing -c "cd $SERVERS_DIR/$conf && ./stop" RETVAL=$? [ $RETVAL -eq 0 ] && success || failure + stop_httpd + sync_logs done } +sync_logs() { + echo "Executing logrotate followed by a sync to ensure that logs are synchronized" >> /var/log/sailing.out + logrotate -f /etc/logrotate.conf + sync +} + # launch_httpd(server_name, port, event_id) launch_httpd() { echo "Will try to launch httpd so this replica can work with an ELB easily." >>/var/log/sailing.out @@ -78,7 +98,7 @@ launch_httpd() { source $SERVERS_DIR/$FIRST_SERVER/env.sh # Append Apache macro invocation for /internal-server-status based on mod_status and INSTANCE_DNS to $APACHE_CONFIG_DIR/001-events.conf echo "Appending macro usage for $INSTANCE_DNS/internal-server-status URL for mod_status based Apache monitoring to $APACHE_CONFIG_DIR/001-events.conf" >>/var/log/sailing.out - echo "## SERVER STATUS" >>$APACHE_CONFIG_DIR/001-events.conf + echo "## SERVER STATUS" >>$APACHE_CONFIG_DIR/001-events.conf echo "Use Status $INSTANCE_DNS internal-server-status" >>$APACHE_CONFIG_DIR/001-events.conf echo "Launching httpd..." >>/var/log/sailing.out service httpd start @@ -87,13 +107,23 @@ launch_httpd() { fi } +stop_httpd() { + if [ -x /etc/init.d/httpd ]; then + service httpd stop + echo "Stopped httpd..." >>/var/log/sailing.out + fi +} + # See how we were called. case "$1" in start) + /usr/sbin/update-motd start_servers + touch /var/lock/subsys/sailing ;; stop) stop_servers + rm -f /var/lock/subsys/sailing ;; status) status java diff --git a/java/com.sap.sailing.dashboards.gwt/.project b/java/com.sap.sailing.dashboards.gwt/.project index 65425ba1398..520fc2bd59d 100644 --- a/java/com.sap.sailing.dashboards.gwt/.project +++ b/java/com.sap.sailing.dashboards.gwt/.project @@ -20,16 +20,6 @@ - - com.google.gdt.eclipse.core.webAppProjectValidator - - - - - com.google.gwt.eclipse.core.gwtProjectValidator - - - com.gwtplugins.gdt.eclipse.core.webAppProjectValidator @@ -44,7 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch index 28937ea9e3e..7310aa92bc6 100644 --- a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch +++ b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch @@ -3,7 +3,10 @@ - + + + + @@ -19,84 +22,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + diff --git a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch index 9a058165d0a..c5fc2f809c2 100644 --- a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch +++ b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch @@ -3,7 +3,7 @@ - + @@ -24,22 +24,24 @@ - - - - - - - - - - - + + + + + + + + + + + + + - + diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch deleted file mode 100644 index a9c1dda4f80..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT.launch deleted file mode 100644 index 6dc3f4ffd8d..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT.launch +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sailing.datamining.shared/.project b/java/com.sap.sailing.datamining.shared/.project index 07fda63ea6f..3e76c7f629f 100644 --- a/java/com.sap.sailing.datamining.shared/.project +++ b/java/com.sap.sailing.datamining.shared/.project @@ -21,12 +21,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java index 190b38265a6..cd565ac9a20 100644 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java @@ -15,7 +15,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext; +import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; import com.sap.sailing.datamining.test.util.ConcurrencyTestsUtil; import com.sap.sailing.datamining.test.util.NullProcessor; import com.sap.sailing.domain.leaderboard.Leaderboard; @@ -27,7 +27,7 @@ import com.sap.sse.datamining.components.Processor; public class TestLeaderboardGroupRetrievalProcessor { private RacingEventService service; - private Processor retriever; + private Processor retriever; private Collection retrievedGroups; @@ -37,14 +37,14 @@ public class TestLeaderboardGroupRetrievalProcessor { stub(service.getLeaderboardGroups()).toReturn(getGroupsInService()); retrievedGroups = new HashSet<>(); - Processor receiver = new NullProcessor(LeaderboardGroupWithContext.class, Void.class) { + Processor receiver = new NullProcessor(HasLeaderboardGroupContext.class, Void.class) { @Override - public void processElement(LeaderboardGroupWithContext element) { + public void processElement(HasLeaderboardGroupContext element) { retrievedGroups.add(element.getLeaderboardGroup()); } }; - Collection> resultReceivers = new ArrayList<>(); + Collection> resultReceivers = new ArrayList<>(); resultReceivers.add(receiver); retriever = new LeaderboardGroupRetrievalProcessor(ConcurrencyTestsUtil.getExecutor(), resultReceivers, 0); } diff --git a/java/com.sap.sailing.datamining/.project b/java/com.sap.sailing.datamining/.project index 39e695078e3..f40554ff5f4 100644 --- a/java/com.sap.sailing.datamining/.project +++ b/java/com.sap.sailing.datamining/.project @@ -21,18 +21,19 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator - + + org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties index 65084d8084a..c67e1bcfb75 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties @@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Abs. relative bearing to the next mark RatioBetweenDistanceSailedWithAndWithoutManeuver=Ratio between distance sailed with and without maneuver RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Ratio between distance sailed toward middle angle projection with and without maneuver DurationLostByManeuver=Duration lost by maneuver -DurationLostByManeuverTowardMiddleAngleProjection=duration lost by maneuver toward middle angle projection +DurationLostByManeuverTowardMiddleAngleProjection=Duration lost by maneuver toward middle angle projection JibingCount=Jibing count TackingCount=Tacking count AbsTwaAtMaxTurningRate=Abs. TWA at max. turning rate @@ -198,4 +198,5 @@ PercentageOfMainCurveProgressUntilLowestSpeed=Percentage of main curve progress PercentageOfMainCurveProgressUntilHighestSpeed=Percentage of main curve progress until highest speed PercentageOfMainCurveProgressUntilMaxTurningRate=Percentage of main curve progress until max. turning rate speed AbsTwaAtManeuverMiddle=Abs. TWA at maneuver middle -GpsSamplingRate=GPS sampling rate \ No newline at end of file +GpsSamplingRate=GPS sampling rate +RaceDuration=Race duration \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index 2ef0edf2761..82c4ad2377d 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -201,4 +201,5 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Prozentualer Fortschritt der Haup PercentageOfMainCurveProgressUntilMaxTurningRate=Prozentualer Fortschritt der Hauptkurve bis zum Erreichen von max. Drehrate ManeuverTypeForCompleteManeuverCurve=Manövertyp für komplette Manöverkurve AbsTwaAtManeuverMiddle=Abs. TWA während Manövermitte -GpsSamplingRate=GPS-Samplingrate \ No newline at end of file +GpsSamplingRate=GPS-Samplingrate +RaceDuration=Renndauer \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java index df083c132e0..0cedaa8921f 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java @@ -5,12 +5,13 @@ import java.util.Collection; import com.sap.sailing.datamining.data.HasBravoFixContext; import com.sap.sailing.datamining.data.HasBravoFixTrackContext; +import com.sap.sailing.datamining.data.HasCompleteManeuverCurveWithEstimationDataContext; import com.sap.sailing.datamining.data.HasFoilingSegmentContext; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasLeaderboardContext; +import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; import com.sap.sailing.datamining.data.HasManeuverContext; import com.sap.sailing.datamining.data.HasManeuverSpeedDetailsContext; -import com.sap.sailing.datamining.data.HasCompleteManeuverCurveWithEstimationDataContext; import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext; import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext; @@ -22,13 +23,13 @@ import com.sap.sailing.datamining.data.HasWindTrackContext; import com.sap.sailing.datamining.impl.components.BravoFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.BravoFixTrackRetrievalProcessor; import com.sap.sailing.datamining.impl.components.CompetitorOfRaceInLeaderboardRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.CompleteManeuverCurveWithEstimationDataRetrievalProcessor; import com.sap.sailing.datamining.impl.components.FoilingSegmentRetrievalProcessor; import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardRetrievalProcessor; import com.sap.sailing.datamining.impl.components.ManeuverRetrievalProcessor; import com.sap.sailing.datamining.impl.components.ManeuverSpeedDetailsRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.CompleteManeuverCurveWithEstimationDataRetrievalProcessor; import com.sap.sailing.datamining.impl.components.MarkPassingRetrievalProcessor; import com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProcessor; import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor; @@ -36,7 +37,6 @@ import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor; import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor; import com.sap.sailing.datamining.impl.components.WindFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.WindTrackRetrievalProcessor; -import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext; import com.sap.sailing.datamining.shared.FoilingSegmentsDataMiningSettings; import com.sap.sailing.datamining.shared.ManeuverSettings; import com.sap.sailing.datamining.shared.ManeuverSettingsImpl; @@ -55,7 +55,7 @@ public class SailingDataRetrievalChainDefinitions { final DataRetrieverChainDefinition leaderboardRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( RacingEventService.class, HasLeaderboardContext.class, "LeaderboardSailingDomainRetrieverChain"); - leaderboardRetrieverChainDefinition.startWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardGroupWithContext.class, "LeaderboardGroup"); + leaderboardRetrieverChainDefinition.startWith(LeaderboardGroupRetrievalProcessor.class, HasLeaderboardGroupContext.class, "LeaderboardGroup"); leaderboardRetrieverChainDefinition.endWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardRetrievalProcessor.class, HasLeaderboardContext.class, "Leaderboard"); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasBravoFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasBravoFixContext.java index f733d6f9bb4..a5d2afce531 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasBravoFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasBravoFixContext.java @@ -16,7 +16,7 @@ public interface HasBravoFixContext { @Connector(ordinal=1) BravoFix getBravoFix(); - @Connector(messageKey="Speed") + @Connector(messageKey="") SpeedWithBearing getSpeed(); @Connector(messageKey="Wind") diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java index d063b5eef97..06705c310dd 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java @@ -2,7 +2,6 @@ package com.sap.sailing.datamining.data; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Dimension; @@ -12,8 +11,6 @@ public interface HasLeaderboardContext { Leaderboard getLeaderboard(); - PolarDataService getPolarDataService(); - @Dimension(messageKey="Leaderboard", ordinal=1) String getName(); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java index 3fbcd143d7e..5ed03ec38b1 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java @@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Tack; import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Speed; import com.sap.sse.datamining.annotations.Connector; @@ -14,74 +15,74 @@ import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; public interface HasRaceOfCompetitorContext { @Connector(scanForStatistics=false) - public HasTrackedRaceContext getTrackedRaceContext(); + HasTrackedRaceContext getTrackedRaceContext(); @Connector(messageKey="Competitor") - @Statistic(messageKey="") - public Competitor getCompetitor(); + @Statistic(messageKey="Competitor") + Competitor getCompetitor(); @Dimension(messageKey="TackAtStart", ordinal=12) - public Tack getTackAtStart() throws NoWindException; + Tack getTackAtStart() throws NoWindException; @Dimension(messageKey="DistanceToStarboardSideAtStartOfCompetitor", ordinal=13) - public ClusterDTO getPercentageClusterForDistanceToStarboardSideAtStart(); + ClusterDTO getPercentageClusterForDistanceToStarboardSideAtStart(); @Dimension(messageKey="RelativeScoreInPercent", ordinal=14) - public ClusterDTO getPercentageClusterForRelativeScore(); + ClusterDTO getPercentageClusterForRelativeScore(); @Statistic(messageKey="DistanceAtStart", resultDecimals=2, ordinal=0) - public Distance getDistanceToStartLineAtStart(); + Distance getDistanceToStartLineAtStart(); @Statistic(messageKey="DistanceToStarboardSideAtStartOfCompetitor", resultDecimals=2, ordinal=1) - public Double getNormalizedDistanceToStarboardSideAtStartOfCompetitor(); + Double getNormalizedDistanceToStarboardSideAtStartOfCompetitor(); @Statistic(messageKey="DistanceToStarboardSideAtStartOfCompetitorVsRankAtFirstMark", resultDecimals=2, ordinal=1) - public Pair getNormalizedDistanceToStarboardSideAtStartOfCompetitorVsRankAtFirstMark(); + Pair getNormalizedDistanceToStarboardSideAtStartOfCompetitorVsRankAtFirstMark(); @Statistic(messageKey="WindwardDistanceToAdvantageousEndOfLineAtStartOfRace", resultDecimals=2, ordinal=2) - public Distance getWindwardDistanceToAdvantageousLineEndAtStartofRace(); + Distance getWindwardDistanceToAdvantageousLineEndAtStartofRace(); @Statistic(messageKey="WindwardDistanceToAdvantageousEndOfLineAtStartOfCompetitor", resultDecimals=2, ordinal=2) - public Distance getWindwardDistanceToAdvantageousLineEndAtStartofCompetitor(); + Distance getWindwardDistanceToAdvantageousLineEndAtStartofCompetitor(); @Connector(messageKey="SpeedWhenStarting", ordinal=3) - public Speed getSpeedWhenStarting(); + Speed getSpeedWhenStarting(); @Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=4) - public Speed getSpeedTenSecondsBeforeStart(); + Speed getSpeedTenSecondsBeforeStart(); @Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=5) - public Speed getSpeedTenSecondsAfterStartOfRace(); + Speed getSpeedTenSecondsAfterStartOfRace(); @Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=6) - public Double getRankThirtySecondsAfterStartOfRace(); + Double getRankThirtySecondsAfterStartOfRace(); @Statistic(messageKey="RankAfterHalfOfTheFirstLeg", resultDecimals=2, ordinal=7) - public Double getRankAfterHalfOfTheFirstLeg(); + Double getRankAfterHalfOfTheFirstLeg(); @Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=8) - public Double getRankAtFirstMark(); + Double getRankAtFirstMark(); @Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=9) - public Double getRankGainsOrLossesBetweenFirstMarkAndFinish(); + Double getRankGainsOrLossesBetweenFirstMarkAndFinish(); @Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=10) - public int getNumberOfManeuvers(); + int getNumberOfManeuvers(); @Statistic(messageKey="NumberOfTacks", resultDecimals=0, ordinal=11) - public int getNumberOfTacks(); + int getNumberOfTacks(); @Statistic(messageKey="NumberOfJibes", resultDecimals=0, ordinal=12) - public int getNumberOfJibes(); + int getNumberOfJibes(); @Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=0, ordinal=13) - public int getNumberOfPenaltyCircles(); + int getNumberOfPenaltyCircles(); @Statistic(messageKey="DistanceTraveled", resultDecimals=1) - public Distance getDistanceTraveled(); + Distance getDistanceTraveled(); @Statistic(messageKey="LineLengthAtStart", resultDecimals=1) - public Distance getLineLengthAtStart(); + Distance getLineLengthAtStart(); @Statistic(messageKey="AbsoluteWindwardDistanceToStarboardSideAtStartOfCompetitor", resultDecimals=2) Distance getAbsoluteWindwardDistanceToStarboardSideAtStartOfCompetitor(); @@ -94,4 +95,8 @@ public interface HasRaceOfCompetitorContext { @Statistic(messageKey="RelativeDistanceToAdvantageousEndOfLineAtStartOfRace", resultDecimals=2) Double getRelativeDistanceToAdvantageousEndOfLineAtStartOfRace(); + + @Statistic(messageKey="RaceDuration") + Duration getDuration(); + } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java index 138f4b7788f..b5a77441a5a 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java @@ -8,6 +8,7 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Duration; import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Dimension; import com.sap.sse.datamining.annotations.Statistic; @@ -46,10 +47,13 @@ public interface HasTrackedRaceContext { @Dimension(messageKey="IsTracked", ordinal=9) public Boolean isTracked(); - @Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0) + @Statistic(messageKey="RaceDuration", ordinal=0) + public Duration getDuration(); + + @Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=1) public int getNumberOfCompetitorFixes(); - @Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1) + @Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=2) public int getNumberOfMarkFixes(); // Convenience methods for race dependent calculation to avoid code duplication diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasWindFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasWindFixContext.java index 39168a6cc3c..ba4cbe091e1 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasWindFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasWindFixContext.java @@ -11,7 +11,7 @@ public interface HasWindFixContext extends HasWind { @Connector(scanForStatistics=false) public HasTrackedRaceContext getTrackedRaceContext(); - @Statistic(messageKey="", ordinal=0) + @Statistic(messageKey="WindFix", ordinal=0) public Wind getWind(); @Statistic(messageKey="windFrom", resultDecimals=1, ordinal=1) diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixRetrievalProcessor.java index 3370ffc238c..73dfb14ceb3 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixRetrievalProcessor.java @@ -24,21 +24,24 @@ public class BravoFixRetrievalProcessor extends AbstractRetrievalProcessor retrieveData(HasTrackedLegOfCompetitorContext element) { Collection bravoFixesWithContext = new ArrayList<>(); - BravoFixTrack bravoFixTrack = element.getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getSensorTrack(element.getCompetitor(), BravoFixTrack.TRACK_NAME); - if (bravoFixTrack != null) { - bravoFixTrack.lockForRead(); - try { - TrackedLegOfCompetitor trackedLegOfCompetitor = element.getTrackedLegOfCompetitor(); - if (trackedLegOfCompetitor.getStartTime() != null && trackedLegOfCompetitor.getFinishTime() != null) { + TrackedLegOfCompetitor trackedLegOfCompetitor = element.getTrackedLegOfCompetitor(); + if (trackedLegOfCompetitor.getStartTime() != null && trackedLegOfCompetitor.getFinishTime() != null) { + BravoFixTrack bravoFixTrack = element.getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getSensorTrack(element.getCompetitor(), BravoFixTrack.TRACK_NAME); + if (bravoFixTrack != null) { + bravoFixTrack.lockForRead(); + try { for (BravoFix bravoFix : bravoFixTrack.getFixes(trackedLegOfCompetitor.getStartTime(), true, trackedLegOfCompetitor.getFinishTime(), true)) { + if (isAborted()) { + break; + } BravoFixWithContext gpsFixWithContext = new BravoFixWithContext( new TrackedLegOfCompetitorWithSpecificTimePointWithContext( element.getTrackedLegContext(), element.getTrackedLegOfCompetitor(), bravoFix.getTimePoint()), bravoFix); bravoFixesWithContext.add(gpsFixWithContext); } + } finally { + bravoFixTrack.unlockAfterRead(); } - } finally { - bravoFixTrack.unlockAfterRead(); } } return bravoFixesWithContext; diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixTrackRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixTrackRetrievalProcessor.java index 0905ce48aef..87e4b42e4c1 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixTrackRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/BravoFixTrackRetrievalProcessor.java @@ -1,7 +1,7 @@ package com.sap.sailing.datamining.impl.components; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.concurrent.ExecutorService; import com.sap.sailing.datamining.data.HasBravoFixTrackContext; @@ -28,13 +28,9 @@ public class BravoFixTrackRetrievalProcessor extends AbstractRetrievalProcessor< @Override protected Iterable retrieveData(HasRaceOfCompetitorContext element) { - Collection bravoTracksWithContext = new ArrayList<>(); final TrackedRace trackedRace = element.getTrackedRaceContext().getTrackedRace(); final BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(element.getCompetitor(), BravoFixTrack.TRACK_NAME); - if (bravoFixTrack != null) { - bravoTracksWithContext.add(new BravoFixTrackWithContext(element, bravoFixTrack)); - } - return bravoTracksWithContext; + return bravoFixTrack == null ? Collections.emptySet() : Collections.singleton(new BravoFixTrackWithContext(element, bravoFixTrack)); } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompetitorOfRaceInLeaderboardRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompetitorOfRaceInLeaderboardRetrievalProcessor.java index 5d81bac8881..15ed1eeca2f 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompetitorOfRaceInLeaderboardRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompetitorOfRaceInLeaderboardRetrievalProcessor.java @@ -24,9 +24,15 @@ public class CompetitorOfRaceInLeaderboardRetrievalProcessor extends protected Iterable retrieveData(HasLeaderboardContext element) { Collection raceResultsOfCompetitor = new ArrayList<>(); for (RaceColumn raceColumn : element.getLeaderboard().getRaceColumns()) { + if (isAborted()) { + break; + } for (Competitor competitor : element.getLeaderboard().getCompetitors()) { + if (isAborted()) { + break; + } HasRaceResultOfCompetitorContext raceResultOfCompetitorContext = new RaceResultOfCompetitorWithContext(element, raceColumn, competitor, - element.getPolarDataService()); + element.getLeaderboardGroupContext().getPolarDataService()); raceResultsOfCompetitor.add(raceResultOfCompetitorContext); } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java index eff334d61f5..01659c4ac04 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java @@ -48,15 +48,27 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends Competitor competitor = element.getCompetitor(); ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( new ManeuverDetectorImpl(trackedRace, competitor), - element.getTrackedRaceContext().getLeaderboardContext().getPolarDataService()); + element.getTrackedRaceContext().getLeaderboardContext().getLeaderboardGroupContext().getPolarDataService()); + Iterable maneuvers = trackedRace.getManeuvers(competitor, false); + if (isAborted()) { + return result; + } + Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); + if (isAborted()) { + return result; + } + Iterable maneuversWithEstimationData = maneuverDetector .getCompleteManeuverCurvesWithEstimationData(maneuverCurves); - CompleteManeuverCurveWithEstimationData previousManeuver = null; CompleteManeuverCurveWithEstimationData currentManeuver = null; for (CompleteManeuverCurveWithEstimationData nextManeuver : maneuversWithEstimationData) { + if (isAborted()) { + break; + } + if (currentManeuver != null) { CompleteManeuverCurveWithEstimationDataWithContext maneuverWithContext = new CompleteManeuverCurveWithEstimationDataWithContext( element, currentManeuver, settings, previousManeuver, nextManeuver); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java index fbfa62cd126..bbc21f56c33 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java @@ -50,6 +50,9 @@ public class FoilingSegmentRetrievalProcessor extends AbstractRetrievalProcessor bravoFixTrack.lockForRead(); try { for (final BravoFix bravoFix : bravoFixTrack.getFixes(startOfRace, /* fromInclusive */ true, end, /* toInclusive */ false)) { + if (isAborted()) { + break; + } final boolean currentFixIsFoiling = (bravoFix.isFoiling(settings.getMinimumRideHeight()) && (settings.getMinimumSpeedForFoiling() == null || settings.getMinimumSpeedForFoiling().compareTo( diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java index 19161c9d6f3..f9ee2372d1d 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java @@ -30,6 +30,9 @@ public class GPSFixRetrievalProcessor extends AbstractRetrievalProcessor { +public class LeaderboardGroupRetrievalProcessor extends AbstractRetrievalProcessor { public LeaderboardGroupRetrievalProcessor(ExecutorService executor, - Collection> resultReceivers, int retrievalLevel) { - super(RacingEventService.class, LeaderboardGroupWithContext.class, executor, resultReceivers, retrievalLevel); + Collection> resultReceivers, int retrievalLevel) { + super(RacingEventService.class, HasLeaderboardGroupContext.class, executor, resultReceivers, retrievalLevel); } @Override - protected Iterable retrieveData(RacingEventService element) { - final PolarDataService polarDataService = element.getPolarDataService(); - return element.getLeaderboardGroups() - .values() - .stream() - .map(lg -> new LeaderboardGroupWithContext(lg, polarDataService)) - .collect(Collectors.toSet()); + protected Iterable retrieveData(RacingEventService element) { + Set data = new HashSet<>(); + PolarDataService polarDataService = element.getPolarDataService(); + for (LeaderboardGroup leaderboardGroup : element.getLeaderboardGroups().values()) { + if (isAborted()) { + break; + } + data.add(new LeaderboardGroupWithContext(leaderboardGroup, polarDataService)); + } + return data; } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardRetrievalProcessor.java index 09062609cfb..6feee91098b 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardRetrievalProcessor.java @@ -5,24 +5,27 @@ import java.util.Collection; import java.util.concurrent.ExecutorService; import com.sap.sailing.datamining.data.HasLeaderboardContext; -import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext; +import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; import com.sap.sailing.datamining.impl.data.LeaderboardWithContext; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor; -public class LeaderboardRetrievalProcessor extends AbstractRetrievalProcessor { +public class LeaderboardRetrievalProcessor extends AbstractRetrievalProcessor { public LeaderboardRetrievalProcessor(ExecutorService executor, Collection> resultReceivers, int retrievalLevel) { - super(LeaderboardGroupWithContext.class, HasLeaderboardContext.class, executor, resultReceivers, retrievalLevel); + super(HasLeaderboardGroupContext.class, HasLeaderboardContext.class, executor, resultReceivers, retrievalLevel); } @Override - protected Iterable retrieveData(LeaderboardGroupWithContext element) { + protected Iterable retrieveData(HasLeaderboardGroupContext element) { Collection leaderboardsWithContext = new ArrayList<>(); for (Leaderboard leaderboard : element.getLeaderboardGroup().getLeaderboards()) { - leaderboardsWithContext.add(new LeaderboardWithContext(leaderboard, element, element.getPolarDataService())); + if (isAborted()) { + break; + } + leaderboardsWithContext.add(new LeaderboardWithContext(leaderboard, element)); } return leaderboardsWithContext; } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java index 097e191fba4..c2a683b3423 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java @@ -9,6 +9,7 @@ import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.impl.data.ManeuverWithContext; import com.sap.sailing.datamining.impl.data.TrackedLegOfCompetitorWithSpecificTimePointWithContext; import com.sap.sailing.datamining.shared.ManeuverSettings; +import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; @@ -36,6 +37,9 @@ public class ManeuverRetrievalProcessor TimePoint finishTime = element.getTrackedLegOfCompetitor().getFinishTime(); if (finishTime != null) { Iterable maneuvers = null; + if (!isLastLeg(element)) { + finishTime = finishTime.minus(1); + } try { maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false); } catch (NoWindException e) { @@ -44,6 +48,10 @@ public class ManeuverRetrievalProcessor Maneuver previousManeuver = null; Maneuver currentManeuver = null; for (Maneuver nextManeuver : maneuvers) { + if (isAborted()) { + break; + } + if (currentManeuver != null) { ManeuverWithContext maneuverWithContext = new ManeuverWithContext(new TrackedLegOfCompetitorWithSpecificTimePointWithContext( element.getTrackedLegContext(), element.getTrackedLegOfCompetitor(), currentManeuver.getTimePoint()), currentManeuver, @@ -66,6 +74,12 @@ public class ManeuverRetrievalProcessor return maneuversWithContext; } + private boolean isLastLeg(HasTrackedLegOfCompetitorContext element) { + Waypoint finishWaypoint = element.getTrackedLegContext().getTrackedRaceContext().getRace().getCourse().getLastWaypoint(); + Waypoint toWaypoint = element.getTrackedLegOfCompetitor().getLeg().getTo(); + return finishWaypoint.equals(toWaypoint); + } + private boolean isManeuverCompliantWithSettings(Maneuver previousManeuver, ManeuverWithContext currentManeuverWithContext, Maneuver nextManeuver) { boolean mainCurveAnalysis = settings.isMainCurveAnalysis(); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverSpeedDetailsRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverSpeedDetailsRetrievalProcessor.java index 04dc15d7f7d..b7cb426f9d2 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverSpeedDetailsRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverSpeedDetailsRetrievalProcessor.java @@ -87,6 +87,10 @@ public class ManeuverSpeedDetailsRetrievalProcessor double directionChangeForAnalysisSignum = Math.signum(directionChangeInDegreesForAnalysis); for (SpeedWithBearingStep bearingStep : maneuverBearingSteps) { + if (isAborted()) { + break; + } + currentDirectionChangeSumInDegrees += bearingStep.getCourseChangeInDegrees(); if (previousRoundedTWA != -1 && Math.signum(currentDirectionChangeSumInDegrees) != directionChangeForAnalysisSignum @@ -123,6 +127,9 @@ public class ManeuverSpeedDetailsRetrievalProcessor for (int step = 1, fillingTWA = twaIterationFunction .apply(previousRoundedTWA); fillingTWA != roundedTWA; fillingTWA = twaIterationFunction .apply(fillingTWA), ++step) { + if (isAborted()) { + break; + } if (speedPerTWA[fillingTWA] == 0) { speedPerTWA[fillingTWA] = previousSpeed + diffWithPreviousSpeed * step / diffWithPreviousTWA; diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java index 62ce35b65b2..a3da6e980ad 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java @@ -29,6 +29,9 @@ public class MarkPassingRetrievalProcessor extends AbstractRetrievalProcessor maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false); for (Maneuver maneuver : maneuvers) { + if (isAborted()) { + break; + } if (maneuver.isMarkPassing()) { maneuversWithContext.add(new MarkPassingWithContext(new TrackedLegOfCompetitorWithSpecificTimePointWithContext( element.getTrackedLegContext(), element.getTrackedLegOfCompetitor(), maneuver.getTimePoint()), maneuver)); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RaceOfCompetitorRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RaceOfCompetitorRetrievalProcessor.java index d88833c883e..822b73ee064 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RaceOfCompetitorRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RaceOfCompetitorRetrievalProcessor.java @@ -22,6 +22,9 @@ public class RaceOfCompetitorRetrievalProcessor extends AbstractRetrievalProcess protected Iterable retrieveData(HasTrackedRaceContext element) { Collection raceOfCompetitorsWithContext = new ArrayList<>(); for (Competitor competitor : element.getTrackedRace().getRace().getCompetitors()) { + if (isAborted()) { + break; + } HasRaceOfCompetitorContext raceOfCompetitorWithContext = new RaceOfCompetitorWithContext(element, competitor); raceOfCompetitorsWithContext.add(raceOfCompetitorWithContext); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java index 9e8ea01aaff..82e87d42dca 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java @@ -22,6 +22,9 @@ public class TrackedLegOfCompetitorRetrievalProcessor extends AbstractRetrievalP protected Iterable retrieveData(HasTrackedLegContext element) { Collection trackedLegOfCompetitorsWithContext = new ArrayList<>(); for (Competitor competitor : element.getTrackedRaceContext().getTrackedRace().getRace().getCompetitors()) { + if (isAborted()) { + break; + } HasTrackedLegOfCompetitorContext trackedLegOfCompetitorWithContext = new TrackedLegOfCompetitorWithContext(element, element.getTrackedLeg().getTrackedLeg(competitor)); trackedLegOfCompetitorsWithContext.add(trackedLegOfCompetitorWithContext); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java index b20f1f62fb5..50cb80cc85f 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java @@ -23,6 +23,9 @@ public class TrackedLegRetrievalProcessor extends AbstractRetrievalProcessor trackedLegsWithContext = new ArrayList<>(); int legNumber = 1; for (TrackedLeg trackedLeg : element.getTrackedRace().getTrackedLegs()) { + if (isAborted()) { + break; + } HasTrackedLegContext trackedLegWithContext = new TrackedLegWithContext(element, trackedLeg, legNumber); trackedLegsWithContext.add(trackedLegWithContext); legNumber++; diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java index 12c2b9fbbe1..71fa5e11b76 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java @@ -25,7 +25,13 @@ public class TrackedRaceRetrievalProcessor extends AbstractRetrievalProcessor retrieveData(HasLeaderboardContext element) { Collection trackedRacesWithContext = new ArrayList<>(); for (RaceColumn raceColumn : element.getLeaderboard().getRaceColumns()) { + if (isAborted()) { + break; + } for (Fleet fleet : raceColumn.getFleets()) { + if (isAborted()) { + break; + } TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); if (trackedRace != null) { Regatta regatta = trackedRace.getTrackedRegatta().getRegatta(); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/WindFixRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/WindFixRetrievalProcessor.java index ae337f4561d..fe023f79b92 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/WindFixRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/WindFixRetrievalProcessor.java @@ -33,6 +33,9 @@ public class WindFixRetrievalProcessor extends AbstractRetrievalProcessor windTracksWithContext = new ArrayList<>(); final TrackedRace trackedRace = element.getTrackedRace(); for (final WindSource windSource : trackedRace.getWindSources()) { + if (isAborted()) { + break; + } if (!trackedRace.getWindSourcesToExclude().contains(windSource)) { final WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); windTracksWithContext.add(new WindTrackWithContext(element, windTrack, windSource)); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java index 18cd52840b7..b1226dd579b 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java @@ -64,6 +64,9 @@ public abstract class AbstractParallelAverageAggregationProcessor minAggregation = minAggregationProcessor.getResult(); Map maxAggregation = maxAggregationProcessor.getResult(); for (Entry sumAggregationEntry : sumAggregation.entrySet()) { + if (isAborted()) { + break; + } GroupKey key = sumAggregationEntry.getKey(); result.put(key, new AverageWithStatsImpl<>( /* average */ divide(sumAggregationEntry.getValue(), elementAmountPerKey.get(key).longValue()), diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java index a2a9f27cdc6..debe1785c53 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java @@ -19,8 +19,6 @@ public abstract class AbstractParallelSumAggregationProcessor extends super(executor, resultReceivers, "Sum"); results = new HashMap<>(); } - - protected abstract T add(T t1, T t2); @Override protected void handleElement(GroupedDataEntry element) { @@ -31,6 +29,8 @@ public abstract class AbstractParallelSumAggregationProcessor extends results.put(key, add(results.get(key), element.getDataEntry())); } } + + protected abstract T add(T t1, T t2); @Override protected Map getResult() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingAverageDegreesAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingAverageDegreesAggregationProcessor.java index 9ed6ac09b6b..cabff7897b4 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingAverageDegreesAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingAverageDegreesAggregationProcessor.java @@ -47,6 +47,9 @@ public class ParallelBearingAverageDegreesAggregationProcessor protected Map aggregateResult() { Map result = new HashMap<>(); for (Entry clusterEntry : results.entrySet()) { + if (isAborted()) { + break; + } GroupKey key = clusterEntry.getKey(); result.put(key, clusterEntry.getValue().getAverage().getDegrees()); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceMedianAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceMedianAggregationProcessor.java index b208c2e5e85..0147a22c63c 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceMedianAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceMedianAggregationProcessor.java @@ -50,6 +50,9 @@ public class ParallelDistanceMedianAggregationProcessor protected Map aggregateResult() { Map result = new HashMap<>(); for (Entry> groupedValuesEntry : groupedValues.entrySet()) { + if (isAborted()) { + break; + } result.put(groupedValuesEntry.getKey(), getMedianOf(groupedValuesEntry.getValue())); } return result; diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java index a0c6dc0d2b8..9ee53091241 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java @@ -4,17 +4,14 @@ import com.sap.sailing.datamining.data.HasLeaderboardContext; import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.polars.PolarDataService; public class LeaderboardWithContext implements HasLeaderboardContext { private final Leaderboard leaderboard; private final HasLeaderboardGroupContext leaderboardGroupContext; - private final PolarDataService polarDataService; - public LeaderboardWithContext(Leaderboard leaderboard, HasLeaderboardGroupContext leaderboardGroupContext, PolarDataService polarDataService) { + public LeaderboardWithContext(Leaderboard leaderboard, HasLeaderboardGroupContext leaderboardGroupContext) { this.leaderboard = leaderboard; this.leaderboardGroupContext = leaderboardGroupContext; - this.polarDataService = polarDataService; } public HasLeaderboardGroupContext getLeaderboardGroupContext() { @@ -31,11 +28,6 @@ public class LeaderboardWithContext implements HasLeaderboardContext { return getLeaderboard().getBoatClass(); } - @Override - public PolarDataService getPolarDataService() { - return polarDataService; - } - @Override public String getName() { return getLeaderboard().getName(); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java index e3258e846e6..a49907755db 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java @@ -1,5 +1,9 @@ package com.sap.sailing.datamining.impl.data; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import com.sap.sailing.datamining.Activator; @@ -25,9 +29,12 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; import com.sap.sse.common.Speed; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; @@ -240,50 +247,56 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { @Override public int getNumberOfManeuvers() { - return getNumberOfTacks() + getNumberOfJibes(); + Set maneuverTypes = new HashSet<>(); + maneuverTypes.add(ManeuverType.TACK); + maneuverTypes.add(ManeuverType.JIBE); + return getNumberOf(maneuverTypes); } @Override public int getNumberOfTacks() { - return getNumberOf(ManeuverType.TACK); + return getNumberOf(Collections.singleton(ManeuverType.TACK)); } @Override public int getNumberOfJibes() { - return getNumberOf(ManeuverType.JIBE); + return getNumberOf(Collections.singleton(ManeuverType.JIBE)); } @Override public int getNumberOfPenaltyCircles() { - return getNumberOf(ManeuverType.PENALTY_CIRCLE); + return getNumberOf(Collections.singleton(ManeuverType.PENALTY_CIRCLE)); } - private int getNumberOf(ManeuverType maneuverType) { + private int getNumberOf(Set maneuverTypes) { int number = 0; TrackedRace trackedRace = getTrackedRace(); if (trackedRace != null) { + TimePoint from = null; + TimePoint to = null; + Competitor competitor = getCompetitor(); Course course = trackedRace.getRace().getCourse(); - Waypoint startWaypoint = course.getFirstWaypoint(); - MarkPassing startPassing = trackedRace.getMarkPassing(getCompetitor(), startWaypoint); - TimePoint start = startPassing != null ? startPassing.getTimePoint() : trackedRace.getStartOfRace(); + List waypoints = Util.createList(course.getWaypoints()); - Waypoint finishWaypoint = course.getLastWaypoint(); - MarkPassing finishPassing = trackedRace.getMarkPassing(getCompetitor(), finishWaypoint); - TimePoint end; - if (finishPassing != null) { - end = finishPassing.getTimePoint(); - } else { - end = trackedRace.getEndOfRace(); - if (end == null) { - TimePoint endOfTracking = trackedRace.getEndOfTracking(); - TimePoint now = MillisecondsTimePoint.now(); - end = endOfTracking != null && endOfTracking.before(now) ? endOfTracking : now; + int fromIndex = 0; + while (fromIndex < waypoints.size()) { + MarkPassing markPassing = trackedRace.getMarkPassing(competitor, waypoints.get(fromIndex)); + TimePoint passingTime = markPassing != null ? markPassing.getTimePoint() : null; + if (passingTime != null) { + if (from == null) { + from = passingTime; + } else { + to = passingTime; + } } + fromIndex++; } - for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), start, end, false)) { - if (maneuver.getType() == maneuverType) { - number++; + if (from != null && to != null) { + for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), from, to, false)) { + if (maneuverTypes.contains(maneuver.getType())) { + number++; + } } } } @@ -346,4 +359,18 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { return distance / length; } + @Override + public Duration getDuration() { + Duration duration = null; + TrackedRace race = getTrackedRace(); + Course course = race.getRace().getCourse(); + MarkPassing startPassing = race.getMarkPassing(competitor, course.getFirstWaypoint()); + MarkPassing finishPassing = race.getMarkPassing(competitor, course.getLastWaypoint()); + if (startPassing != null && finishPassing != null) { + long durationMillis = finishPassing.getTimePoint().asMillis() - startPassing.getTimePoint().asMillis(); + duration = new MillisecondsDurationImpl(durationMillis); + } + return duration; + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java index 89de0e2091e..445e93d1d39 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java @@ -17,8 +17,10 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.LineDetails; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; public class TrackedRaceWithContext implements HasTrackedRaceContext { @@ -113,6 +115,26 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext { public Boolean isTracked() { return getTrackedRace().hasStarted(MillisecondsTimePoint.now()); } + + @Override + public Duration getDuration() { + Duration duration = null; + TrackedRace race = getTrackedRace(); + TimePoint start = race.getStartOfRace(); + if (start == null) { + start = race.getStartOfTracking(); + } + if (start != null) { + TimePoint end = race.getEndOfRace(); + if (end == null) { + end = race.getEndOfTracking(); + } + if (end != null) { + duration = new MillisecondsDurationImpl(end.asMillis() - start.asMillis()); + } + } + return duration; + } @Override public int getNumberOfCompetitorFixes() { diff --git a/java/com.sap.sailing.domain.common/.project b/java/com.sap.sailing.domain.common/.project index 8c2ade13ac6..7779c8b14a0 100755 --- a/java/com.sap.sailing.domain.common/.project +++ b/java/com.sap.sailing.domain.common/.project @@ -21,12 +21,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java index 0af8c31849b..3b8053ea0b5 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java @@ -18,6 +18,7 @@ public class BasicRaceDTO extends NamedDTO { public Date startOfRace; public Date endOfRace; + public Date raceFinishedTime; public TrackedRaceDTO trackedRace; public BasicRaceDTO() {} // for GWT serialization only diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java index fd2bb4ca0e7..7f904a4694c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java @@ -55,10 +55,6 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { private final AbstractLogEventAuthor author; - public static ReadonlyRaceState create(RaceLogResolver raceLogResolver, RaceLog raceLog) { - return create(raceLogResolver, raceLog, /* forRaceLogIdentifier */ null, Collections.emptyMap()); - } - /** * Creates a {@link RaceState} with the initial racing procedure type set to a fallback value and an empty configuration. */ diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java index 67d7140fbf9..4904be72af4 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 @@ -44,6 +44,7 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util; +import com.sap.sse.util.WeakIdentityHashMap; /** * Implementation of {@link ReadonlyRaceState}. Use the static factory methods to instantiate your race state. @@ -55,8 +56,37 @@ import com.sap.sse.common.Util; * See {@link ReadonlyRaceStateImpl#update()} for the core of the algorithm. */ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedListener { - - public static ReadonlyRaceState create(RaceLogResolver raceLogResolver, RaceLog raceLog) { + private static final WeakIdentityHashMap> raceStateCache = + new WeakIdentityHashMap<>(); + + /** + * Obtains a race state for the combination of {@code raceLog} and {@code raceLogResolver}. The result is + * cache using weak references to the race log and the race log resolver for quick retrieval upon equal + * requests, saving memory, saving listeners and thereby avoiding more synchronization issues. + * + * See also bug4704. + */ + public static ReadonlyRaceState getOrCreate(RaceLogResolver raceLogResolver, RaceLog raceLog) { + WeakIdentityHashMap raceStatesForRaceLog; + ReadonlyRaceState result = null; + synchronized (raceStateCache) { + raceStatesForRaceLog = raceStateCache.get(raceLog); + if (raceStatesForRaceLog == null) { + raceStatesForRaceLog = new WeakIdentityHashMap<>(); + raceStateCache.put(raceLog, raceStatesForRaceLog); + } else { + result = raceStatesForRaceLog.get(raceLogResolver); + } + if (result == null) { + result = createInternal(raceLogResolver, raceLog); + raceStatesForRaceLog.put(raceLogResolver, result); + } + } + return result; + } + + + private static final ReadonlyRaceState createInternal(RaceLogResolver raceLogResolver, RaceLog raceLog) { return create(raceLogResolver, raceLog, /* forRaceLogIdentifier */null, Collections. emptyMap()); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java index d90bffeb0ca..26a4a5053ff 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/FieldNames.java @@ -2,7 +2,8 @@ package com.sap.sailing.domain.swisstimingadapter.persistence.impl; public enum FieldNames { // SwissTiming configuration parameters: - ST_CONFIG_NAME, ST_CONFIG_HOSTNAME, ST_CONFIG_PORT, ST_CONFIG_JSON_URL, ST_ARCHIVE_JSON_URL, + ST_CONFIG_NAME, ST_CONFIG_HOSTNAME, ST_CONFIG_PORT, ST_CONFIG_JSON_URL, ST_CONFIG_UPDATE_URL, + ST_CONFIG_UPDATE_USERNAME, ST_CONFIG_UPDATE_PASSWORD, ST_ARCHIVE_JSON_URL, // last message count field: LAST_MESSAGE_COUNT, diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java index 7d6b0d260f8..71d8b5d3a54 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingAdapterPersistenceImpl.java @@ -37,8 +37,8 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers DBCollection stConfigs = database.getCollection(CollectionNames.SWISSTIMING_CONFIGURATIONS.name()); for (DBObject o : stConfigs.find()) { SwissTimingConfiguration stConfig = loadSwissTimingConfiguration(o); - // the old swisstiming config was not based on a json URL -> ignore such configs - if(stConfig.getJsonURL() != null) { + // the old SwissTiming configuration was not based on a JSON URL -> ignore such configurations + if (stConfig.getJsonURL() != null) { result.add(stConfig); } } @@ -57,8 +57,10 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers String jsonURL = (String) object.get(FieldNames.ST_CONFIG_JSON_URL.name()); String hostname = (String) object.get(FieldNames.ST_CONFIG_HOSTNAME.name()); Integer port = (Integer) object.get(FieldNames.ST_CONFIG_PORT.name()); - - return swissTimingFactory.createSwissTimingConfiguration(name, jsonURL, hostname, port); + String updateURL = (String) object.get(FieldNames.ST_CONFIG_UPDATE_URL.name()); + String updateUsername = (String) object.get(FieldNames.ST_CONFIG_UPDATE_USERNAME.name()); + String updatePassword = (String) object.get(FieldNames.ST_CONFIG_UPDATE_PASSWORD.name()); + return swissTimingFactory.createSwissTimingConfiguration(name, jsonURL, hostname, port, updateURL, updateUsername, updatePassword); } @Override @@ -96,6 +98,9 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers result.put(FieldNames.ST_CONFIG_JSON_URL.name(), swissTimingConfiguration.getJsonURL()); result.put(FieldNames.ST_CONFIG_HOSTNAME.name(), swissTimingConfiguration.getHostname()); result.put(FieldNames.ST_CONFIG_PORT.name(), swissTimingConfiguration.getPort()); + result.put(FieldNames.ST_CONFIG_UPDATE_URL.name(), swissTimingConfiguration.getUpdateURL()); + result.put(FieldNames.ST_CONFIG_UPDATE_USERNAME.name(), swissTimingConfiguration.getUpdateUsername()); + result.put(FieldNames.ST_CONFIG_UPDATE_PASSWORD.name(), swissTimingConfiguration.getUpdatePassword()); stConfigCollection.insert(result); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java index a034257ea2b..5c8dca04bae 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/src/com/sap/sailing/domain/swisstimingadapter/persistence/impl/SwissTimingConnectivityParamsHandler.java @@ -54,6 +54,9 @@ public class SwissTimingConnectivityParamsHandler extends AbstractRaceTrackingCo private static final String PORT = "port"; private static final String BOAT_CLASS_NAME = "boatClassName"; private static final String HOSTNAME = "hostname"; + private static final String UPDATE_URL = "updateURL"; + private static final String UPDATE_USERNAME = "updateUsername"; + private static final String UPDATE_PASSWORD = "updatePassword"; private final RaceLogStore raceLogStore; private final RegattaLogStore regattaLogStore; private final DomainFactory domainFactory; @@ -78,6 +81,9 @@ public class SwissTimingConnectivityParamsHandler extends AbstractRaceTrackingCo result.put(START_LIST, createStartListDBObject(stParams)); result.put(DELAY_TO_LIVE_IN_MILLIS, stParams.getDelayToLiveInMillis()); result.put(USE_INTERNAL_MARK_PASSING_ALGORITHM, stParams.isUseInternalMarkPassingAlgorithm()); + result.put(UPDATE_URL, stParams.getUpdateURL()); + result.put(UPDATE_USERNAME, stParams.getUpdateUsername()); + result.put(UPDATE_PASSWORD, stParams.getUpdatePassword()); addWindTrackingParameters(stParams, result); return result; } @@ -175,7 +181,10 @@ public class SwissTimingConnectivityParamsHandler extends AbstractRaceTrackingCo map.get(START_LIST) == null ? null : createStartListFromDBObject((String) map.get(RACE_ID), (BasicDBList) map.get(START_LIST)), ((Number) map.get(DELAY_TO_LIVE_IN_MILLIS)).longValue(), swissTimingFactory, domainFactory, raceLogStore, regattaLogStore, - (boolean) map.get(USE_INTERNAL_MARK_PASSING_ALGORITHM), isTrackWind(map), isCorrectWindDirectionByMagneticDeclination(map)); + (boolean) map.get(USE_INTERNAL_MARK_PASSING_ALGORITHM), isTrackWind(map), isCorrectWindDirectionByMagneticDeclination(map), + (String) map.get(UPDATE_URL), + (String) map.get(UPDATE_USERNAME), + (String) map.get(UPDATE_PASSWORD)); } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java index b8036a8adc0..c9a4979fa1f 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.swisstimingadapter; import java.io.Serializable; +import java.net.URISyntaxException; import java.util.List; import java.util.Map; @@ -18,6 +19,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.regattalog.RegattaLogStore; import com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; @@ -70,10 +72,17 @@ public interface DomainFactory { RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, - RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination); + RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword); ControlPoint getOrCreateControlPoint(String description, Iterable deviceIds, MarkType markType); RaceDefinition createRaceDefinition(Regatta regatta, String swissTimingRaceID, Map competitorsAndBoats, List courseDefinition, String raceName, String raceIdForRaceDefinition); + + /** + * Adds update handlers that forward events about a race, such as start time changes, course changes + * or postponents, to an update URL using specific REST requests. + */ + void addUpdateHandlers(String updateURL, String username, String password, Serializable eventId, + RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) throws URISyntaxException; } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java index 10f4669c7f5..cdeaf3a7368 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.swisstimingadapter; +import java.net.URISyntaxException; import java.util.Collection; import java.util.List; @@ -16,9 +17,9 @@ public interface SailMasterListener { void receivedClockAtMark(String raceID, List> markIndicesTimePointsAndBoatIDs); - void receivedStartList(String raceID, StartList startList); + void receivedStartList(String raceID, StartList startList) throws URISyntaxException; - void receivedCourseConfiguration(String raceID, Course course); + void receivedCourseConfiguration(String raceID, Course course) throws URISyntaxException; void receivedAvailableRaces(Iterable races); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java index e275b3573c8..bcb05fe84c4 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java @@ -26,12 +26,10 @@ public interface SwissTimingAdapter { * is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used * to add the races to. Otherwise, a default regatta as described above will be created and used. * @param useInternalMarkPassingAlgorithm use our own instead of the SwissTiming-provided mark passing / split times - * @param trackWind TODO - * @param correctWindDirectionByMagneticDeclination TODO */ RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, - StartList startList, RaceLogStore logStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) + StartList startList, RaceLogStore logStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword) throws InterruptedException, UnknownHostException, IOException, ParseException, Exception; StartList readStartListForRace(String raceId, RegattaResults regattaResults); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java index 293eff3c8be..09c500c6581 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingConfiguration.java @@ -14,4 +14,10 @@ public interface SwissTimingConfiguration { String getHostname(); Integer getPort(); + + String getUpdateURL(); + + String getUpdateUsername(); + + String getUpdatePassword(); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java index 8badef20f87..33ea647eaa3 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java @@ -49,20 +49,14 @@ public interface SwissTimingFactory { SailMasterTransceiver createSailMasterTransceiver(); - SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port); + SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword); - SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, - BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, - RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, - boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams) throws InterruptedException, UnknownHostException, - IOException, ParseException; + SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, + DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams) + throws InterruptedException, UnknownHostException, IOException, ParseException; - RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, - BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, - WindStore windStore, boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceLogStore raceLogStore, - RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams) + RaceTracker createRaceTracker(Regatta regatta, WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, + RaceLogResolver raceLogResolver, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams) throws UnknownHostException, InterruptedException, IOException, ParseException; Race createRace(String raceId, String raceName, String description, BoatClass boatClass); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 5c493877887..758369f0b5b 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; @@ -50,9 +52,14 @@ import com.sap.sailing.domain.swisstimingadapter.RaceType; import com.sap.sailing.domain.swisstimingadapter.RaceType.OlympicRaceCode; import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler; +import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler; +import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler; +import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler; import com.sap.sse.common.Named; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -402,9 +409,29 @@ public class DomainFactoryImpl implements DomainFactory { String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, - boolean trackWind, boolean correctWindDirectionByMagneticDeclination) { + boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, + String updateUsername, String updatePassword) { return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, swissTimingFactory, domainFactory, raceLogStore, - regattaLogStore, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination); + regattaLogStore, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, + updateURL, updateUsername, updatePassword); + } + + @Override + public void addUpdateHandlers(String updateURL, String username, String password, Serializable eventId, + RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) throws URISyntaxException { + final URI updateURI = new URI(updateURL); + CourseDesignUpdateHandler courseDesignHandler = new CourseDesignUpdateHandler( + updateURI, username, password, eventId, raceDefinition.getId()); + StartTimeUpdateHandler startTimeHandler = new StartTimeUpdateHandler( + updateURI, username, password, eventId, + raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta()); + RaceAbortedHandler raceAbortedHandler = new RaceAbortedHandler( + updateURI, username, password, eventId, + raceDefinition.getId()); + final FinishTimeUpdateHandler finishTimeUpdateHandler = new FinishTimeUpdateHandler(updateURI, username, password, eventId, + raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta()); + baseDomainFactory.addUpdateHandlers(trackedRace, courseDesignHandler, startTimeHandler, raceAbortedHandler, + finishTimeUpdateHandler); } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java index 9d386a29c6b..a7978a8d466 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java @@ -69,12 +69,13 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { public RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, - boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) throws Exception { + boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, + String updateURL, String updateUsername, String updatePassword) throws Exception { return trackerManager.addRace(regattaToAddTo, swissTimingDomainFactory.createTrackingConnectivityParameters(hostname, port, raceID, raceName, raceDescription, boatClass, startList, DEFAULT_SWISSTIMING_LIVE_DELAY_IN_MILLISECONDS, swissTimingFactory, swissTimingDomainFactory, raceLogStore, regattaLogStore, - useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination), + useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, updateURL, updateUsername, updatePassword), timeoutInMilliseconds); } @@ -118,9 +119,9 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { Person person = persons.get(crewMember.getPersonID()); crew.add(new CrewMemberImpl( person.getGivenName() + " " + person.getFamilyName(), - person.getNOC().name(), crewMember.getPosition().name())); + (person.getNOC()==null)?null:(person.getNOC().name()), crewMember.getPosition().name())); } - String nationality = team.getNOC().name(); + String nationality = team.getNOC()==null?null:team.getNOC().name(); CompetitorWithID competitor = new CompetitorWithID(team.getTeamID(), boat.getSailNumber(), nationality, team.getTeamName(), crew); competitors.add(competitor); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java index c16ca7a5b71..8c1087e2d11 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingConfigurationImpl.java @@ -7,13 +7,19 @@ public class SwissTimingConfigurationImpl implements SwissTimingConfiguration { private final String jsonURL; private final String hostname; private final Integer port; + private final String updateURL; + private final String updateUsername; + private final String updatePassword; - public SwissTimingConfigurationImpl(String name, String jsonURL, String hostname, Integer port) { + public SwissTimingConfigurationImpl(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) { super(); this.name = name; this.jsonURL = jsonURL; this.hostname = hostname; this.port = port; + this.updateURL = updateURL; + this.updateUsername = updateUsername; + this.updatePassword = updatePassword; } @Override @@ -36,4 +42,15 @@ public class SwissTimingConfigurationImpl implements SwissTimingConfiguration { return port; } + public String getUpdateURL() { + return updateURL; + } + + public String getUpdateUsername() { + return updateUsername; + } + + public String getUpdatePassword() { + return updatePassword; + } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java index 9d7b4b82cb1..70236e46404 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java @@ -17,7 +17,6 @@ import com.sap.sailing.domain.swisstimingadapter.Race; import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector; import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; import com.sap.sailing.domain.swisstimingadapter.SailMasterTransceiver; -import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration; import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; @@ -83,32 +82,27 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory { } @Override - public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port) { - return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port); + public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, + Integer port, String updateURL, String updateUsername, String updatePassword) { + return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port, updateURL, updateUsername, updatePassword); } @Override - public SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, - BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, - RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, - boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams) + public SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, + RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams) throws InterruptedException, UnknownHostException, IOException, ParseException { - return new SwissTimingRaceTrackerImpl(raceID, raceName, raceDescription, boatClass, hostname, port, startList, - raceLogStore, regattaLogStore, windStore, domainFactory, this, - trackedRegattaRegistry, raceLogResolver, delayToLiveInMillis, useInternalMarkPassingAlgorithm, connectivityParams); + return new SwissTimingRaceTrackerImpl(raceLogStore, regattaLogStore, windStore, domainFactory, this, + trackedRegattaRegistry, raceLogResolver, connectivityParams); } @Override - public RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, - BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, - WindStore windStore, boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory, + public RaceTracker createRaceTracker(Regatta regatta, WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams) throws UnknownHostException, InterruptedException, IOException, ParseException { - return new SwissTimingRaceTrackerImpl(regatta, raceID, raceName, raceDescription, boatClass, hostname, port, - startList, windStore, domainFactory, this, trackedRegattaRegistry, raceLogStore, regattaLogStore, - raceLogResolver, delayToLiveInMillis, useInternalMarkPassingAlgorithm, connectivityParams); + return new SwissTimingRaceTrackerImpl(regatta, windStore, domainFactory, this, trackedRegattaRegistry, + raceLogStore, regattaLogStore, raceLogResolver, connectivityParams); } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index e963f7c5033..8b960a35e78 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.io.IOException; import java.io.Serializable; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.UnknownHostException; import java.text.ParseException; import java.util.Collection; @@ -63,6 +64,7 @@ import com.sap.sailing.domain.tracking.TrackingDataLoader; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; +import com.sap.sailing.domain.tracking.impl.UpdateHandler; import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -110,23 +112,30 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl private final TMDMessageQueue tmdMessageQueue; private final RaceLogResolver raceLogResolver; + + /** + * If set to a non-{@code null}, non-{@link String#isEmpty() empty} value, updates about start time changes, course + * changes, etc. will be sent as REST requests to this URL, using the {@link #updateUsername} / + * {@link #updatePassword} as credentials. + */ + private final String updateURL; + + private final String updateUsername; + + private final String updatePassword; - protected SwissTimingRaceTrackerImpl(String raceID, String raceName, String raceDescription, BoatClass boatClass, - String hostname, int port, StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + protected SwissTimingRaceTrackerImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, DomainFactory domainFactory, SwissTimingFactory factory, - TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, long delayToLiveInMillis, - boolean useInternalMarkPassingAlgorithm, SwissTimingTrackingConnectivityParameters connectivityParams) throws InterruptedException, - UnknownHostException, IOException, ParseException { - this(/* regatta */ null, raceID, raceName, raceDescription, boatClass, hostname, port, startList, windStore, - domainFactory, factory, trackedRegattaRegistry, raceLogStore, regattaLogStore, raceLogResolver, - delayToLiveInMillis, useInternalMarkPassingAlgorithm, connectivityParams); + TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, + SwissTimingTrackingConnectivityParameters connectivityParams) + throws InterruptedException, UnknownHostException, IOException, ParseException { + this(/* regatta */ null, windStore, domainFactory, factory, trackedRegattaRegistry, raceLogStore, regattaLogStore, raceLogResolver, connectivityParams); } - protected SwissTimingRaceTrackerImpl(Regatta regatta, String raceID, String raceName, String raceDescription, - BoatClass boatClass, String hostname, int port, StartList startList, WindStore windStore, - DomainFactory domainFactory, SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry, - RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, RaceLogResolver raceLogResolver, - long delayToLiveInMillis, boolean useInternalMarkPassingAlgorithm, SwissTimingTrackingConnectivityParameters connectivityParams) + protected SwissTimingRaceTrackerImpl(Regatta regatta, WindStore windStore, DomainFactory domainFactory, + SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogStore raceLogStore, + RegattaLogStore regattaLogStore, RaceLogResolver raceLogResolver, + SwissTimingTrackingConnectivityParameters connectivityParams) throws InterruptedException, UnknownHostException, IOException, ParseException { super(connectivityParams); this.raceLogResolver = raceLogResolver; @@ -134,29 +143,33 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl final Regatta effectiveRegatta; // Try to find a pre-associated event based on the Race ID if (regatta == null) { - effectiveRegatta = trackedRegattaRegistry.getRememberedRegattaForRace(raceID); + effectiveRegatta = trackedRegattaRegistry.getRememberedRegattaForRace(connectivityParams.getRaceID()); } else { effectiveRegatta = regatta; } // if regatta is still null, no previous assignment of any of the races in this TracTrac event to a Regatta was // found; in this case, create a default regatta based on the TracTrac event data this.regatta = effectiveRegatta == null ? domainFactory.getOrCreateDefaultRegatta(raceLogStore, regattaLogStore, - raceID, boatClass, trackedRegattaRegistry) : effectiveRegatta; - this.connector = factory.getOrCreateSailMasterConnector(hostname, port, raceID, raceName, raceDescription, boatClass); + connectivityParams.getRaceID(), connectivityParams.getBoatClass(), trackedRegattaRegistry) : effectiveRegatta; + this.connector = factory.getOrCreateSailMasterConnector(connectivityParams.getHostname(), connectivityParams.getPort(), + connectivityParams.getRaceID(), connectivityParams.getRaceName(), connectivityParams.getRaceDescription(), connectivityParams.getBoatClass()); this.domainFactory = domainFactory; - this.raceID = raceID; - this.raceName = raceName; - this.startList = startList; - this.startListFromManage2Sail = startList != null; - this.raceDescription = raceDescription; - this.boatClass = boatClass; + this.raceID = connectivityParams.getRaceID(); + this.raceName = connectivityParams.getRaceName(); + this.startList = connectivityParams.getStartList(); + this.startListFromManage2Sail = connectivityParams.getStartList() != null; + this.raceDescription = connectivityParams.getRaceDescription(); + this.boatClass = connectivityParams.getBoatClass(); this.windStore = windStore; - this.id = createID(raceID, hostname, port); + this.id = createID(connectivityParams.getRaceID(), connectivityParams.getHostname(), connectivityParams.getPort()); connector.addSailMasterListener(this); trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(this.regatta); - this.delayToLiveInMillis = delayToLiveInMillis; + this.delayToLiveInMillis = connectivityParams.getDelayToLiveInMillis(); this.competitorsByBoatId = new HashMap(); - this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm; + this.useInternalMarkPassingAlgorithm = connectivityParams.isUseInternalMarkPassingAlgorithm(); + this.updateURL = connectivityParams.getUpdateURL(); + this.updateUsername = connectivityParams.getUpdateUsername(); + this.updatePassword = connectivityParams.getUpdatePassword(); } @Override @@ -406,7 +419,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } @Override - public void receivedStartList(String raceID, StartList startList) { + public void receivedStartList(String raceID, StartList startList) throws URISyntaxException { // ignore STL messages if the startlist has been already provided by Manage2Sail if (!startListFromManage2Sail && this.raceID.equals(raceID)) { StartList oldStartList = this.startList; @@ -435,7 +448,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } } - private void createRaceDefinition(Course course) { + private void createRaceDefinition(Course course) throws URISyntaxException { assert this.raceID.equals(raceID); assert startList != null; assert course != null; @@ -465,9 +478,20 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } }, useInternalMarkPassingAlgorithm, raceLogResolver, /* Not needed because the RaceTracker is not active on a replica */ Optional.empty()); + addUpdateHandlers(); notifyRaceCreationListeners(); logger.info("Created SwissTiming RaceDefinition and TrackedRace for "+race.getName()); } + + /** + * If a non-{@code null}, non-{@link String#isEmpty() empty} {@link #updateURL} is set, adds {@link UpdateHandler}s + * to the race that will forward changes to the race such as start time changes or course changes to the URL as REST + * requests. + */ + private void addUpdateHandlers() throws URISyntaxException { + getDomainFactory().addUpdateHandlers(updateURL, updateUsername, updatePassword, + /* TODO using the regatta ID as the "eventId" for now... */ regatta.getId(), trackedRace.getRace(), trackedRace); + } /** * Checks if {@link #getRegatta()} still contains the {@link RaceDefinition} obtained when calling @@ -481,7 +505,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } @Override - public void receivedCourseConfiguration(String raceID, Course course) { + public void receivedCourseConfiguration(String raceID, Course course) throws URISyntaxException { Course oldCourse = this.course; if (trackedRace == null) { if (oldCourse == null && startList != null) { diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java index de24889e36e..e14df771fa1 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java @@ -31,11 +31,15 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack private final long delayToLiveInMillis; private final StartList startList; private final boolean useInternalMarkPassingAlgorithm; + private final String updateURL; + private final String updateUsername; + private final String updatePassword; public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, - RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) { + RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, + boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword) { super(trackWind, correctWindDirectionByMagneticDeclination); this.hostname = hostname; this.port = port; @@ -50,6 +54,9 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack this.raceLogStore = raceLogStore; this.regattaLogStore = regattaLogStore; this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm; + this.updateURL = updateURL; + this.updateUsername = updateUsername; + this.updatePassword = updatePassword; } @Override @@ -60,18 +67,15 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack @Override public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, RaceLogResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, long timeoutInMilliseconds) throws Exception { - return swissTimingFactory.createRaceTracker(raceID, raceName, raceDescription, boatClass, hostname, port, - startList, delayToLiveInMillis, raceLogStore, regattaLogStore, windStore, - useInternalMarkPassingAlgorithm, - domainFactory, trackedRegattaRegistry, raceLogResolver, this); + return swissTimingFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, domainFactory, trackedRegattaRegistry, raceLogResolver, + this); } @Override public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, RaceLogResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, long timeoutInMilliseconds) throws Exception { - return swissTimingFactory.createRaceTracker(regatta, raceID, raceName, raceDescription, boatClass, hostname, - port, startList, delayToLiveInMillis, windStore, useInternalMarkPassingAlgorithm, - domainFactory, trackedRegattaRegistry, raceLogResolver, raceLogStore, regattaLogStore, this); + return swissTimingFactory.createRaceTracker(regatta, windStore, domainFactory, trackedRegattaRegistry, raceLogResolver, raceLogStore, + regattaLogStore, this); } @Override @@ -92,6 +96,18 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack return port; } + public String getUpdateURL() { + return updateURL; + } + + public String getUpdateUsername() { + return updateUsername; + } + + public String getUpdatePassword() { + return updatePassword; + } + public String getRaceID() { return raceID; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverComputationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverComputationTest.java index a7aceba5379..572727bfa3b 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverComputationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverComputationTest.java @@ -130,11 +130,14 @@ public class IncrementalManeuverComputationTest extends AbstractManeuverDetectio performanceMeasurementStartedAt = System.currentTimeMillis(); List normallyDetectedManeuvers = normalManeuverDetector.detectManeuvers(); long millisForNormalManeuverDetection = System.currentTimeMillis() - performanceMeasurementStartedAt; + int performanceBenefitOfIncrementalManeuverDetectionInPercent = (int) ((1.0 * (millisForNormalManeuverDetection - millisForIncrementalManeuverDetection) + / millisForNormalManeuverDetection) * 100); assertEquals("Incrementally calculated maneuvers differ from normally calculated maneuvers", normallyDetectedManeuvers, incrementallyDetectedManeuvers); assertTrue( - "Incremental maneuver detection was not 30% faster in detecting maneuvers incrementally, than the full maneuver detection", - millisForIncrementalManeuverDetection * 1.3 < millisForNormalManeuverDetection); + "Incremental maneuver detection was not 20% faster in detecting maneuvers incrementally, than the full maneuver detection. The actual performance benefit was: " + + performanceBenefitOfIncrementalManeuverDetectionInPercent + "%", + performanceBenefitOfIncrementalManeuverDetectionInPercent >= 20); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java index 9906f341350..5983e1f4f22 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java @@ -15,6 +15,7 @@ import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack; import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sse.common.Util; import com.tractrac.model.lib.api.route.IControlRoute; +import com.tractrac.model.lib.api.route.IPathRoute; import com.tractrac.subscription.lib.api.control.IControlRouteChangeListener; public class RouteAssemblyTest extends AbstractTracTracLiveTest { @@ -66,6 +67,11 @@ public class RouteAssemblyTest extends AbstractTracTracLiveTest { first = false; } } + + @Override + public void gotRouteChange(IPathRoute pathRoute, long timeStamp) { + // will never be invoked for sailing events + } }); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 1d6ccf2ae2b..da49ca9a085 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -14,8 +14,8 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -73,6 +73,9 @@ import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler; +import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler; +import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.JSONService; import com.sap.sailing.domain.tractracadapter.MetadataParser; @@ -677,17 +680,16 @@ public class DomainFactoryImpl implements DomainFactory { TracTracCourseDesignUpdateHandler courseDesignHandler = new TracTracCourseDesignUpdateHandler( tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid, raceDefinition.getId(), tractracRace, this); - trackedRace.addCourseDesignChangedListener(courseDesignHandler); - TracTracStartTimeUpdateHandler startTimeHandler = new TracTracStartTimeUpdateHandler( + StartTimeUpdateHandler startTimeHandler = new StartTimeUpdateHandler( tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid, raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta()); - trackedRace.addStartTimeChangedListener(startTimeHandler); - TracTracRaceAbortedHandler raceAbortedHandler = new TracTracRaceAbortedHandler( + RaceAbortedHandler raceAbortedHandler = new RaceAbortedHandler( tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid, raceDefinition.getId()); - trackedRace.addRaceAbortedListener(raceAbortedHandler); - trackedRace.addListener(new TracTracFinishTimeUpdateHandler(tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid, - raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta()).getListener()); + final FinishTimeUpdateHandler finishTimeUpdateHandler = new FinishTimeUpdateHandler(tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid, + raceDefinition.getId(), trackedRace.getTrackedRegatta().getRegatta()); + baseDomainFactory.addUpdateHandlers(trackedRace, courseDesignHandler, startTimeHandler, raceAbortedHandler, + finishTimeUpdateHandler); } private DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race, diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java index 8f6a279789b..2efecd731d8 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java @@ -37,6 +37,7 @@ import com.tractrac.model.lib.api.event.IEvent; import com.tractrac.model.lib.api.event.IRace; import com.tractrac.model.lib.api.route.IControl; import com.tractrac.model.lib.api.route.IControlRoute; +import com.tractrac.model.lib.api.route.IPathRoute; import com.tractrac.model.lib.api.route.IRoute; import com.tractrac.subscription.lib.api.IEventSubscriber; import com.tractrac.subscription.lib.api.IRaceSubscriber; @@ -97,6 +98,13 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue(controlRoute, timeStamp, null)); } + + @Override + public void gotRouteChange(IPathRoute pathRoute, long timeStamp) { + // will never be invoked for sailing events; Jorge Llodra (2018-07-30): + // "The IControlRouteChangeListener has been extended adding a new method to get updates of a + // IPathRoute. If you are managing "sailing events" this method will never be invoked." + } }; } @@ -243,17 +251,19 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue sidelines, Consumer runAfterCreatingTrackedRace) { - DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines, - windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, - /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), + private DynamicTrackedRace createTrackedRace(RaceDefinition race, Iterable sidelines, + Consumer runAfterCreatingTrackedRace) { + DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines, windStore, + delayToLiveInMillis, millisecondsOverWhichToAverageWind, + /* time over which to average speed: */ race.getBoatClass() + .getApproximateManeuverDurationInMilliseconds(), raceDefinitionSetToUpdate, useInternalMarkPassingAlgorithm, raceLogResolver, /* Not needed because the RaceTracker is not active on a replica */ Optional.empty()); if (runAfterCreatingTrackedRace != null) { - runAfterCreatingTrackedRace.accept(trackedRace); + runAfterCreatingTrackedRace.accept(trackedRace); } - getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, tracTracPassword, race, trackedRace, - tractracRace); + getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, + tracTracPassword, race, trackedRace, tractracRace); return trackedRace; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java index c2167e8d888..18984574043 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracCourseDesignUpdateHandler.java @@ -1,86 +1,32 @@ package com.sap.sailing.domain.tractracadapter.impl; -import java.io.IOException; import java.io.Serializable; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URL; import java.util.Iterator; -import java.util.logging.Logger; - -import org.json.simple.JSONObject; -import org.json.simple.parser.ParseException; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.CourseDataImpl; -import com.sap.sailing.domain.tracking.CourseDesignChangedListener; +import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler; import com.sap.sailing.domain.tractracadapter.DomainFactory; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sailing.server.gateway.serialization.coursedata.impl.ControlPointJsonSerializer; -import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.coursedata.impl.GateJsonSerializer; -import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer; -import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJsonSerializer; import com.sap.sse.common.Util; import com.tractrac.model.lib.api.event.IRace; import com.tractrac.model.lib.api.route.IControl; -public class TracTracCourseDesignUpdateHandler extends UpdateHandler implements CourseDesignChangedListener { - - private final static String ACTION = "update_course"; - - private final static Logger logger = Logger.getLogger(TracTracCourseDesignUpdateHandler.class.getName()); - private final JsonSerializer courseSerializer; +public class TracTracCourseDesignUpdateHandler extends CourseDesignUpdateHandler { private final IRace tractracRace; private final DomainFactory domainFactory; public TracTracCourseDesignUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId, IRace tractracRace, DomainFactory domainFactory) { - super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId); + super(updateURI, tracTracUsername, tracTracPassword, tracTracEventId, raceId); this.domainFactory = domainFactory; this.tractracRace = tractracRace; - this.courseSerializer = new CourseJsonSerializer( - new CourseBaseJsonSerializer( - new WaypointJsonSerializer( - new ControlPointJsonSerializer( - new MarkJsonSerializer(), - new GateJsonSerializer(new MarkJsonSerializer()))))); } @Override - public void courseDesignChanged(final CourseBase newCourseDesign) throws MalformedURLException, IOException { - if (!isActive()) { - logger.info("Not sending course update to TracTrac because no URL has been provided."); - return; - } - final CourseBase newCourseDesignWithExistingControlPoints = replaceControlPointsByMatchingExistingControlPoints(newCourseDesign); - JSONObject serializedCourseDesign = courseSerializer.serialize(newCourseDesignWithExistingControlPoints); - String payload = serializedCourseDesign.toJSONString(); - URL currentCourseDesignURL = buildUpdateURL(); - logger.info("Using " + currentCourseDesignURL.toString() + " for the course update!"); - logger.info("Payload is " + payload); - HttpURLConnection connection = (HttpURLConnection) currentCourseDesignURL.openConnection(); - try { - setConnectionPropertiesAndSendWithPayload(connection, payload); - try { - checkAndLogUpdateResponse(connection); - } catch (ParseException e) { - e.printStackTrace(); - } - } finally { - if (connection != null) { - connection.disconnect(); - } else { - logger.severe("Connection to TracTrac Course Update URL " + currentCourseDesignURL.toString() + " could not be established"); - } - } - } - - private CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) { + protected CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) { final Iterable candidates = domainFactory.getControlsForCourseArea(tractracRace.getEvent(), tractracRace.getCourseArea()); final CourseBase result = new CourseDataImpl(courseDesign.getName()); int zeroBasedPosition = 0; diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index 6f6d9a68bbe..8d5cf1bb0a9 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -47,5 +47,12 @@ Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0", com.sap.sse.shared.android, org.apache.commons.math, com.sap.sailing.declination, + com.sap.sailing.server.gateway.serialization.shared.android, com.sap.sse.datamining.annotations +Import-Package: org.apache.http, + org.apache.http.client;version="[4.2.5,4.3.0)", + org.apache.http.client.entity;version="[4.2.5,4.3.0)", + org.apache.http.client.methods;version="[4.2.5,4.3.0)", + org.apache.http.impl.client;version="[4.2.5,4.3.0)", + org.apache.http.message;version="[4.2.5,4.3.0)" Automatic-Module-Name: com.sap.sailing.domain diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java index b81e8e1b1ff..be6b6a144c7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java @@ -21,9 +21,14 @@ import com.sap.sailing.domain.common.dto.TrackedRaceStatisticsDTO; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler; +import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler; +import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler; +import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler; import com.sap.sse.common.IsManagedByCache; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -101,4 +106,11 @@ public interface DomainFactory extends SharedDomainFactory { */ void updateRaceDTOWithTrackedRaceData(TrackedRace trackedRace, RaceDTO raceDTO); + /** + * Adds update handlers to a {@link TrackedRace} which can send updates to a REST service about race status + * changes such as a start time change, a course change, or a postponement. + */ + void addUpdateHandlers(DynamicTrackedRace trackedRace, CourseDesignUpdateHandler courseDesignHandler, + StartTimeUpdateHandler startTimeHandler, RaceAbortedHandler raceAbortedHandler, + final FinishTimeUpdateHandler finishTimeUpdateHandler); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java index 1d268d47ff9..92807bed0c6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java @@ -11,6 +11,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -60,15 +62,15 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem private TrackedRaces trackedRaces; private Map raceIdentifiers; - private Map raceLogs; - + private ConcurrentMap raceLogs; + private transient RaceLogStore raceLogStore; private RegattaLikeIdentifier regattaLikeParent; public AbstractRaceColumn() { this.trackedRaces = new TrackedRaces(); this.raceIdentifiers = new HashMap(); - this.raceLogs = new HashMap(); + this.raceLogs = new ConcurrentHashMap(); } @Override @@ -82,9 +84,7 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem @Override public RaceLog getRaceLog(Fleet fleet) { - synchronized (raceLogs) { - return raceLogs.get(fleet); - } + return raceLogs.get(fleet); } @Override @@ -197,25 +197,23 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem @Override public void reloadRaceLog(Fleet fleet) { - synchronized (raceLogs) { - RaceLogIdentifier identifier = getRaceLogIdentifier(fleet); - RaceLog newOrLoadedRaceLog = raceLogStore.getRaceLog(identifier, /* ignoreCache */true); - RaceLog raceLogAvailable = raceLogs.get(fleet); - if (raceLogAvailable == null) { - RaceColumnRaceLogReplicator listener = new RaceColumnRaceLogReplicator(this, identifier); - // FIXME Wouldn't this skip any listener notifications that a merge below would trigger if the race log already existed? - // FIXME For example, how about the race log-provided score corrections that need application to the leaderboard and replication? - newOrLoadedRaceLog.addListener(listener); - raceLogs.put(fleet, newOrLoadedRaceLog); - final TrackedRace trackedRace = getTrackedRace(fleet); - if (trackedRace != null) { - // need to attach race log - trackedRace.attachRaceLog(newOrLoadedRaceLog); - } - } else { - // now add all race log events from newOrLoadedRaceLog that are not already in raceLogAvailable - raceLogAvailable.merge(newOrLoadedRaceLog); + RaceLogIdentifier identifier = getRaceLogIdentifier(fleet); + RaceLog newOrLoadedRaceLog = raceLogStore.getRaceLog(identifier, /* ignoreCache */true); + RaceLog raceLogAvailable = raceLogs.get(fleet); + if (raceLogAvailable == null) { + RaceColumnRaceLogReplicator listener = new RaceColumnRaceLogReplicator(this, identifier); + // FIXME Wouldn't this skip any listener notifications that a merge below would trigger if the race log already existed? + // FIXME For example, how about the race log-provided score corrections that need application to the leaderboard and replication? + newOrLoadedRaceLog.addListener(listener); + raceLogs.put(fleet, newOrLoadedRaceLog); + final TrackedRace trackedRace = getTrackedRace(fleet); + if (trackedRace != null) { + // need to attach race log + trackedRace.attachRaceLog(newOrLoadedRaceLog); } + } else { + // now add all race log events from newOrLoadedRaceLog that are not already in raceLogAvailable + raceLogAvailable.merge(newOrLoadedRaceLog); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java index 01e73aeacfe..7444495aabc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java @@ -64,11 +64,16 @@ import com.sap.sailing.domain.leaderboard.impl.LowPointTieBreakBasedOnLastSeries import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; import com.sap.sailing.domain.leaderboard.impl.LowPointWithAutomaticRDG; import com.sap.sailing.domain.leaderboard.impl.LowPointWithEliminationsAndRoundsWinnerGets07; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.impl.CourseDesignUpdateHandler; +import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler; +import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler; import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; import com.sap.sailing.geocoding.ReverseGeocoder; import com.sap.sse.common.Distance; @@ -201,11 +206,12 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain assert trackedRace != null; raceDTO.startOfRace = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate(); raceDTO.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate(); + raceDTO.raceFinishedTime = trackedRace.getFinishedTime() == null ? null : trackedRace.getFinishedTime().asDate(); raceDTO.status = new RaceStatusDTO(); raceDTO.status.status = trackedRace.getStatus() == null ? null : trackedRace.getStatus().getStatus(); raceDTO.status.loadingProgress = trackedRace.getStatus() == null ? 0.0 : trackedRace.getStatus().getLoadingProgress(); } - + @Override public TrackedRaceDTO createTrackedRaceDTO(TrackedRace trackedRace) { TrackedRaceDTO trackedRaceDTO = new TrackedRaceDTO(); @@ -394,6 +400,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return result; } + @Override public List getCompetitorDTOList(List> competitors) { List result = new ArrayList<>(); for (Pair competitorAndBoat : competitors) { @@ -401,4 +408,14 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain } return result; } + + @Override + public void addUpdateHandlers(DynamicTrackedRace trackedRace, CourseDesignUpdateHandler courseDesignHandler, + StartTimeUpdateHandler startTimeHandler, RaceAbortedHandler raceAbortedHandler, + final FinishTimeUpdateHandler finishTimeUpdateHandler) { + trackedRace.addCourseDesignChangedListener(courseDesignHandler); + trackedRace.addStartTimeChangedListener(startTimeHandler); + trackedRace.addRaceAbortedListener(raceAbortedHandler); + trackedRace.addListener(finishTimeUpdateHandler.getListener()); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java index 9738718cca4..35ee095e22f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java @@ -100,9 +100,9 @@ public interface CompleteManeuverCurveWithEstimationData extends Timed, Position Distance getDistanceToClosestMark(); - Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees(); + Double getTargetTackAngleInDegrees(); - Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees(); + Double getTargetJibeAngleInDegrees(); /** * Gets whether a mark was crossed within the maneuver curve. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java index 89c43715903..89459a483a5 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java @@ -27,14 +27,15 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane private final boolean markPassing; private final Position position; private final Distance distanceToClosestMark; - private final Double deviationOfManeuverAngleFromTargetTackAngleInDegrees; - private final Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees; + private final Double targetTackAngleInDegrees; + private final Double targetJibeAngleInDegrees; public CompleteManeuverCurveWithEstimationDataImpl(Position position, ManeuverMainCurveWithEstimationData mainCurve, ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed, Wind wind, int tackingCount, int jibingCount, boolean maneuverStartsByRunningAwayFromWind, Bearing relativeBearingToNextMarkBeforeManeuver, Bearing relativeBearingToNextMarkAfterManeuver, - boolean markPassing, Distance distanceToClosestMark, Double deviationOfManeuverAngleFromTargetTackAngleInDegrees, Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees) { + boolean markPassing, Distance distanceToClosestMark, Double targetTackAngleInDegrees, + Double targetJibeAngleInDegrees) { this.position = position; this.mainCurve = mainCurve; this.curveWithUnstableCourseAndSpeed = curveWithUnstableCourseAndSpeed; @@ -46,8 +47,8 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane this.relativeBearingToNextMarkAfterManeuver = relativeBearingToNextMarkAfterManeuver; this.markPassing = markPassing; this.distanceToClosestMark = distanceToClosestMark; - this.deviationOfManeuverAngleFromTargetTackAngleInDegrees = deviationOfManeuverAngleFromTargetTackAngleInDegrees; - this.deviationOfManeuverAngleFromTargetJibeAngleInDegrees = deviationOfManeuverAngleFromTargetJibeAngleInDegrees; + this.targetTackAngleInDegrees = targetTackAngleInDegrees; + this.targetJibeAngleInDegrees = targetJibeAngleInDegrees; } @Override @@ -99,20 +100,20 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane public Position getPosition() { return position; } - + @Override public Distance getDistanceToClosestMark() { return distanceToClosestMark; } - + @Override - public Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees() { - return deviationOfManeuverAngleFromTargetTackAngleInDegrees; + public Double getTargetTackAngleInDegrees() { + return targetTackAngleInDegrees; } - + @Override - public Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees() { - return deviationOfManeuverAngleFromTargetJibeAngleInDegrees; + public Double getTargetJibeAngleInDegrees() { + return targetJibeAngleInDegrees; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java index 14d8b767aea..8e13e8fe62e 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java @@ -278,7 +278,8 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(), maneuverCurve.getMainCurveBoundaries().getTimePoint(), maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate, - distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), distanceSailedIfNotManeuvering, + distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), + distanceSailedIfNotManeuvering, projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(), Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees()) / maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(), @@ -338,9 +339,9 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(), durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), distanceSailedIfNotManeuvering, - projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(), gpsFixCountWithinWholeCurve, - longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix, - intervalBetweenFirstFixOfCurveAndPreviousFix); + projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(), + gpsFixCountWithinWholeCurve, longestGpsFixIntervalBetweenTwoFixes, + intervalBetweenLastFixOfCurveAndNextFix, intervalBetweenFirstFixOfCurveAndPreviousFix); TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); @@ -357,8 +358,8 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl .getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing()); BoatClass boatClass = maneuverDetector.trackedRace.getRace().getBoatOfCompetitor(maneuverDetector.competitor) .getBoatClass(); - Double deviationFromTackAngle = null; - Double deviationFromJibeAngle = null; + Double targetTackAngle = null; + Double targetJibeAngle = null; Speed boatSpeed = curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore() .compareTo(curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter()) < 0 ? curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore() @@ -369,11 +370,11 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl SpeedWithBearingWithConfidence closestJibeTwa = polarDataService.getClosestTwaTws(ManeuverType.JIBE, boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass); if (closestTackTwa != null) { - deviationFromTackAngle = polarDataService.getManeuverAngleInDegreesFromTwa( + targetTackAngle = polarDataService.getManeuverAngleInDegreesFromTwa( closestTackTwa.getObject().getBearing().getDegrees(), ManeuverType.TACK); } if (closestJibeTwa != null) { - deviationFromJibeAngle = polarDataService.getManeuverAngleInDegreesFromTwa( + targetJibeAngle = polarDataService.getManeuverAngleInDegreesFromTwa( closestJibeTwa.getObject().getBearing().getDegrees(), ManeuverType.JIBE); } } @@ -383,7 +384,7 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes, maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver, relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing(), closestDistanceToMark, - deviationFromTackAngle, deviationFromJibeAngle); + targetTackAngle, targetJibeAngle); } public Distance getClosestDistanceToMark(TimePoint timePoint) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java index a587a968b97..b67baa44004 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java @@ -53,6 +53,13 @@ public interface CandidateFinder { */ Map, Iterable>> getCandidateDeltasAfterRaceStartTimeChange(); + /** + * Notifies this finder about the race's start of tracking time having changed. In case of a race that infers + * the race start time from the start mark passings, a change in start of tracking needs to adjust the time range + * in which candidates are considered valid. + */ + Map, Iterable>> getCandidateDeltasAfterStartOfTrackingChange(); + /** * Notifies this finder about the race's finished time having changed. The candidates collections are adjusted * accordingly: new candidates may be added if the finished time was moved to a later point in time and therefore diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java index 185d153b6d6..00b1a8be99f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java @@ -195,6 +195,25 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { }); } + @Override + public void startOfTrackingChanged(TimePoint oldStartOfTracking, TimePoint newStartOfTracking) { + markPassingCalculator.enqueueUpdate(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, + Map> markFixes, List addedWaypoints, List removedWaypoints, + IntHolder smallestChangedWaypointIndex, + List> fixedMarkPassings, + List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { + final Map, Iterable>> newAndRemovedCandidatesPerCompetitor = + candidateFinder.getCandidateDeltasAfterStartOfTrackingChange(); + for (final Entry, Iterable>> i : newAndRemovedCandidatesPerCompetitor.entrySet()) { + candidateChooser.calculateMarkPassDeltas(i.getKey(), i.getValue().getA(), i.getValue().getB()); + } + } + }); + } + @Override public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { markPassingCalculator.enqueueUpdate(new StorePositionUpdateStrategy() { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java index 3d7a67bb366..171ddb82cf9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java @@ -37,6 +37,7 @@ import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl; import com.sap.sailing.domain.tracking.impl.TimedComparator; import com.sap.sse.common.Bearing; @@ -1455,12 +1456,26 @@ public class CandidateFinderImpl implements CandidateFinder { /** * If the {@link #race}'s regatta is configured to infer the start times from start mark passings then {@code null} * must be tolerated as a value for {@code from}, leading to an open interval starting at the - * {@link TimePoint#BeginningOfTime beginning of time}. However, if the start time is expected to be set and not - * inferred, mark passings need to be detected only from the start minus some tolerance interval. In this case, - * an interval that has {@code null} as its {@code from} time point and thus is considered empty will be returned. - * It hence returns {@code null} from its {@link TimeRangeWithNullStartMeaningEmpty#getTimeRangeOrNull()} method. + * {@link TrackedRace#getStartOfTracking()} or, if not set, the {@link TimePoint#BeginningOfTime beginning of time}. + * However, if the start time is expected to be set and not inferred, mark passings need to be detected only from + * the start minus some tolerance interval. In this case, an interval that has {@code null} as its {@code from} time + * point and thus is considered empty will be returned. It hence returns {@code null} from its + * {@link TimeRangeWithNullStartMeaningEmpty#getTimeRangeOrNull()} method. */ private TimeRangeWithNullStartMeaningEmpty getTimeRangeOrNull(TimePoint from, TimePoint to) { + final TimePoint effectiveFrom = getEffectiveFrom(from); + return new TimeRangeWithNullStartMeaningEmpty(effectiveFrom, to); + } + + /** + * If the {@link #race}'s regatta is configured to infer the start times from start mark passings then {@code null} + * must be tolerated as a value for {@code from}, leading to an open interval starting at the + * {@link TrackedRace#getStartOfTracking()} or, if not set, the {@link TimePoint#BeginningOfTime beginning of time}. + * However, if the start time is expected to be set and not inferred, mark passings need to be detected only from + * the start minus some tolerance interval. In this case, for an interval that has {@code null} as its {@code from} time + * point and thus is considered empty, {@code null} will be returned. + */ + private TimePoint getEffectiveFrom(TimePoint from) { final TimePoint effectiveFrom; if (from == null && race.getTrackedRegatta().getRegatta().useStartTimeInference()) { // need to check the whole track to be able to find start mark passings @@ -1470,15 +1485,22 @@ public class CandidateFinderImpl implements CandidateFinder { } else { effectiveFrom = from; } - return new TimeRangeWithNullStartMeaningEmpty(effectiveFrom, to); + return effectiveFrom; } @Override public Map, Iterable>> getCandidateDeltasAfterRaceStartTimeChange() { - final Map, Iterable>> result; final TimePoint newNonInferredStartTime = race.getStartOfRace(/* inferred */ false); final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime); - final TimeRangeWithNullStartMeaningEmpty newTimeRange = timeRangeForValidCandidates.getWithNewFrom(newTimePointWhenToStartConsideringCandidates); + final TimePoint newEffectiveTimePointWhenToStartConsideringCandidates = getEffectiveFrom(newTimePointWhenToStartConsideringCandidates); + final TimeRangeWithNullStartMeaningEmpty newTimeRange = timeRangeForValidCandidates.getWithNewFrom(newEffectiveTimePointWhenToStartConsideringCandidates); + return getCandidateDeltasAfterTimingChange(newEffectiveTimePointWhenToStartConsideringCandidates, newTimeRange); + } + + private Map, Iterable>> getCandidateDeltasAfterTimingChange( + final TimePoint newTimePointWhenToStartConsideringCandidates, + final TimeRangeWithNullStartMeaningEmpty newTimeRange) { + final Map, Iterable>> result; if (!Util.equalsWithNull(newTimeRange, timeRangeForValidCandidates)) { if (newTimeRange.getTimeRangeOrNull() == null) { result = clearAllCandidates(); @@ -1499,6 +1521,15 @@ public class CandidateFinderImpl implements CandidateFinder { return result; } + @Override + public Map, Iterable>> getCandidateDeltasAfterStartOfTrackingChange() { + final TimePoint newNonInferredStartTime = race.getStartOfRace(/* inferred */ false); + final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime); + final TimePoint newEffectiveTimePointWhenToStartConsideringCandidates = getEffectiveFrom(newTimePointWhenToStartConsideringCandidates); + final TimeRangeWithNullStartMeaningEmpty newTimeRange = timeRangeForValidCandidates.getWithNewFrom(newEffectiveTimePointWhenToStartConsideringCandidates); + return getCandidateDeltasAfterTimingChange(newEffectiveTimePointWhenToStartConsideringCandidates, newTimeRange); + } + @Override public Map, Iterable>> getCandidateDeltasAfterRaceFinishedTimeChange( TimePoint oldFinishedTime, TimePoint newFinishedTime) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseDesignUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseDesignUpdateHandler.java new file mode 100755 index 00000000000..1ad1a82c1c5 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseDesignUpdateHandler.java @@ -0,0 +1,78 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.io.IOException; +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.logging.Logger; + +import org.json.simple.JSONObject; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.tracking.CourseDesignChangedListener; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sailing.server.gateway.serialization.coursedata.impl.ControlPointJsonSerializer; +import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.coursedata.impl.GateJsonSerializer; +import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer; +import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJsonSerializer; + +public class CourseDesignUpdateHandler extends UpdateHandler implements CourseDesignChangedListener { + + private final static String ACTION = "update_course"; + + private final static Logger logger = Logger.getLogger(CourseDesignUpdateHandler.class.getName()); + private final JsonSerializer courseSerializer; + + public CourseDesignUpdateHandler(URI updateURI, String username, String password, Serializable eventId, Serializable raceId) { + super(updateURI, ACTION, username, password, eventId, raceId); + this.courseSerializer = new CourseJsonSerializer( + new CourseBaseJsonSerializer( + new WaypointJsonSerializer( + new ControlPointJsonSerializer( + new MarkJsonSerializer(), + new GateJsonSerializer(new MarkJsonSerializer()))))); + } + + @Override + public void courseDesignChanged(final CourseBase newCourseDesign) throws MalformedURLException, IOException { + if (!isActive()) { + logger.info("Not sending course update to TracTrac because no URL has been provided."); + return; + } + final CourseBase newCourseDesignWithExistingControlPoints = replaceControlPointsByMatchingExistingControlPoints(newCourseDesign); + JSONObject serializedCourseDesign = courseSerializer.serialize(newCourseDesignWithExistingControlPoints); + String payload = serializedCourseDesign.toJSONString(); + URL currentCourseDesignURL = buildUpdateURL(); + logger.info("Using " + currentCourseDesignURL.toString() + " for the course update!"); + logger.info("Payload is " + payload); + HttpURLConnection connection = (HttpURLConnection) currentCourseDesignURL.openConnection(); + try { + setConnectionPropertiesAndSendWithPayload(connection, payload); + try { + checkAndLogUpdateResponse(connection); + } catch (ParseException e) { + e.printStackTrace(); + } + } finally { + if (connection != null) { + connection.disconnect(); + } else { + logger.severe("Connection to TracTrac Course Update URL " + currentCourseDesignURL.toString() + " could not be established"); + } + } + } + + /** + * This default implementation returns the {@code courseDesign} unchanged. Subclasses have the possibility to + * override this method in order to replace individual course marks provided in a detailed course design, + * based on the actual course marks available. + */ + protected CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) { + return courseDesign; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java index 850dcb7ed62..572a27ff89a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java @@ -205,6 +205,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { owner.addListener(this); } + @FunctionalInterface private interface CrossTrackErrorMeterRetriever { double getDistanceInMetersSumFromStart(CrossTrackErrorSumAndNumberOfFixes cacheEntry); } @@ -220,12 +221,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { */ public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatest) throws NoWindException { - CrossTrackErrorMeterRetriever absoluteMetersRetriever = new CrossTrackErrorMeterRetriever() { - @Override - public double getDistanceInMetersSumFromStart(CrossTrackErrorSumAndNumberOfFixes cacheEntry) { - return cacheEntry.getAbsoluteDistanceInMetersSumFromStart(); - } - }; + CrossTrackErrorMeterRetriever absoluteMetersRetriever = cacheEntry->cacheEntry.getAbsoluteDistanceInMetersSumFromStart(); return getAbsoluteOrSignedAverageCrossTrackError(competitor, from, to, upwindOnly, waitForLatest, absoluteMetersRetriever); } @@ -240,20 +236,15 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { */ public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatest) throws NoWindException { - CrossTrackErrorMeterRetriever signedMetersRetriever = new CrossTrackErrorMeterRetriever() { - @Override - public double getDistanceInMetersSumFromStart(CrossTrackErrorSumAndNumberOfFixes cacheEntry) { - return cacheEntry.getSignedDistanceInMetersSumFromStart(); - } - }; + CrossTrackErrorMeterRetriever signedMetersRetriever = cacheEntry->cacheEntry.getSignedDistanceInMetersSumFromStart(); return getAbsoluteOrSignedAverageCrossTrackError(competitor, from, to, upwindOnly, waitForLatest, signedMetersRetriever); } /** - * @param absoluteMetersRetriever determines whether the absolute or signed cross track error will be aggregated + * @param absoluteOrSignedMetersRetriever determines whether the absolute or signed cross track error will be aggregated */ private Distance getAbsoluteOrSignedAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, - boolean upwindOnly, boolean waitForLatest, CrossTrackErrorMeterRetriever absoluteMetersRetriever) + boolean upwindOnly, boolean waitForLatest, CrossTrackErrorMeterRetriever absoluteOrSignedMetersRetriever) throws NoWindException { Track cacheForCompetitor = cachePerCompetitor.get(competitor, waitForLatest); double distanceInMeters = 0; @@ -268,7 +259,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { final MarkPassing legStartMarkPassing = owner.getMarkPassing(competitor, leg.getFrom()); if (legStartMarkPassing != null) { if (!upwindOnly || trackedLeg.getLegType(legStartMarkPassing.getTimePoint()) == LegType.UPWIND) { - TimePoint start; + final TimePoint start; final TimePoint legStart = legStartMarkPassing.getTimePoint(); if (legStart.compareTo(from) < 0) { // the interval requested starts after this leg's start: @@ -277,7 +268,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { start = legStart; } final MarkPassing legEndMarkPassing = owner.getMarkPassing(competitor, leg.getTo()); - TimePoint end; + final TimePoint end; if (legEndMarkPassing == null || legEndMarkPassing.getTimePoint().compareTo(to) >= 0) { // no next mark passing, or next mark passing is beyond the "to" time point; aggregate up to "to" end = to; @@ -294,7 +285,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { } CrossTrackErrorSumAndNumberOfFixes endAggregate = cacheForCompetitor.getLastFixAtOrBefore(end); if (endAggregate != null) { - distanceInMeters += absoluteMetersRetriever.getDistanceInMetersSumFromStart(endAggregate) - absoluteMetersRetriever.getDistanceInMetersSumFromStart(startAggregate); + distanceInMeters += absoluteOrSignedMetersRetriever.getDistanceInMetersSumFromStart(endAggregate) - absoluteOrSignedMetersRetriever.getDistanceInMetersSumFromStart(startAggregate); count += endAggregate.getFixCountFromStart() - startAggregate.getFixCountFromStart(); startAggregate = endAggregate; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index aaa83b444ec..bcc66c88fa7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -79,7 +79,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { logger.severe("Trying to add " + this + " as listener to a null race log for tracked race " + trackedRace.getRace()); } else { raceLog.addListener(this); - final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.create(trackedRace.getRaceLogResolver(), raceLog); + final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(trackedRace.getRaceLogResolver(), raceLog); raceLogs.put(raceLog, raceState); raceState.addChangedListener(new BaseRaceStateChangedListener() { @Override diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracFinishTimeUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java similarity index 84% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracFinishTimeUpdateHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java index dfae3907cfa..e5c027ec562 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracFinishTimeUpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.tractracadapter.impl; +package com.sap.sailing.domain.tracking.impl; import java.io.BufferedReader; import java.io.IOException; @@ -20,12 +20,11 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; import com.sap.sse.common.TimePoint; import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes; -public class TracTracFinishTimeUpdateHandler extends UpdateHandler { - private final static Logger logger = Logger.getLogger(TracTracFinishTimeUpdateHandler.class.getName()); +public class FinishTimeUpdateHandler extends UpdateHandler { + private final static Logger logger = Logger.getLogger(FinishTimeUpdateHandler.class.getName()); private final static String ACTION_STOP_TRACKING = "end_tracking"; private final static String FIELD_TRACKING_END_TIME = "tracking_end_time"; @@ -36,9 +35,9 @@ public class TracTracFinishTimeUpdateHandler extends UpdateHandler { */ private final Regatta regatta; - public TracTracFinishTimeUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, - Serializable tracTracEventId, Serializable raceId, Regatta regatta) { - super(updateURI, ACTION_STOP_TRACKING, tracTracUsername, tracTracPassword, tracTracEventId, raceId); + public FinishTimeUpdateHandler(URI updateURI, String username, String password, + Serializable eventId, Serializable raceId, Regatta regatta) { + super(updateURI, ACTION_STOP_TRACKING, username, password, eventId, raceId); this.regatta = regatta; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceAbortedHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java similarity index 78% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceAbortedHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java index 1963e3f4dc5..50b723a4405 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceAbortedHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.tractracadapter.impl; +package com.sap.sailing.domain.tracking.impl; import java.io.IOException; import java.io.Serializable; @@ -15,14 +15,14 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.tracking.RaceAbortedListener; -public class TracTracRaceAbortedHandler extends UpdateHandler implements RaceAbortedListener { +public class RaceAbortedHandler extends UpdateHandler implements RaceAbortedListener { private final static String ACTION = "update_race_status"; - private final static Logger logger = Logger.getLogger(TracTracRaceAbortedHandler.class.getName()); + private final static Logger logger = Logger.getLogger(RaceAbortedHandler.class.getName()); - public TracTracRaceAbortedHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) { - super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId); + public RaceAbortedHandler(URI updateURI, String username, String password, Serializable eventId, Serializable raceId) { + super(updateURI, ACTION, username, password, eventId, raceId); } @Override diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeResetHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java similarity index 73% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeResetHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java index dc46e1b23a9..941d74e20ee 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeResetHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.tractracadapter.impl; +package com.sap.sailing.domain.tracking.impl; import java.io.IOException; import java.io.Serializable; @@ -13,14 +13,14 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.tracking.StartTimeChangedListener; import com.sap.sse.common.TimePoint; -public class TracTracStartTimeResetHandler extends UpdateHandler implements StartTimeChangedListener { +public class StartTimeResetHandler extends UpdateHandler implements StartTimeChangedListener { private static final String ACTION = "resetStartTime"; - private final static Logger logger = Logger.getLogger(TracTracStartTimeResetHandler.class.getName()); + private final static Logger logger = Logger.getLogger(StartTimeResetHandler.class.getName()); - public TracTracStartTimeResetHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) { - super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId); + public StartTimeResetHandler(URI updateURI, String username, String password, Serializable eventId, Serializable raceId) { + super(updateURI, ACTION, username, password, eventId, raceId); } @Override diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java similarity index 93% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeUpdateHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java index eaef290e628..8dab411be13 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracStartTimeUpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.tractracadapter.impl; +package com.sap.sailing.domain.tracking.impl; import java.io.BufferedReader; import java.io.IOException; @@ -28,8 +28,8 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes; -public class TracTracStartTimeUpdateHandler extends UpdateHandler implements StartTimeChangedListener { - private final static Logger logger = Logger.getLogger(TracTracStartTimeUpdateHandler.class.getName()); +public class StartTimeUpdateHandler extends UpdateHandler implements StartTimeChangedListener { + private final static Logger logger = Logger.getLogger(StartTimeUpdateHandler.class.getName()); private final static String ACTION = "update_race_start_time"; private final static String ACTION_START_TRACKING = "start_tracking"; @@ -42,7 +42,7 @@ public class TracTracStartTimeUpdateHandler extends UpdateHandler implements Sta */ private final Regatta regatta; - public TracTracStartTimeUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, + public StartTimeUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId, Regatta regatta) { super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId); this.regatta = regatta; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 2760bf877ca..86501ccd376 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -50,7 +50,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinde import com.sap.sailing.domain.abstractlog.race.impl.RaceLogGateLineOpeningTimeEventImpl; import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; -import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl; +import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDefinedMarkAnalyzer; @@ -2975,7 +2975,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl synchronized (raceStates) { result = raceStates.get(raceLog); if (result == null) { - result = RaceStateImpl.create(raceLogResolver, raceLog); + result = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog); raceStates.put(raceLog, result); } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java similarity index 83% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java index f87d3c8a814..97cd7a4a61d 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.tractracadapter.impl; +package com.sap.sailing.domain.tracking.impl; import java.io.BufferedReader; import java.io.DataOutputStream; @@ -25,21 +25,28 @@ import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; -import com.sap.sailing.domain.tractracadapter.UpdateResponse; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.Helpers; import com.sap.sse.common.Duration; +/** + * An update handler can be registered as a listener on a {@link TrackedRace} and can then propagate information to + * an external service. This can, e.g., be a tracking partner's system that is interested in receiving race status + * updates such as new start times or postponements. + * + * @author Axel Uhl (d043530) + * + */ public class UpdateHandler { - private final static Logger logger = Logger.getLogger(UpdateHandler.class.getName()); private JsonDeserializer updateDeserializer; private final URI baseURI; - private final String tracTracUsername; - private final String tracTracPassword; - private final Serializable tracTracEventId; + private final String username; + private final String password; + private final Serializable eventId; private final Serializable raceId; private final String action; private final boolean active; @@ -53,16 +60,16 @@ public class UpdateHandler { private final static String ResponseCodeForFailure = "FAILURE"; private final static String UpdateUrlTemplate = "%s%s?eventid=%s&raceid=%s&username=%s&password=%s"; - public UpdateHandler(URI updateURI, String action, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) { + public UpdateHandler(URI updateURI, String action, String username, String password, Serializable eventId, Serializable raceId) { this.baseURI = updateURI; this.action = action; - this.tracTracUsername = tracTracUsername; - this.tracTracPassword = tracTracPassword; - this.tracTracEventId = tracTracEventId; + this.username = username; + this.password = password; + this.eventId = eventId; this.raceId = raceId; this.updateDeserializer = new UpdateResponseDeserializer(); if (updateURI != null && !updateURI.toString().equals("")) { - logger.info("Activating TracTrac update handler "+this+" for race with ID "+raceId); + logger.info("Activating update handler "+this+" for race with ID "+raceId); this.active = true; } else { this.active = false; @@ -87,10 +94,10 @@ public class UpdateHandler { */ protected List getDefaultParametersAsNewList() { final List result = new ArrayList<>(); - result.add(new BasicNameValuePair("eventid", tracTracEventId.toString())); + result.add(new BasicNameValuePair("eventid", eventId.toString())); result.add(new BasicNameValuePair("raceid", this.raceId.toString())); - result.add(new BasicNameValuePair("username", tracTracUsername)); - result.add(new BasicNameValuePair("password", tracTracPassword)); + result.add(new BasicNameValuePair("username", username)); + result.add(new BasicNameValuePair("password", password)); return result; } @@ -103,10 +110,10 @@ public class UpdateHandler { String url = String.format(UpdateUrlTemplate, serverUpdateURI.toString(), this.action, - URLEncoder.encode(this.tracTracEventId.toString(), EncodingUtf8), + URLEncoder.encode(this.eventId.toString(), EncodingUtf8), URLEncoder.encode(this.raceId.toString(), EncodingUtf8), - URLEncoder.encode(tracTracUsername, EncodingUtf8), - URLEncoder.encode(tracTracPassword, EncodingUtf8)); + URLEncoder.encode(username, EncodingUtf8), + URLEncoder.encode(password, EncodingUtf8)); for (Entry entry : additionalParameters.entrySet()) { url += String.format("&%s=%s", @@ -123,7 +130,7 @@ public class UpdateHandler { protected void checkAndLogUpdateResponse(HttpURLConnection connection) throws IOException, ParseException { connection.setConnectTimeout(10000/*milliseconds*/); connection.connect(); - BufferedReader reader = getResponseOnUpdateFromTracTrac(connection); + BufferedReader reader = getResponseOnUpdateFromProvider(connection); parseAndLogResponse(reader); } @@ -133,13 +140,13 @@ public class UpdateHandler { JSONObject responseObject = Helpers.toJSONObjectSafe(responseBody); UpdateResponse updateResponse = updateDeserializer.deserialize(responseObject); if (updateResponse.getStatus().equals(ResponseCodeForFailure)) { - logger.severe("Failed to send data to TracTrac, got following response: " + updateResponse.getMessage()); + logger.severe("Failed to send data to provider, got following response: " + updateResponse.getMessage()); } else { - logger.info("Successfully sent data to TracTrac with response: " + updateResponse.getMessage()); + logger.info("Successfully sent data to provider with response: " + updateResponse.getMessage()); } } - private BufferedReader getResponseOnUpdateFromTracTrac(HttpURLConnection connection) throws IOException { + private BufferedReader getResponseOnUpdateFromProvider(HttpURLConnection connection) throws IOException { InputStream inputStream = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); return reader; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/UpdateResponse.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponse.java similarity index 63% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/UpdateResponse.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponse.java index 2a3a9193096..49ea7846f60 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/UpdateResponse.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponse.java @@ -1,7 +1,6 @@ -package com.sap.sailing.domain.tractracadapter; +package com.sap.sailing.domain.tracking.impl; public interface UpdateResponse { - String getStatus(); String getMessage(); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseDeserializer.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseDeserializer.java similarity index 86% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseDeserializer.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseDeserializer.java index 496f22b4f09..75bd2dabe92 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseDeserializer.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseDeserializer.java @@ -1,8 +1,7 @@ -package com.sap.sailing.domain.tractracadapter.impl; +package com.sap.sailing.domain.tracking.impl; import org.json.simple.JSONObject; -import com.sap.sailing.domain.tractracadapter.UpdateResponse; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseImpl.java similarity index 78% rename from java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseImpl.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseImpl.java index 9c1eef86ce1..109414350ec 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateResponseImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateResponseImpl.java @@ -1,6 +1,4 @@ -package com.sap.sailing.domain.tractracadapter.impl; - -import com.sap.sailing.domain.tractracadapter.UpdateResponse; +package com.sap.sailing.domain.tracking.impl; public class UpdateResponseImpl implements UpdateResponse { diff --git a/java/com.sap.sailing.expeditionconnector.common/.project b/java/com.sap.sailing.expeditionconnector.common/.project index 96ef73399d7..255447215e5 100755 --- a/java/com.sap.sailing.expeditionconnector.common/.project +++ b/java/com.sap.sailing.expeditionconnector.common/.project @@ -21,12 +21,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.gwt.ui.test/.project b/java/com.sap.sailing.gwt.ui.test/.project index 92bf08afd13..1b5fe668904 100755 --- a/java/com.sap.sailing.gwt.ui.test/.project +++ b/java/com.sap.sailing.gwt.ui.test/.project @@ -20,16 +20,6 @@ - - com.google.gdt.eclipse.core.webAppProjectValidator - - - - - com.google.gwt.eclipse.core.gwtProjectValidator - - - com.gwtplugins.gdt.eclipse.core.webAppProjectValidator @@ -44,7 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index af633c042ae..74bfa2b601f 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -75,7 +75,7 @@ public class SailingServiceImplMock extends SailingServiceImpl { public RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore logStore, - RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) throws InterruptedException, + RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword) throws InterruptedException, UnknownHostException, IOException, ParseException, Exception { // TODO Auto-generated method stub return null; diff --git a/java/com.sap.sailing.gwt.ui/.project b/java/com.sap.sailing.gwt.ui/.project index 0846e29b8b9..c684e289776 100755 --- a/java/com.sap.sailing.gwt.ui/.project +++ b/java/com.sap.sailing.gwt.ui/.project @@ -20,16 +20,6 @@ - - com.google.gdt.eclipse.core.webAppProjectValidator - - - - - com.google.gwt.eclipse.core.gwtProjectValidator - - - com.gwtplugins.gdt.eclipse.core.webAppProjectValidator @@ -44,7 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature org.eclipse.wst.jsdt.core.jsNature com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT All SDM.launch b/java/com.sap.sailing.gwt.ui/SailingGWT All SDM.launch deleted file mode 100644 index c1e46d3416d..00000000000 --- a/java/com.sap.sailing.gwt.ui/SailingGWT All SDM.launch +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch deleted file mode 100755 index 1fd2a0ae702..00000000000 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch deleted file mode 100644 index 482871f0a61..00000000000 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java index 349d9ba9ac7..b4cbfeaf8cb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java @@ -1,10 +1,11 @@ package com.sap.sailing.gwt.autoplay.client.configs; import com.google.gwt.http.client.UrlBuilder; -import com.google.gwt.user.client.Window; import com.sap.sailing.gwt.autoplay.client.app.AutoPlayClientFactory; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings; import com.sap.sse.gwt.settings.SettingsToUrlSerializer; import com.sap.sse.gwt.settings.UrlBuilderUtil; @@ -21,7 +22,7 @@ public abstract class AutoPlayConfiguration { public void openSettingsDialog(EventDTO selectedEvent, StrippedLeaderboardDTO leaderboard, OnSettingsCallback holder, PerspectiveCompositeSettings settings, AutoPlayContextDefinition apcd, UserService userService) { - Window.alert("This configuration does not have settings"); + Notification.notify("This configuration does not have settings", NotificationType.WARNING); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java index b03b79dc71f..b24e97e74af 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java @@ -137,12 +137,11 @@ public class AutoplayHelper { RaceTimesInfoDTO raceTimes = raceTimesInfos.get(raceIdentifier); boolean notNullInRequiredValues = raceTimes != null && raceTimes.startOfTracking != null && raceTimes.getStartOfRace() != null; - - boolean raceHasNotEndedOrOnlyRecentlyEnded = raceTimes.endOfRace == null + Date endTime = raceTimes.getFinishedTime() != null ? raceTimes.getFinishedTime() : raceTimes.getEndOfRace(); + boolean raceHasNotEndedOrOnlyRecentlyEnded = endTime == null || serverTimeDuringRequest.getTime() - - raceTimes.delayToLiveInMs < raceTimes.endOfRace.getTime() + - raceTimes.delayToLiveInMs < endTime.getTime() + waitTimeAfterRaceEndInMillis; - if (notNullInRequiredValues && raceHasNotEndedOrOnlyRecentlyEnded) { long startTimeInMs = raceTimes.getStartOfRace().getTime(); long startIn = startTimeInMs - serverTimeDuringRequest.getTime() - raceTimes.delayToLiveInMs; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html index fa28a560f40..aa3858de21b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html @@ -6,8 +6,13 @@
July 2018
    -
  • In RaceBoard.html there is a new feature for maneuver loss visualization. There is a setting in the map settings and in the maneuver pop up. +
  • The Maneuver Table now uses the same color coding for competitors as the leaderboard to help + recognize competitor tracks for selected maneuvers more easily.
  • +
  • In RaceBoard.html there is a new feature for maneuver loss visualization. There is a setting in the map settings and in the maneuver pop up.
  • In RaceBoard.html when zooming into a specific time range, the time slider will provide a "reset zoom" button in expanded mode.
  • +
  • Issues with sporadic incorrect zoom/pan states in the Race Board / race viewer have been fixed.
  • +
  • Fixed an issue with the "Winning Lanes" mode for large races that could lead to full tails for + all competitors to be loaded instead of only the ones for the podium competitors.
June 2018
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.gss index f4cc6530815..bbfdcab2e04 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetition.gss @@ -50,6 +50,7 @@ text-align: center; } .regattacompetition_phase_fleetfullwidth .regattacompetition_phase_fleet_race_title { + text-overflow: ellipsis; position: absolute; top: 50%; left: 1.133333333333333em; @@ -178,3 +179,19 @@ white-space: nowrap; text-overflow: ellipsis; } + +.regattacompetition_phase_fleet_race_title_big { + width: 27%; +} + +@media (min-width: 375px) { + .regattacompetition_phase_fleet_race_title_big { + width: 33%; + } +} + +@media (min-width: 700px) { + .regattacompetition_phase_fleet_race_title_big { + width: 40%; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleet.java index 242dee4f366..9a0ba00edf5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleet.java @@ -17,6 +17,9 @@ import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompet public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet { private static RegattaCompetitionFleetUiBinder uiBinder = GWT.create(RegattaCompetitionFleetUiBinder.class); + + private int fleetCount = -1; + private RegattaCompetitionFleetRace raceView; interface RegattaCompetitionFleetUiBinder extends UiBinder { } @@ -32,8 +35,9 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet { @Override public RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, RegattaCompetitionPresenter presenter) { - RegattaCompetitionFleetRace raceView = new RegattaCompetitionFleetRace(race, presenter); + raceView = new RegattaCompetitionFleetRace(race, presenter); raceContainerUi.add(raceView); + updateRaceViewIfReady(); return raceView; } @@ -42,6 +46,8 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet { getElement().getStyle().setWidth(100.0 / fleetCount, Unit.PCT); setStyleName(local_res.css().regattacompetition_phase_fleetfullwidth(), fleetCount < 2); setStyleName(local_res.css().regattacompetition_phase_fleetcompact(), fleetCount > 4); + this.fleetCount = fleetCount; + updateRaceViewIfReady(); } @Override @@ -64,5 +70,10 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet { protected Element getFleetCornerUiElement() { return fleetCornerUi; } - + + private void updateRaceViewIfReady() { + if (raceView != null && fleetCount > 1) { + raceView.removeBigRaceTitleCSS(); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.java index f58842a8fb9..69c46429724 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.java @@ -65,5 +65,9 @@ public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleet protected String getRaceUntrackedStyleName() { return local_res.css().regattacompetition_phase_fleet_raceuntracked(); } + + public void removeBigRaceTitleCSS() { + raceNameUi.removeClassName(local_res.css().regattacompetition_phase_fleet_race_title_big()); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.ui.xml index ee0c5602bc6..f9f68841252 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionFleetRace.ui.xml @@ -1,15 +1,15 @@ - - - + xmlns:mp="urn:import:com.sap.sailing.gwt.home.mobile.partials" xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> + + +
-
-
-
-
-
+
+
+
+
+
-
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionResources.java index dd113f3c1de..c669e066169 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/regattacompetition/RegattaCompetitionResources.java @@ -33,5 +33,6 @@ public interface RegattaCompetitionResources extends SharedHomeResources { String regattacompetition_phase_fleet_raceplanned(); String regattacompetition_phase_fleet_race_state(); String regattacompetition_phase_fleet_race_date(); + String regattacompetition_phase_fleet_race_title_big(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java index 0cda630cbc8..2e313562472 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java @@ -104,7 +104,7 @@ public class RaceContext { this.fleet = fleet; trackedRace = raceColumn.getTrackedRace(fleet); raceLog = raceColumn.getRaceLog(fleet); - state = (raceLog == null) ? null : ReadonlyRaceStateImpl.create(raceLogResolver, raceLog); + state = (raceLog == null) ? null : ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog); } private boolean isShowFleetData() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/profile/preferences/UserPreferencesPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/profile/preferences/UserPreferencesPresenter.java index c21bf0daa38..8dbab241479 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/profile/preferences/UserPreferencesPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/profile/preferences/UserPreferencesPresenter.java @@ -1,6 +1,5 @@ package com.sap.sailing.gwt.home.shared.places.user.profile.preferences; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO; import com.sap.sailing.gwt.home.communication.user.profile.CompetitorSuggestionResult; @@ -17,6 +16,8 @@ import com.sap.sailing.gwt.home.shared.partials.multiselection.AbstractSuggested import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionBoatClassDataProvider; import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionCompetitorDataProvider; import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.dispatch.shared.commands.VoidResult; /** @@ -95,7 +96,7 @@ public class UserPreferencesPresenter() { @Override public void onFailure(Throwable caught) { - Window.alert("Error while loading competitor suggestion"); + Notification.notify("Error while loading competitor suggestion", NotificationType.ERROR); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java index de81d943467..091642b1f28 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java @@ -6,7 +6,6 @@ import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.http.client.UrlBuilder; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; @@ -21,6 +20,8 @@ import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaOverviewContex import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaRaceStatesSettings; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.settings.SettingsToUrlSerializer; public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint { @@ -60,7 +61,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint { .deserializeFromCurrentLocation(new RegattaOverviewContextDefinition()); if (regattaOverviewContextDefinition.getEvent() == null) { - Window.alert("Missing parameter"); + Notification.notify("Missing parameter", NotificationType.ERROR); return; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java index e166d36801b..74232f6c701 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/MultiRaceLeaderboardSettings.java @@ -63,6 +63,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings { newSettings.showAddedScores.setValue(this.isShowAddedScores()); newSettings.showCompetitorShortNameColumn.setValue(this.isShowCompetitorShortNameColumn()); newSettings.showCompetitorFullNameColumn.setValue(this.isShowCompetitorFullNameColumn()); + newSettings.showCompetitorBoatInfoColumn.setValue(this.isShowCompetitorBoatInfoColumn()); newSettings.isShowCompetitorNationality.setValue(this.isShowCompetitorNationality()); newSettings.namesOfRaceColumnsToShow.setValues(this.getNamesOfRaceColumnsToShow()); newSettings.namesOfRaceColumnsToShow.setDefaultValues(namesOfRaceColumns); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java index 7b153fe21e3..84d310a3cfa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java @@ -20,7 +20,6 @@ public class GetBoatPositionsAction extends AbstractGetMapRelatedDataAction callback) { Map fromByCompetitorIdAsString = new HashMap(); - System.out.println(fromByCompetitorIdAsString); for (Map.Entry fromEntry : getFrom().entrySet()) { fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java index d02f523e933..df4261fa983 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java @@ -22,6 +22,8 @@ import com.sap.sailing.gwt.ui.shared.RaceRecordDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel { protected final TrackedRacesListComposite trackedRacesListComposite; @@ -121,7 +123,7 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel builder.append(")"); builder.append("\n"); } - Window.alert(builder.toString()); + Notification.notify(builder.toString(), NotificationType.ERROR); result = false; } else { result = true; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java index 88a48717b39..31862a1dbe1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java @@ -8,7 +8,6 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; @@ -25,6 +24,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; public abstract class AbstractFileImportWidget extends Composite { @@ -89,7 +90,7 @@ public abstract class AbstractFileImportWidget extends Composite { void onFileImportComplete(SubmitCompleteEvent event) { SensorDataImportResponse importResponse = SensorDataImportResponse.parse(event.getResults()); if (importResponse == null) { - Window.alert(StringMessages.INSTANCE.unexpectedErrorDuringFileImport()); + Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else { SensorDataImportResultsDialog.showResults(importResponse); if (importResponse.didSucceedImportingAnyFile()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java index 8a1047d9d48..7af822f63b5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -51,6 +51,8 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Triple; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; @@ -281,7 +283,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement refreshRaceLog(column, fleet, false); } } - Window.alert(stringMessages.raceLogReloaded()); + Notification.notify(stringMessages.raceLogReloaded(), NotificationType.ERROR); } }); vPanel.add(reloadAllRaceLogs); @@ -430,7 +432,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement @Override public void onSuccess(Void result) { if (showAlerts) { - Window.alert(stringMessages.raceLogReloaded()); + Notification.notify(stringMessages.raceLogReloaded(), NotificationType.SUCCESS); } } })); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java index 808ebdc3e2a..0cc890bac1d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java @@ -13,7 +13,6 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Label; import com.sap.sailing.domain.common.dto.BoatDTO; @@ -24,6 +23,8 @@ import com.sap.sse.common.Color; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -282,7 +283,7 @@ public class BoatTableWrapper> exte @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyAllowedBoatReset(boats.toString())); + Notification.notify(stringMessages.successfullyAllowedBoatReset(boats.toString()), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorInvitationHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorInvitationHelper.java index 7e14ff019c7..cf625080d91 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorInvitationHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorInvitationHelper.java @@ -3,7 +3,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Set; import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -11,6 +10,8 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sse.common.Util.Pair; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; public class CompetitorInvitationHelper { @@ -27,13 +28,13 @@ public class CompetitorInvitationHelper { public void inviteCompetitors(Set competitors, String leaderboardName) { if (competitors.size() == 0){ - Window.alert(stringMessages.selectAtLeastOneCompetitorForInvitation()); + Notification.notify(stringMessages.selectAtLeastOneCompetitorForInvitation(), NotificationType.ERROR); } else { boolean emailProvidedForAll = isEmailProvidedForAll(competitors); if (emailProvidedForAll) { openChooseEventDialogAndSendMails(competitors, leaderboardName); } else { - Window.alert(stringMessages.notAllCompetitorsProvideEmail()); + Notification.notify(stringMessages.notAllCompetitorsProvideEmail(), NotificationType.ERROR); } } } @@ -61,12 +62,12 @@ public class CompetitorInvitationHelper { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.sendingMailsFailed() + caught.getMessage()); + Notification.notify(stringMessages.sendingMailsFailed() + caught.getMessage(), NotificationType.ERROR); } @Override public void onSuccess(Void result) { - Window.alert(stringMessages.sendingMailsSuccessful()); + Notification.notify(stringMessages.sendingMailsSuccessful(), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 36d8d1ee111..af5ce2fc3cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -14,7 +14,6 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Label; import com.google.gwt.view.client.CellPreviewEvent; @@ -32,6 +31,8 @@ import com.sap.sse.common.Color; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -479,7 +480,7 @@ public class CompetitorTableWrapper competitors) { if (competitors.isEmpty()) { - Window.alert(stringMessages.importCanceledNoCompetitorAdded()); + Notification.notify(stringMessages.importCanceledNoCompetitorAdded(), NotificationType.ERROR); } else { sailingService.setCompetitorRegistrationsInRegattaLog(leaderboard.getName(), competitors, new AsyncCallback() { @@ -337,7 +338,7 @@ public class ExpeditionAllInOneAfterImportHandler { @Override public final void cancel() { - Window.alert(stringMessages.importCanceledByUser()); + Notification.notify(stringMessages.importCanceledByUser(), NotificationType.WARNING); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java index eeee4a424a6..cc04ef0440b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java @@ -7,7 +7,6 @@ import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; @@ -36,6 +35,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; @@ -153,7 +154,7 @@ public class ExpeditionAllInOneImportPanel extends Composite implements Regattas busyIndicator.setBusy(false); final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(event.getResults()); if (response == null) { - Window.alert(StringMessages.INSTANCE.unexpectedErrorDuringFileImport()); + Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else if (response.hasEventId()) { new ExpeditionAllInOneAfterImportHandler(response.getEventId(), response.getRegattaName(), response.getLeaderboardName(), response.getLeaderboardGroupName(), response.getRaceEntries(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java index 57448b3ee20..01601beec40 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java @@ -40,6 +40,8 @@ import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; @@ -256,10 +258,10 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override public void onSuccess(Boolean result) { if (result) { - Window.alert(stringMessages.successfullyAuthorizedAccessToIgtimiUser(editedObject.geteMail())); + Notification.notify(stringMessages.successfullyAuthorizedAccessToIgtimiUser(editedObject.geteMail()), NotificationType.SUCCESS); updateAllAccounts(sailingService, filterAccountsPanel, stringMessages, errorReporter); } else { - Window.alert(stringMessages.couldNotAuthorizedAccessToIgtimiUser(editedObject.geteMail())); + Notification.notify(stringMessages.couldNotAuthorizedAccessToIgtimiUser(editedObject.geteMail()), NotificationType.ERROR); } } }); @@ -326,7 +328,7 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyRemoveIgtimiAccount(eMailOfAccountToRemove)); + Notification.notify(stringMessages.successfullyRemoveIgtimiAccount(eMailOfAccountToRemove), NotificationType.INFO); removeFrom.getList().remove(eMailOfAccountToRemove); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index f215666b1b3..5d71a179735 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -72,6 +72,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.shared.components.LinkWithSettingsGenerator; @@ -380,7 +382,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { otherExistingLeaderboard.addAll(availableLeaderboardList); otherExistingLeaderboard.remove(leaderboardDTO); if (leaderboardDTO.type.isMetaLeaderboard()) { - Window.alert(stringMessages.metaLeaderboardCannotBeChanged()); + Notification.notify(stringMessages.metaLeaderboardCannotBeChanged(), NotificationType.ERROR); } else { AbstractLeaderboardDialog dialog; switch (leaderboardDTO.type) { @@ -437,7 +439,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { leaderboardDTO.scoringScheme)); break; default: - Window.alert(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name())); + Notification.notify(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name()), NotificationType.ERROR); } } } @@ -531,7 +533,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onClick(ClickEvent event) { if (getSelectedLeaderboard().type.isRegattaLeaderboard()) { - Window.alert(stringMessages.cannotAddRacesToRegattaLeaderboardButOnlyToRegatta()); + Notification.notify(stringMessages.cannotAddRacesToRegattaLeaderboardButOnlyToRegatta(), NotificationType.ERROR); } else { addRaceColumnsToLeaderboard(); } @@ -653,7 +655,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onSuccess(Boolean result) { if (!result) { - Window.alert(stringMessages.failedToSetNewStartTime()); + Notification.notify(stringMessages.failedToSetNewStartTime(), NotificationType.ERROR); } } }); @@ -680,7 +682,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onSuccess(Pair result) { if (!result.getA() || !result.getB()) { - Window.alert(stringMessages.failedToSetNewFinishingAndFinishTime()); + Notification.notify(stringMessages.failedToSetNewFinishingAndFinishTime(), NotificationType.ERROR); } } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index fad9c27a9a8..bd427552d62 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -54,6 +54,8 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.URLEncoder; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; @@ -986,7 +988,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements setDescriptionEditable(false); updateGroup(selectedGroup); } else { - Window.alert(stringMessages.pleaseEnterNonEmptyDescription() + "."); + Notification.notify(stringMessages.pleaseEnterNonEmptyDescription() + ".", NotificationType.ERROR); descriptionTextArea.setText(selectedGroup.description); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java index c960cb2ffc9..1cb7c2cc2db 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java @@ -59,6 +59,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; @@ -139,7 +141,7 @@ public class MediaPanel extends FlowPanel implements MediaTracksRefresher { public void onClick(ClickEvent event) { Set selected = refreshableSelectionModel.getSelectedSet(); if (selected.isEmpty()) { - Window.alert(stringMessages.noSelection()); + Notification.notify(stringMessages.noSelection(), NotificationType.ERROR); } else { new MultiURLChangeDialog(mediaService, stringMessages, selected, errorReporter, new Runnable() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationDialog.java index 50d8c0e7c2e..645021bcc64 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationDialog.java @@ -24,6 +24,8 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class PairingListCreationDialog extends DataEntryDialog { @@ -176,7 +178,7 @@ public class PairingListCreationDialog extends DataEntryDialog() { @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyFilledRaceLogsFromPairingList()); + Notification.notify(stringMessages.successfullyFilledRaceLogsFromPairingList(), NotificationType.SUCCESS); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 46b143863fc..2023058b4e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -13,7 +13,6 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.HasVerticalAlignment; @@ -31,6 +30,8 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.IntegerBox; import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; @@ -418,7 +419,7 @@ public class SeriesEditDialog extends DataEntryDialog { } validateAndUpdate(); } else { - Window.alert(getStringMessages().pleaseSelectASeriesFirst()); + Notification.notify(getStringMessages().pleaseSelectASeriesFirst(), NotificationType.ERROR); } } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java index d6f2baa6b49..20e494bd823 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java @@ -67,6 +67,8 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -233,7 +235,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC } }).show(); } else { - Window.alert(stringMessages.canNotRegisterBoats()); + Notification.notify(stringMessages.canNotRegisterBoats(), NotificationType.ERROR); } } else if (RaceLogTrackingEventManagementImagesBarCell.ACTION_MAP_DEVICES.equals(value)) { new RegattaLogTrackingDeviceMappingsDialog(sailingService, stringMessages, errorReporter, @@ -755,7 +757,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Void result) { - Window.alert(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks()); + Notification.notify(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks(), NotificationType.SUCCESS); } })); } @@ -845,7 +847,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Boolean result) { if (!result) { - Window.alert(stringMessages.failedToSetNewStartTime()); + Notification.notify(stringMessages.failedToSetNewStartTime(), NotificationType.ERROR); } else { trackedRacesListComposite.regattaRefresher.fillRegattas(); } @@ -872,7 +874,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Pair result) { if (!result.getA() || !result.getB()) { - Window.alert(stringMessages.failedToSetNewFinishingAndFinishTime()); + Notification.notify(stringMessages.failedToSetNewFinishingAndFinishTime(), NotificationType.ERROR); } else { trackedRacesListComposite.regattaRefresher.fillRegattas(); } @@ -942,12 +944,12 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC getLocaleInfo(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.sendingMailsFailed() + caught.getMessage()); + Notification.notify(stringMessages.sendingMailsFailed() + caught.getMessage(), NotificationType.ERROR); } @Override public void onSuccess(Void result) { - Window.alert(stringMessages.sendingMailsSuccessful()); + Notification.notify(stringMessages.sendingMailsSuccessful(), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java index 99d79268374..e657ca630db 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java @@ -15,7 +15,6 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CaptionPanel; @@ -48,6 +47,8 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -475,7 +476,7 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat @Override public void onSuccess(Void result) { regattaRefresher.fillRegattas(); - Window.alert(stringMessages.successfullyCreatedRegattas()); + Notification.notify(stringMessages.successfullyCreatedRegattas(), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index 54a122e1cce..8f4fd9b38d0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -25,6 +25,7 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.IntegerBox; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.PasswordTextBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; @@ -62,6 +63,9 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane private final ListBox previousConfigurationsComboBox; private final TextBox eventIdBox; private final TextBox jsonUrlBox; + private final TextBox updateURLBox; + private final TextBox updateUsernameBox; + private final PasswordTextBox updatePasswordBox; private final TextBox hostnameTextbox; private final IntegerBox portIntegerbox; private final List availableSwissTimingRaces = new ArrayList(); @@ -75,22 +79,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane RegattaRefresher regattaRefresher, StringMessages stringConstants) { super(sailingService, regattaRefresher, errorReporter, true, stringConstants); this.errorReporter = errorReporter; - VerticalPanel mainPanel = new VerticalPanel(); this.setWidget(mainPanel); mainPanel.setWidth("100%"); - CaptionPanel captionPanelConnections = new CaptionPanel(stringConstants.connections()); mainPanel.add(captionPanelConnections); - VerticalPanel verticalPanel = new VerticalPanel(); - captionPanelConnections.setContentWidget(verticalPanel); captionPanelConnections.setStyleName("bold"); - - Grid connectionsGrid = new Grid(6, 2); + Grid connectionsGrid = new Grid(9, 2); verticalPanel.add(connectionsGrid); - previousConfigurations = new HashMap(); previousConfigurationsComboBox = new ListBox(); connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox); @@ -107,10 +105,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane } }); fillConfigurations(); - - jsonUrlBox = new TextBox(); - jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM); - connectionsGrid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":")); connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox); @@ -125,6 +119,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane } }); + jsonUrlBox = new TextBox(); + jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM); connectionsGrid.setWidget(2, 0, new Label(stringMessages.manage2SailEventURLBox() + ":")); connectionsGrid.setWidget(2, 1, jsonUrlBox); jsonUrlBox.addChangeHandler(event -> { @@ -142,8 +138,23 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane connectionsGrid.setWidget(4, 0, new Label(stringMessages.manage2SailPort() + ":")); connectionsGrid.setWidget(4, 1, portIntegerbox); + updateURLBox = new TextBox(); + updateURLBox.getElement().getStyle().setWidth(50, Unit.EM); + connectionsGrid.setWidget(5, 0, new Label(stringMessages.swissTimingUpdateURL() + ":")); + connectionsGrid.setWidget(5, 1, updateURLBox); + + updateUsernameBox = new TextBox(); + updateUsernameBox.getElement().getStyle().setWidth(50, Unit.EM); + connectionsGrid.setWidget(6, 0, new Label(stringMessages.swissTimingUpdateUsername() + ":")); + connectionsGrid.setWidget(6, 1, updateUsernameBox); + + updatePasswordBox = new PasswordTextBox(); + updatePasswordBox.getElement().getStyle().setWidth(50, Unit.EM); + connectionsGrid.setWidget(7, 0, new Label(stringMessages.swissTimingUpdatePassword() + ":")); + connectionsGrid.setWidget(7, 1, updatePasswordBox); + Button btnListRaces = new Button(stringConstants.listRaces()); - connectionsGrid.setWidget(5, 1, btnListRaces); + connectionsGrid.setWidget(8, 1, btnListRaces); btnListRaces.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -459,6 +470,9 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane private void fillRaces(final SailingServiceAsync sailingService) { final String jsonUrl = jsonUrlBox.getValue(); + final String updateURL = updateURLBox.getValue(); + final String updateUsername = updateUsernameBox.getValue(); + final String updatePassword = updatePasswordBox.getValue(); sailingService.getRacesOfSwissTimingEvent(jsonUrl, new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -469,21 +483,18 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane @Override public void onSuccess(final SwissTimingEventRecordDTO result) { availableSwissTimingRaces.clear(); - if (result != null) + if (result != null) { availableSwissTimingRaces.addAll(result.races); - + } raceList.getList().clear(); raceList.getList().addAll(availableSwissTimingRaces); - filterablePanelEvents.getTextBox().setText(null); filterablePanelEvents.updateAll(result.races); - // store a successful configuration in the database for later retrieval final String hostname = result.trackingDataHost; final Integer port = result.trackingDataPort; final String configName = result.eventName; - - sailingService.storeSwissTimingConfiguration(configName, jsonUrl, hostname, port, + sailingService.storeSwissTimingConfiguration(configName, jsonUrl, hostname, port, updateURL, updateUsername, updatePassword, new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -495,10 +506,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane public void onSuccess(Void voidResult) { // refresh list of previous configurations SwissTimingConfigurationDTO stConfig = new SwissTimingConfigurationDTO(configName, jsonUrl, - hostname, port); + hostname, port, updateURL, updateUsername, updatePassword); if (previousConfigurations.put(stConfig.getName(), stConfig) == null) { previousConfigurationsComboBox.addItem(stConfig.getName()); } + for (int i=0; i selectedRaces = new ArrayList(); for (final SwissTimingRaceRecordDTO race : this.raceList.getList()) { if (raceTable.getSelectionModel().isSelected(race)) { @@ -526,7 +546,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane sailingService.trackWithSwissTiming( /* regattaToAddTo */ regattaIdentifier, selectedRaces, hostname, port, trackWind, correctWindByDeclination, - useInternalMarkPassingAlgorithm, new AsyncCallback() { + useInternalMarkPassingAlgorithm, updateURL, updateUsername, updatePassword, new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error trying to register races " + selectedRaces + " for tracking: " @@ -549,6 +569,9 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane hostnameTextbox.setValue(stConfig.getHostname()); portIntegerbox.setValue(stConfig.getPort()); jsonUrlBox.setValue(stConfig.getJsonURL()); + updateURLBox.setValue(stConfig.getUpdateURL()); + updateUsernameBox.setValue(stConfig.getUpdateUsername()); + updatePasswordBox.setValue(stConfig.getUpdatePassword()); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 5b7069b09a8..9864e5ac78b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -28,6 +28,7 @@ import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.PasswordTextBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; @@ -79,7 +80,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { private TextBox jsonURLTextBox; private TextBox tracTracUpdateURITextBox; private TextBox tractracUsernameTextBox; - private TextBox tractracPasswordTextBox; + private PasswordTextBox tractracPasswordTextBox; private Label loadingMessageLabel; private LabeledAbstractFilterablePanel racesFilterablePanel; @@ -199,7 +200,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { grid.setWidget(6, 1, tractracUsernameTextBox); // TracTrac Password - tractracPasswordTextBox = new TextBox(); + tractracPasswordTextBox = new PasswordTextBox(); tractracPasswordTextBox.ensureDebugId("TracTracPasswordTextBox"); tractracPasswordTextBox.setVisibleLength(40); @@ -222,10 +223,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { } }); grid.setWidget(9, 1, listRacesButton); - loadingMessageLabel = new Label(); grid.setWidget(9, 2, loadingMessageLabel); - return connectionsPanel; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java index 1432949153e..d7c70fc7ed6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java @@ -61,15 +61,16 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel { controlsPanel.add(setStartTimeButton); selectedRaceContentPanel.add(controlsPanel); - raceDataGrid = new Grid(6,2); + raceDataGrid = new Grid(7, 2); this.selectedRaceContentPanel.add(raceDataGrid); raceDataGrid.setText(0, 0, stringMessages.startTime()+":"); raceDataGrid.setText(1, 0, stringMessages.endTime()+":"); - raceDataGrid.setText(2, 0, stringMessages.duration()+":"); - raceDataGrid.setText(3, 0, stringMessages.startOfTracking()+":"); - raceDataGrid.setText(4, 0, stringMessages.endOfTracking()+":"); - raceDataGrid.setText(5, 0, stringMessages.delayForLiveMode()+" (ms):"); + raceDataGrid.setText(2, 0, stringMessages.finishingTime()+":"); + raceDataGrid.setText(3, 0, stringMessages.duration()+":"); + raceDataGrid.setText(4, 0, stringMessages.startOfTracking()+":"); + raceDataGrid.setText(5, 0, stringMessages.endOfTracking()+":"); + raceDataGrid.setText(6, 0, stringMessages.delayForLiveMode()+" (ms):"); } @Override @@ -87,26 +88,32 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel { } else { raceDataGrid.setText(1, 1, ""); } - if(selectedRaceDTO.startOfRace != null && selectedRaceDTO.endOfRace != null) { - Date duration = new Date(selectedRaceDTO.endOfRace.getTime() - selectedRaceDTO.startOfRace.getTime()); - raceDataGrid.setText(2, 1, durationFormatter.render(duration)); + if (selectedRaceDTO.raceFinishedTime != null) { + raceDataGrid.setText(2, 1, dateFormatter.render(selectedRaceDTO.raceFinishedTime) + " " + + timeFormatter.render(selectedRaceDTO.raceFinishedTime)); } else { raceDataGrid.setText(2, 1, ""); } - if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.startOfTracking != null) { - raceDataGrid.setText(3, 1, dateFormatter.render(selectedRaceDTO.trackedRace.startOfTracking) + " " - + timeFormatter.render(selectedRaceDTO.trackedRace.startOfTracking)); + if(selectedRaceDTO.startOfRace != null && selectedRaceDTO.endOfRace != null) { + Date duration = new Date(selectedRaceDTO.endOfRace.getTime() - selectedRaceDTO.startOfRace.getTime()); + raceDataGrid.setText(3, 1, durationFormatter.render(duration)); } else { raceDataGrid.setText(3, 1, ""); } - if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.endOfTracking != null) { - raceDataGrid.setText(4, 1, dateFormatter.render(selectedRaceDTO.trackedRace.endOfTracking) + " " - + timeFormatter.render(selectedRaceDTO.trackedRace.endOfTracking)); + if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.startOfTracking != null) { + raceDataGrid.setText(4, 1, dateFormatter.render(selectedRaceDTO.trackedRace.startOfTracking) + " " + + timeFormatter.render(selectedRaceDTO.trackedRace.startOfTracking)); } else { raceDataGrid.setText(4, 1, ""); } + if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.endOfTracking != null) { + raceDataGrid.setText(5, 1, dateFormatter.render(selectedRaceDTO.trackedRace.endOfTracking) + " " + + timeFormatter.render(selectedRaceDTO.trackedRace.endOfTracking)); + } else { + raceDataGrid.setText(5, 1, ""); + } if (selectedRaceDTO.trackedRace != null) { - raceDataGrid.setText(5, 1, "" + selectedRaceDTO.trackedRace.delayToLiveInMs); + raceDataGrid.setText(6, 1, "" + selectedRaceDTO.trackedRace.delayToLiveInMs); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiURLChangeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiURLChangeDialog.java index 4da6830a727..521754bde13 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiURLChangeDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiURLChangeDialog.java @@ -18,7 +18,6 @@ import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; @@ -34,6 +33,8 @@ import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.media.TimeFormatUtil; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * This dialog allows to change multiple urls for mediatracks at once. It will determine the longest common prefix, that @@ -135,7 +136,7 @@ public class MultiURLChangeDialog extends DialogBox { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.multiUrlChangeCannotSave()); + Notification.notify(stringMessages.multiUrlChangeCannotSave(), NotificationType.ERROR); } }); } @@ -150,7 +151,7 @@ public class MultiURLChangeDialog extends DialogBox { final String maxPrefixForAll = maxPrefixForAll(); if (maxPrefixForAll.length() == 0) { - Window.alert(this.stringMessages.multiUrlNoPrefixWarning()); + Notification.notify(this.stringMessages.multiUrlNoPrefixWarning(), NotificationType.WARNING); } replacePartIn.setText(maxPrefixForAll); updateUI(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiVideoDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiVideoDialog.java index 0304382dc9b..8238cda8f2a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiVideoDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/multivideo/MultiVideoDialog.java @@ -24,7 +24,6 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.typedarrays.shared.Int8Array; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; @@ -59,6 +58,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.media.MimeType; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput; import com.sap.sse.gwt.client.controls.datetime.DateTimeInput.Accuracy; @@ -617,7 +618,7 @@ public class MultiVideoDialog extends DialogBox { @Override public void noResult() { - Window.alert(stringMessages.serverURLInvalid()); + Notification.notify(stringMessages.serverURLInvalid(), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 1530fdfee6e..3abada9ea35 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -146,8 +146,9 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception; - void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, - boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception; + void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, + int port, boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, + String updateURL, String updateUsername, String updatePassword) throws Exception; void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm); @@ -286,7 +287,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS SwissTimingEventRecordDTO getRacesOfSwissTimingEvent(String eventJsonURL) throws Exception; - void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port); + void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword); String[] getCountryCodes(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index b3bbe02e0e0..dccdc5a4e1a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -163,7 +163,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, boolean trackWind, boolean correctWindByDeclination, - boolean useInternalMarkPassingAlgorithm, AsyncCallback asyncCallback); + boolean useInternalMarkPassingAlgorithm, String updateURL, String updateUsername, String updatePassword, AsyncCallback asyncCallback); void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, @@ -334,7 +334,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void getRacesOfSwissTimingEvent(String eventJsonUrl, AsyncCallback asyncCallback); void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port, - AsyncCallback asyncCallback); + String updateURL, String updateUsername, String updatePassword, AsyncCallback asyncCallback); void getCountryCodes(AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 38a6ea7f575..d427d79208e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -2093,6 +2093,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String manage2SailEventIdBoxTooltip(); String manage2SailPort(); String windFinderSpotCollectionsList(); + String swissTimingUpdateURL(); + String swissTimingUpdateUsername(); + String swissTimingUpdatePassword(); String tagging(); String tagFilterExplanation(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 8f80a3c3ca1..fc97390e838 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -2087,6 +2087,9 @@ manage2SailEventIdBox=Manage2Sail Event ID manage2SailEventURLBox=Manage2Sail EventURL (json) manage2SailEventIdBoxTooltip=Event ID or a URL that contains the event ID from Manage2Sail manage2SailPort=Port +swissTimingUpdateURL=Update URL (e.g., for start time and course change feedback) +swissTimingUpdateUsername=Username for sending updates +swissTimingUpdatePassword=Password for sending updates tagging=Tags tagFilterExplanation=The filter will show all tags matching the criterias: username=User name diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 48ef82f8c55..50f122fc377 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -2081,4 +2081,7 @@ gender=Geschlecht manage2SailEventId=Manage2Sail Event ID manage2SailEventURL=Manage2Sail Event URL (json) manage2SailEventIdBoxTooltip=Event ID von Manage 2 Sail oder URL die die Event ID beinhaltet -manage2SailPort=Port \ No newline at end of file +manage2SailPort=Port +swissTimingUpdateURL=Update URL (z.B. für Startzeit-Übertragung) +swissTimingUpdateUsername=Benutzername zum Versenden der Updates +swissTimingUpdatePassword=Passwort zum Versenden der Updates \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties index e036c02256c..6cddd2a64fb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Puntuación alta, el ganador obtien scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Calificación en puntos. El ganador obtiene 12 puntos; el segundo, 11 puntos, etc. Si hay un empate, se resuelve a favor del competidor que tiene la mayor puntuación. Si aún persiste el empate, se resuelve mediante la posición obtenida en la última carrera puntuada. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Puntuación alta ESS 2017, el ganador obtiene 12 puntos (u 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Calificación en puntos. El ganador obtiene 12 puntos; el segundo, 11 puntos, etc. Si hay un empate, se resuelve a favor del competidor que tiene la mayor puntuación. Si aún persiste el empate, se resuelve mediante la posición obtenida en la última carrera puntuada. +scoringSchemeLowPointSystemFirstTwoWins=Sistema de puntuación baja, primero con dos victorias en medallas es ganador +scoringSchemeLowPointSystemFirstTwoWinsDescription=Sistema de puntuación baja. El primero en la serie de medallas que gane dos carreras, gana la serie de medallas. Ser el mejor en la serie premedallas cuenta como una victoria para este. scoringSystem=Sistema de puntuación createFlexibleLeaderboard=Crear tabla de clasificación flexible createRegattaLeaderboard=Crear tabla de clasificación de regata @@ -1163,7 +1165,8 @@ racesScoredTooltip=Número de carreras que ha completado un competidor o en las averageNumberOfOperationsPerMessage=Número promedio de operaciones por mensaje showUncorrectedTotalPoints=Mostrar puntos totales no corregidos setStartTimeReceived=Fijar hora de inicio recibida -setStartTimeReceivedDescription=Fija la HoraInicioRecibida del RegistroRastreado seleccionado que no es persistente. Esto significa que el nuevo valor se ignorará después del reinicio del servidor. +setStartTimeReceivedDescription=Fija la startTimeReceived de la TrackedRace seleccionada en el valor proporcionado. Deje el valor en blanco para eliminar la startTimeReceived actual. +setStartTimeReceivedNotice=Esta modificación no es persistente, es decir, el valor nuevo se olvidará cuando se reinicie el servidor. lastScoreCorrectionsTime=Última hora de corrección de puntuación lastScoreCorrectionsComment=Último comentario de corrección de puntuación setTimeToNow=Fijar la hora a "ahora" @@ -1278,8 +1281,8 @@ showCompetitorBoatColumn=Embarcación del competidor showCompetitorShortNameColumn=Denominación breve de competidor showCompetitorShortNameColumnTooltip=Muestra el nombre breve más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorFullNameColumn=Nombre completo del competidor -showCompetitorNationalityColumn=Siempre mostrar la nacionalidad del competidor -showCompetitorNationalityColumnTooltip=Muestra ambos, las banderas de país así como las imágenes de competidor, si están disponibles. +showCompetitorNationalityColumn=Mostrar adicionalmente la nacionalidad del competidor +showCompetitorNationalityColumnTooltip=Mostrar las banderas de nacionalidad junto con las imágenes del competidor si están disponibles loadingDimensionValues=Cargando valores de dimensión inviteBuoyTenders=Invitar a balizadores orMultipleEmails=o varios correos electrónicos separados por una coma @@ -1971,6 +1974,7 @@ windFinderWindSourceTypeTooltip=Viento medido de uno o más puntos de www.windfi windFinder=Windfinder enterTagsForTheVideo=Indique las etiquetas para el vídeo enterIdOfWindFinderReviewedSpotCollection=Indique el ID de una colección de puntos de Windfinder revisada, por ejemplo "schilksee" +windFinderSpotCollectionsList=Colecciones de puntos de WindFinder enterTagsForTheImage=Indique las etiquetas para la imagen unableToResolveWindFinderSpotId=Imposible resolver el punto Windfinder con ID {0}: {1} windFinderWeatherData=Datos meteorológicos @@ -2055,6 +2059,9 @@ multiVideoIdle=Cola de trabajo inactiva multiVideoDoNoAdd=No añadir multiVideoOffsetInput=Offset de vídeo global en milisegundos: multiVideoDescription=El servidor web debe proporcionar una lista de índices (subcarpetas compatibles si están indexadas) para permitir la detección de ficheros. Tras un análisis inicial de los metadatos contenidos en los ficheros mp4, los vídeos que deben añadirse, deben seleccionarse mediante la columna de la casilla de selección a la izquierda. El botón "Añadir audio/vídeo" creará pistas multimedia para todos los ficheros seleccionados y los añadirá a todas las carreras seleccionadas en la columna de la derecha. +jibeAngle=Ángulo de trasluchada +tackAngle=Ángulo de bordo +maneuverAngle=Ángulo de maniobra multiUrlChangeMediaTrack=Ajustar URLs de rastreo múltiple de medios multiUrlChangeReplace=Reemplazar por multiUrlChangeFind=Buscar @@ -2063,5 +2070,16 @@ multiUrlChangeSave=Grabar modificaciones de URL multiUrlChangeNewURL=URL nuevo multiUrlNoPrefixWarning=No existe ningún prefijo común, esto significa que normalmente no todos los vídeos seleccionados se alojan actualmente en la misma ubicación. Proceda bajo su propio riesgo. multiUrlChangeExplain=Este diálogo reemplazará en masa las partes comunes al inicio de los URLs del rastreo de medios. Asegúrate de que todas las URL empiezan con el mismo prefijo. Además, eche un vistazo a la nueva columna del URL, y pruebe los URLs resultantes antes de pulsar Grabar. +resetZoom=Reinicializar zoom lastEvent=Último evento: {0} teaserOverallLinkToolTip=Para visualizar las series generales, haga clic en la esquina amarilla +middleManeuverAngle=Ángulo medio de maniobra +extrapolatedManeuverStartPosition=Posición de inicio de maniobra extrapolada +projectedExtrapolatedManeuverStartPosition=Proyección de posición de inicio de maniobra extrapolada en línea de ángulo medio de maniobra +projectedManeuverEndPosition=Proyección de posición de fin de maniobra en línea de ángulo medio de maniobra +bearingAtManeuverEndPosition=Rumbo en posición de fin de maniobra +gender=Sexo +manage2SailEventIdBox=ID de evento Manage2Sail +manage2SailEventURLBox=URL de evento Manage2Sail (json) +manage2SailEventIdBoxTooltip=ID de evento o URL que contiene el ID de evento de Manage2Sail +manage2SailPort=Puerto diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties index 547e5d5823c..4eceacdde57 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Système de points avec bonus, le v scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Score en points. Le vainqueur totalise 12 points, le deuxième 11, etc. En cas d''égalité, la victoire est accordée au concurrent qui a totalisé le plus de points. Si l''égalité persiste, la victoire est accordée en fonction du classement dans la dernière course. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Système de points avec bonus - ESS 2017, le vainqueur obtient 12 points (ou 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Score en points. Le vainqueur totalise 12 points, le deuxième 11, etc. En cas d''égalité, la victoire est accordée au concurrent qui a totalisé le plus de points. Si l''égalité persiste, la victoire est accordée en fonction du classement dans la dernière course. +scoringSchemeLowPointSystemFirstTwoWins=Système de points a minima. Le premier ayant gagné deux médailles gagne la compétition. +scoringSchemeLowPointSystemFirstTwoWinsDescription=Système de points a minima. Le premier ayant gagné deux courses gagne la série médaillée. Si un concurrent a été le meilleur dans la série pré-médailles, cela compte comme une victoire pour la série médaillée. scoringSystem=Système d''attribution des points createFlexibleLeaderboard=Créer palmarès flexible createRegattaLeaderboard=Créer palmarès régate @@ -1163,7 +1165,8 @@ racesScoredTooltip=Nombre de courses que le concurrent a terminées, ou pour les averageNumberOfOperationsPerMessage=Nombre moyen d''opérations par message showUncorrectedTotalPoints=Afficher total des points non corrigé setStartTimeReceived=Afficher heure de départ reçue -setStartTimeReceivedDescription=Définit l''heure de départ reçue de la course suivie sélectionnée non persistante. Cela signifie que la nouvelle valeur sera oubliée une fois que le serveur sera relancé. +setStartTimeReceivedDescription=Définit l''heure de départ reçue de la course suivie sélectionnée sur la valeur indiquée. Ne renseignez pas cette valeur si vous voulez supprimer l''actuelle heure de départ reçue définie. +setStartTimeReceivedNotice=Cette modification n''est pas persistante. Cela signifie que la nouvelle valeur sera oubliée une fois que le serveur sera redémarré. lastScoreCorrectionsTime=Dernière heure de correction du score lastScoreCorrectionsComment=Dernier commentaire sur la correction du score setTimeToNow=Définir heure sur "maintenant" @@ -1278,8 +1281,8 @@ showCompetitorBoatColumn=Bateau du concurrent showCompetitorShortNameColumn=Nom court du concurrent showCompetitorShortNameColumnTooltip=Affiche le nom court ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorFullNameColumn=Nom complet du concurrent -showCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent -showCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que l''image du concurrent +showCompetitorNationalityColumn=Afficher en plus la nationalité du concurrent +showCompetitorNationalityColumnTooltip=Si disponible, afficher le drapeau national en plus de l''image du concurrent. loadingDimensionValues=Chargement des valeurs de dimension inviteBuoyTenders=Inviter navire baliseur orMultipleEmails=ou plusieurs adresses e-mails séparées par des virgules @@ -1851,7 +1854,7 @@ minDataCount=Nombre de données min. moreLoginInformationHeadline=Avantages de SAP Sailing Analytics moreLoginInformationIntroduction=Si vous êtes un utilisateur inscrit sur SAP Sailing Analytics, vous avez accès à des fonctionnalités qui ne sont pas disponibles pour les utilisateurs anonymes. Inscrivez-vous pour accéder à toutes les fonctionnalités indiquées ci-dessous. moreLoginInformationSectionUserSettingsHeading=Options utilisateur -moreLoginInformationSectionUserSettingsDescription=Certaines éléments de l''interface utilisateur peuvent être personnalisés. Les fonctionnalités de base sont accessibles à tous les utilisateurs, mais les utilisateurs authentifiés ont la possibilité de modifier leurs options. Celles-ci sont conservées dans leur profil utilisateur et disponibles sur n''importe quel appareil dès que l''utilisateur se connecte. +moreLoginInformationSectionUserSettingsDescription=Certains éléments de l''interface utilisateur peuvent être personnalisés. Les fonctionnalités de base sont accessibles à tous les utilisateurs, mais les utilisateurs authentifiés ont la possibilité de modifier leurs options. Celles-ci sont conservées dans leur profil utilisateur et disponibles sur n''importe quel appareil dès que l''utilisateur se connecte. moreLoginInformationSectionStrategySimulatorDescription=Le simulateur permet aux navigateurs de simuler des stratégies sur des portions de parcours grâce à des conditions de vent configurables. Le simulateur se base sur l''ensemble des données historiques disponibles dans SAP Sailing Analytics pour fournir des simulations de comportements de catégories de bateaux spécifiques les plus précises possibles. moreLoginInformationSectionUserNotificationsHeading=Notifications utilisateur moreLoginInformationSectionUserNotificationsDescription=Grâce aux notifications utilisateur, vous pouvez rester informé sur votre catégorie de bateau, votre équipe ou votre concurrent favoris. Vous avez la possibilité de vous abonner afin de recevoir des notifications par e-mail sur votre catégorie de bateau, votre équipe ou votre concurrent favoris, ainsi que sur les différentes courses à venir. @@ -1874,7 +1877,7 @@ maximumSpeedNotFoilingInKnotsTooltip=Vitesse fond maximale en nœuds qu''un bate minimumSpeedForFoilingInKnots=Vitesse fond minimale pour déjauger (nds) minimumSpeedForFoilingInKnotsTooltip=Vitesse fond minimale en nœuds qu''un bateau doit atteindre pour pouvoir déjauger. Si la vitesse est inférieure à celle-ci, la hauteur de planing sera ignorée et le bateau ne sera pas considéré comme étant en déjaugeage. Laissez vide ou indiquez 0 si vous souhaitez également vous référer au capteur de hauteur de planing pour les vitesses faibles. minimumRideHeightInMeters=Hauteur de planing minimale (m) -minimumRideHeightInMetersTooltip=Hauteur de planing minimale requise en mètres pour qu''une bateau soit considéré comme étant en état de déjaugeage. +minimumRideHeightInMetersTooltip=Hauteur de planing minimale requise en mètres pour qu''un bateau soit considéré comme étant en état de déjaugeage. minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=Durée minimum (s) entre deux tronçons en planing adjacents minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=Si cette valeur est renseignée et que la durée entre deux tronçons de planing adjacents est inférieure à celle-ci, ces tronçons de planing adjacents seront combinés en un seul. needToProvideValidMinimumRideHeight=Vous devez fournir une valeur de hauteur de planing minimale valide en mètres. @@ -1971,6 +1974,7 @@ windFinderWindSourceTypeTooltip=Vent mesuré d''un ou plusieurs endroits de www. windFinder=WindFinder enterTagsForTheVideo=Entrez des balises pour la vidéo. enterIdOfWindFinderReviewedSpotCollection=Entrez l''ID d''un ensemble d''endroits WindFinder révisé, par exemple, "Schilksee". +windFinderSpotCollectionsList=Ensemble d''endroits WindFinder enterTagsForTheImage=Entrez des balises pour l''image. unableToResolveWindFinderSpotId=Impossible de traiter l''endroit WindFinder ayant l''ID {0} : {1} windFinderWeatherData=Données météo @@ -2055,6 +2059,9 @@ multiVideoIdle=La réserve de travail est inactive. multiVideoDoNoAdd=Ne pas ajouter multiVideoOffsetInput=Décalage vidéo global en millisecondes : multiVideoDescription=Le serveur Web doit fournir une liste des index (les sous-dossiers sont pris en charge s''ils sont répertoriés) pour permettre la détection de fichier. Après une analyse initiale des métadonnées contenues dans les fichiers MP4, les vidéos à ajouter doivent être sélectionnées en cochant les cases correspondantes dans la colonne de gauche. Le bouton "Ajouter audio/vidéo" va créer des pistes médias pour tous les fichiers sélectionnés, et ajouter ces fichiers à toutes les courses sélectionnées dans la colonne de droite. +jibeAngle=Angle d''amure +tackAngle=Angle de virement de bord +maneuverAngle=Angle de manœuvre multiUrlChangeMediaTrack=Ajuster plusieurs URL de piste média multiUrlChangeReplace=Remplacer par multiUrlChangeFind=Rechercher @@ -2063,5 +2070,16 @@ multiUrlChangeSave=Sauvegarder modifications apportées aux URL multiUrlChangeNewURL=Nouvelle URL multiUrlNoPrefixWarning=Aucun préfixe commun n''a été trouvé. Cela signifie généralement que les vidéos sélectionnées ne sont actuellement pas toutes hébergées au même endroit. Vous assumez les risques liés à leur utilisation. multiUrlChangeExplain=Cette boîte de dialogue remplacera en masse les éléments communs au début des URL de piste média. Assurez-vous que toutes les URL commencent par le même préfixe. Veuillez également prendre le temps d''observer la nouvelle colonne d''URL, et de tester les URL résultantes avant de sauvegarder. -lastEvent=Dernier événement : {0} +resetZoom=Réinitialiser le zoom +lastEvent=Dernière manifestation : {0} teaserOverallLinkToolTip=Pour afficher l''ensemble des séries, cliquez sur le coin jaune. +middleManeuverAngle=Angle de manœuvre (milieu) +extrapolatedManeuverStartPosition=Position extrapolée du début de la manœuvre +projectedExtrapolatedManeuverStartPosition=Projection de la position extrapolée du début de la manœuvre sur la ligne de l''angle de manœuvre (milieu) +projectedManeuverEndPosition=Projection de la position extrapolée de fin de la manœuvre sur la ligne de l''angle de manœuvre (milieu) +bearingAtManeuverEndPosition=Direction à la position de fin de la manœuvre +gender=Sexe +manage2SailEventIdBox=ID de manifestation Manage2Sail +manage2SailEventURLBox=URL de la manifestation Manage2Sail (json) +manage2SailEventIdBoxTooltip=ID d''événement ou URL contenant l''ID d''événement issu de Manage2Sail +manage2SailPort=Bâbord diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index 6eab3ac7219..6bcbca3f0b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=高得点方式、1 位が 12 点 ( scoringSchemeHighPointFirstGetsTwelveOrEightDescription=得点数によるスコアです。1 位が 12 点、2 位が 11 点などと得点します。同点となった場合は、(各競技者のレース得点を高い方から比較して) 最多得点の競技者を上位とします。それでも同順位となる場合には、最終レースでの得点でのランクで順位を決定します。 scoringSchemeHighPointFirstGetsTwelveOrEight2017=高得点方式 ESS 2017、1 位が 12 点 (または 8 点) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=得点数によるスコアです。1 位が 12 点、2 位が 11 点などと得点します。同点となった場合は、(各競技者のレース得点を高い方から比較して) 最多得点の競技者を上位とします。それでも同順位となる場合には、最終レースでの得点でのランクで順位を決定します。 +scoringSchemeLowPointSystemFirstTwoWins=低得点方式、メダルシリーズで最初に 2 勝した者が勝者 +scoringSchemeLowPointSystemFirstTwoWinsDescription=低得点方式。メダルシリーズで最初に 2 レースに勝利した者をメダルシリーズの勝者とします。プレメダルシリーズでの最上位をメダルシリーズの 1 勝としてカウントします。 scoringSystem=得点方式 createFlexibleLeaderboard=フレキシブルリーダーボード登録 createRegattaLeaderboard=レガッタリーダーボード登録 @@ -1163,7 +1165,8 @@ racesScoredTooltip=このリーダーボードにおいて競技者が完了し averageNumberOfOperationsPerMessage=メッセージごとの操作の平均数 showUncorrectedTotalPoints=未修正総得点表示 setStartTimeReceived=設定済スタート時刻を受信 -setStartTimeReceivedDescription=これにより、永続的でない選択した TrackedRace の startTimeReceived が設定されます。したがって、サーバが再起動された後は新規の値は忘れられます。 +setStartTimeReceivedDescription=選択した TrackedRace の startTimeReceived が指定した値に設定されます。値を空白のままにすると、現在設定されている startTimeReceived は削除されます。 +setStartTimeReceivedNotice=この変更は永続的なものではありません。つまり、新しい値はサーバが再起動したときに失われます。 lastScoreCorrectionsTime=最終得点修正時刻 lastScoreCorrectionsComment=最終得点修正コメント setTimeToNow=時刻を ''現在'' に設定 @@ -1278,8 +1281,8 @@ showCompetitorBoatColumn=競技者の艇 showCompetitorShortNameColumn=競技者の名前 (短) showCompetitorShortNameColumnTooltip=名前 (短) に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorFullNameColumn=競技者氏名 -showCompetitorNationalityColumn=競技者国籍を常に表示 -showCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示 +showCompetitorNationalityColumn=競技者国籍を追加的に表示 +showCompetitorNationalityColumnTooltip=競技者画像に加え国旗を表示 (利用可能な場合) loadingDimensionValues=次元値ロード中 inviteBuoyTenders=ブイ入札の招待 orMultipleEmails=または複数の電子メールをカンマで区切り @@ -1971,6 +1974,7 @@ windFinderWindSourceTypeTooltip=www.windfinder.com の 1 つ以上のスポッ windFinder=Windfinder enterTagsForTheVideo=動画のタグを入力 enterIdOfWindFinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee") +windFinderSpotCollectionsList=WindFinder スポットコレクション enterTagsForTheImage=画像のタグを入力 unableToResolveWindFinderSpotId=ID {0} で Windfinder スポットを決定できませんでした: {1} windFinderWeatherData=気象データ @@ -2055,6 +2059,9 @@ multiVideoIdle=ワークキューが無効です multiVideoDoNoAdd=追加しないでください multiVideoOffsetInput=グローバル動画オフセット (msec): multiVideoDescription=Web サーバは、ファイルディスカバリを可能にするため、インデックスリスト (インデックスが作成されている場合はサブフォルダがサポート済) を用意していることが必要です。MP4 ファイルに含まれているメタデータの初期解析後に、追加する動画を左側のチェックボックス列で選択する必要があります。"音声/動画追加" ボタンにより、選択されているすべてのファイルに対してメディアトラックが作成され、右側の列で選択されているすべてのレースに対してそれらのファイルが追加されます。 +jibeAngle=ジャイブ角 +tackAngle=タック角 +maneuverAngle=マニューバー角 multiUrlChangeMediaTrack=複数メディアトラック URL の調整 multiUrlChangeReplace=置換後の文字列 multiUrlChangeFind=検索 @@ -2063,5 +2070,16 @@ multiUrlChangeSave=URL 変更の保存 multiUrlChangeNewURL=新規 URL multiUrlNoPrefixWarning=共通の接頭辞が見つかりませんでした。これは通常、選択した動画のうち現在同じ場所に置かれていないものがあることを意味します。自身の責任で進めてください。 multiUrlChangeExplain=このダイアログはメディアトラック URL 先頭の共通部分をまとめて置換します。すべての URL が同一の接頭辞で始まっていることを確認してください。また、新規 URL 列を参照し、保存を選択する前に結果として得られる URL を吟味してください。 +resetZoom=拡大/縮小のリセット lastEvent=最終イベント: {0} teaserOverallLinkToolTip=シリーズ全体を参照するには、黄色になっている隅の部分をクリックしてください +middleManeuverAngle=マニューバー角の中心 +extrapolatedManeuverStartPosition=推定マニューバー開始位置 +projectedExtrapolatedManeuverStartPosition=マニューバー角の中心線上に推定マニューバー開始位置を投影 +projectedManeuverEndPosition=マニューバー角の中心線上にマニューバー終了位置を投影 +bearingAtManeuverEndPosition=マニューバー終了位置での方位 +gender=性別 +manage2SailEventIdBox=Manage2Sail イベント ID +manage2SailEventURLBox=Manage2Sail イベント URL (JSON) +manage2SailEventIdBoxTooltip=イベント ID または Manage2Sail からのイベント ID が入っている URL +manage2SailPort=ポート diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties index 0206d843906..d13909efea6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Sistema de bônus, o vencedor receb scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Pontuação em pontos. O vencedor pontua 12 pontos, o 2º - 11 pontos, .... Um desempate é efetuado a favor do competidor com a pontuação mais alta, se o empate continuar, então o desempate é efetuado pela posição na última corrida pontuada. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Sistema de bônus ESS 2017, o vencedor recebe 12 pontos (ou 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Pontuação em pontos. O vencedor pontua 12 pontos, o 2º - 11 pontos, .... Um desempate é efetuado a favor do competidor com a pontuação mais alta, se o empate continuar, então o desempate é efetuado pela posição na última corrida pontuada. +scoringSchemeLowPointSystemFirstTwoWins=Sistema linear, o primeiro com duas vitórias com medalhas é o vencedor. +scoringSchemeLowPointSystemFirstTwoWinsDescription=Sistema linear. O primeiro na série para medalha a vencer duas corridas vence a série para medalha. Ser o melhor na série prévia para medalha conta como uma vitória para este efeito. scoringSystem=Sistema de pontuação createFlexibleLeaderboard=Criar painel de classificação flexível createRegattaLeaderboard=Criar painel de classificação de regata @@ -1163,7 +1165,8 @@ racesScoredTooltip=Número de corridas que o competidor concluiu ou nas quais ob averageNumberOfOperationsPerMessage=Número médio de operações por mensagem showUncorrectedTotalPoints=Visualizar total de pontos não corrigidos setStartTimeReceived=Definir hora de partida recebida -setStartTimeReceivedDescription=Isto define a hora de partida recebida da corrida rastreada selecionada que não é persistente. Isto significa que o novo valor será esquecido após a reinicialização do servidor. +setStartTimeReceivedDescription=Define a hora de partida recebida da corrida rastreada selecionada para o valor fornecido. Deixe o valor em branco para remover a hora de partida recebida definida atualmente. +setStartTimeReceivedNotice=Esta modificação não é persistente, ou seja, o novo valor será esquecido quando o servidor for reiniciado. lastScoreCorrectionsTime=Hora de correção da última pontuação lastScoreCorrectionsComment=Comentário de correção da última pontuação setTimeToNow=Definir hora para ''agora'' @@ -1278,8 +1281,8 @@ showCompetitorBoatColumn=Barco do competidor showCompetitorShortNameColumn=Nome abreviado do competidor showCompetitorShortNameColumnTooltip=Exibe o nome abreviado mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorFullNameColumn=Nome completo do competidor -showCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor -showCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis +showCompetitorNationalityColumn=Exibir adicionalmente a nacionalidade do competidor +showCompetitorNationalityColumnTooltip=Exibir bandeiras de nacionalidade além das imagens do competidor, caso estejam disponíveis loadingDimensionValues=Carregando valores de dimensão inviteBuoyTenders=Convidar navios-balizadores orMultipleEmails=ou vários e-mails separados por vírgula @@ -1971,6 +1974,7 @@ windFinderWindSourceTypeTooltip=Vento medido de um ou mais locais de www.windfin windFinder=WindFinder enterTagsForTheVideo=Inserir etiquetas para o vídeo enterIdOfWindFinderReviewedSpotCollection=Inserir ID de uma coleção revisada de locais do WindFinder, por exemplo, "schilksee" +windFinderSpotCollectionsList=Coleções de locais do WindFinder enterTagsForTheImage=Inserir etiquetas para a imagem unableToResolveWindFinderSpotId=Impossível resolver local do WindFinder com ID {0}: {1} windFinderWeatherData=Dados metereológicos @@ -2055,6 +2059,9 @@ multiVideoIdle=A lista de trabalho está inativa multiVideoDoNoAdd=Não adicionar multiVideoOffsetInput=Deslocamento de vídeo global em milissegundos: multiVideoDescription=O servidor da web é necessário para fornecer a lista de índices (subpastas suportadas se indexadas) de modo a permitir a descoberta de arquivos. Após uma análise inicial dos metadados contidos nos arquivos mp4, os vídeos que deviam ser adicionados precisam ser selecionados mediante a coluna do campo de seleção à esquerda. O botão "Adicionar áudio/vídeo" irá criar faixas de mídia para todos os arquivos selecionados e adicionar esses arquivos a todas as corridas selecionadas na coluna à direita. +jibeAngle=Ângulo de jaibe +tackAngle=Ângulo de cambada +maneuverAngle=Ângulo de manobra multiUrlChangeMediaTrack=Ajustar vários URLs de faixa de mídia multiUrlChangeReplace=Substituir por multiUrlChangeFind=Procurar @@ -2063,5 +2070,16 @@ multiUrlChangeSave=Gravar modificações do URL multiUrlChangeNewURL=novo URL multiUrlNoPrefixWarning=Não foi encontrado um prefixo comum. Isso normalmente significa que nem todos os vídeos selecionados estão hospedados atualmente no mesmo local. Continue por sua própria conta e risco! multiUrlChangeExplain=Este diálogo irá substituir em massa as partes comuns no início dos URLs da faixa de mídia. Assegure que todos os URLs começam com o mesmo prefixo! Veja também a nova coluna do URL e teste os URLs resultantes antes de pressionar Gravar! +resetZoom=Reinicializar zoom lastEvent=Último evento: {0} teaserOverallLinkToolTip=Para ver a série completa, clique no canto amarelo +middleManeuverAngle=Ângulo médio de manobra +extrapolatedManeuverStartPosition=Posição inicial de manobra extrapolada +projectedExtrapolatedManeuverStartPosition=Projeção da posição inicial de manobra extrapolada na linha de ângulo médio de manobra +projectedManeuverEndPosition=Projeção da posição final de manobra na linha de ângulo médio de manobra +bearingAtManeuverEndPosition=Rumo na posição final de manobra +gender=Sexo +manage2SailEventIdBox=ID de evento Manage2Sail +manage2SailEventURLBox=URL de evento Manage2Sail (json) +manage2SailEventIdBoxTooltip=ID de evento ou um URL que contenha o ID de evento do Manage2Sail +manage2SailPort=Porta diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 6847ca0ef94..4bf82159e3e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=Высокий балл, побед scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Оценка в баллах. Победитель получает 12 баллов, 2-й — 11 баллов, .... Равенство разрешается в пользу участника с наивысшей оценкой. Если равенство сохраняется, оно разрешается по рангу в последней гонке. scoringSchemeHighPointFirstGetsTwelveOrEight2017=Высокий балл ESS 2017, победитель получает 12 баллов (или 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Оценка в баллах. Победитель получает 12 баллов, 2-й — 11 баллов, .... Равенство разрешается в пользу участника с наивысшей оценкой. Если равенство сохраняется, оно разрешается по рангу в последней гонке. +scoringSchemeLowPointSystemFirstTwoWins=Система низких баллов, первый с двумя победами в гонках на медали является победителем +scoringSchemeLowPointSystemFirstTwoWinsDescription=Система низких баллов. Первый в серии на медали, кто выиграл две гонки, выигрывает серию на медали. Получение статуса "Лучший" в серии, предшествующей медальной, считается одной победой. scoringSystem=Система оценок createFlexibleLeaderboard=Создать гибкую таблицу лидеров createRegattaLeaderboard=Создать таблицу лидеров регаты @@ -1163,7 +1165,8 @@ racesScoredTooltip=Число гонок, которые участник зав averageNumberOfOperationsPerMessage=Среднее число операций на сообщение showUncorrectedTotalPoints=Показать неисправленный общий балл setStartTimeReceived=Установить полученное время старта -setStartTimeReceivedDescription=Устанавливает полученное время старта для выбранной отслеживаемой гонки, которое не хранится постоянно. Это значит, что после перезагрузки сервера новое значение будет забыто. +setStartTimeReceivedDescription=Устанавливает введенное значение для полученного времени старта выбранной отслеживаемой гонки. Оставьте значение пустым, чтобы удалить текущее установленное полученное время старта. +setStartTimeReceivedNotice=Это изменение не хранится, т.е. новое значение будет забыто после перезагрузки сервера. lastScoreCorrectionsTime=Время последнего исправления оценки lastScoreCorrectionsComment=Комментарий последнего исправления оценки setTimeToNow=Установить время ''сейчас'' @@ -1278,8 +1281,8 @@ showCompetitorBoatColumn=Лодка участника showCompetitorShortNameColumn=Краткое имя участника showCompetitorShortNameColumnTooltip=Показывает краткое имя плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorFullNameColumn=Полное имя участника -showCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника -showCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии) +showCompetitorNationalityColumn=Дополнительно показывать государственную принадлежность участника +showCompetitorNationalityColumnTooltip=Показывать государственные флаги (если есть) дополнительно к фотографии участника loadingDimensionValues=Загрузка значений измерения inviteBuoyTenders=Пригласить лоцманов orMultipleEmails=или несколько адресов эл. почты через запятую @@ -1971,6 +1974,7 @@ windFinderWindSourceTypeTooltip=Измерения ветра из одного windFinder=WindFinder enterTagsForTheVideo=Введите теги для видео enterIdOfWindFinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee") +windFinderSpotCollectionsList=Коллекции мест WindFinder enterTagsForTheImage=Введите теги для изображения unableToResolveWindFinderSpotId=Не удалось разрешить место WindFinder по ид. {0}: {1} windFinderWeatherData=Данные погоды @@ -2055,6 +2059,9 @@ multiVideoIdle=Рабочий список неактивен multiVideoDoNoAdd=Не добавлять multiVideoOffsetInput=Смещение глобального видео в мс: multiVideoDescription=Веб-сервер необходим для предоставления списка индекса (подпапки поддерживаются, если проиндексированы) для поиска файлов. После первичного анализа метаданных, содержащихся в файлах mp4, видео, которые должны быть добавлены, необходимо выбрать в столбце с флажками слева. При нажатии кнопки "Добавить аудио/видео" будут созданы медиаканалы для всех выбранных файлов, а файлы будут добавлены ко всем гонкам, выбранным в правом столбце. +jibeAngle=Поворот фордевинд +tackAngle=Галсовый угол +maneuverAngle=Угол маневра multiUrlChangeMediaTrack=Адаптировать URL объектов мультимедиа multiUrlChangeReplace=Заменить на multiUrlChangeFind=Найти @@ -2063,5 +2070,16 @@ multiUrlChangeSave=Сохранить изменения URL multiUrlChangeNewURL=Новый URL multiUrlNoPrefixWarning=Общий префикс не найден, это означает, что не все выбранные видео хранятся в одном и том же месте. Можете продолжить на свой риск! multiUrlChangeExplain=В этом диалоговом окне можно выполнить массовую замену общих частей в начале URL объектов мультимедиа. Убедитесь, что все URL имеют одинаковый префикс! Проверьте также столбец с новым URL и протестируйте новые URL перед сохранением! +resetZoom=Сбросить увеличение lastEvent=Последнее событие: {0} teaserOverallLinkToolTip=Чтобы увидеть все серии, нажмите желтый треугольник +middleManeuverAngle=Угол в середине маневра +extrapolatedManeuverStartPosition=Расширенная начальная позиция маневра +projectedExtrapolatedManeuverStartPosition=Проекция расширенной начальной позиции маневра на линии угла в середине маневра +projectedManeuverEndPosition=Проекция конечной позиции маневра на линии угла в середине маневра +bearingAtManeuverEndPosition=Направление в конечной позиции маневра +gender=Пол +manage2SailEventIdBox=Ид. события Manage2Sail +manage2SailEventURLBox=URL события Manage2Sail (json) +manage2SailEventIdBoxTooltip=Ид. события или URL, содержащий ид. события из Manage2Sail +manage2SailPort=Порт diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index cb19fde8474..e8e848faa28 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -480,6 +480,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=高分,获胜者得 12 分(或 scoringSchemeHighPointFirstGetsTwelveOrEightDescription=得分。获胜者得 12 分、第 2 名 11 分、.... 得分最高的参赛队取得胜利,如果仍有平局,则以最后一个比赛轮次的名次打破平局。 scoringSchemeHighPointFirstGetsTwelveOrEight2017=2017 年极限帆船系列赛高分,获胜者得 12 分(或 8 分) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=得分。获胜者得 12 分、第 2 名 11 分、.... 得分最高的参赛队取得胜利,如果仍有平局,则以最后一个比赛轮次的名次打破平局。 +scoringSchemeLowPointSystemFirstTwoWins=低分系统,奖牌轮中取得两场胜利的第一名为获胜者 +scoringSchemeLowPointSystemFirstTwoWinsDescription=低分系统。在奖牌轮系列中,在两个比赛轮次中获胜的第一名在奖牌轮系列中获胜。奖牌轮系列预赛中的佼佼者记为获胜。 scoringSystem=记分系统 createFlexibleLeaderboard=创建灵活的积分榜 createRegattaLeaderboard=创建比赛积分榜 @@ -1163,7 +1165,8 @@ racesScoredTooltip=参赛队完成并记录在此积分榜上的比赛轮次数 averageNumberOfOperationsPerMessage=各消息操作的平均数 showUncorrectedTotalPoints=显示未更正的总分 setStartTimeReceived=设置接收的开始时间 -setStartTimeReceivedDescription=设置非持久的所选 TrackedRace 的 startTimeReceived。这意味着,重启服务器后,将忘记新值。 +setStartTimeReceivedDescription=将所选 TrackedRace 的 startTimeReceived 设置为提供的值。将此值留空,以移除当前设置的 startTimeReceived。 +setStartTimeReceivedNotice=此更改并不持久,即重启服务器后,新值将被忘记。 lastScoreCorrectionsTime=上次得分更正时间 lastScoreCorrectionsComment=上次得分更正评论 setTimeToNow=将时间设为“现在” @@ -1278,8 +1281,8 @@ showCompetitorBoatColumn=参赛队的船只 showCompetitorShortNameColumn=参赛队短名称 showCompetitorShortNameColumnTooltip=显示短名称和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorFullNameColumn=参赛队全名 -showCompetitorNationalityColumn=始终显示参赛队国籍 -showCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像 +showCompetitorNationalityColumn=另外显示参赛队国籍 +showCompetitorNationalityColumnTooltip=除参赛队图像外还显示国籍旗帜(如果有) loadingDimensionValues=正在加载尺寸值 inviteBuoyTenders=邀请浮标供应船 orMultipleEmails=或使用逗号隔开的多个电子邮件 @@ -1971,6 +1974,7 @@ windFinderWindSourceTypeTooltip=Www.windfinder.com 中一个或多个地点的 windFinder=WindFinder enterTagsForTheVideo=为视频输入标记 enterIdOfWindFinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee" +windFinderSpotCollectionsList=WindFinder 地点集合 enterTagsForTheImage=为图像输入标记 unableToResolveWindFinderSpotId=无法解析编号为{0}的 WindFinder 地点:{1} windFinderWeatherData=天气数据 @@ -2055,6 +2059,9 @@ multiVideoIdle=工作队列空闲 multiVideoDoNoAdd=请勿添加 multiVideoOffsetInput=全球视频偏移量(毫秒): multiVideoDescription=需要 Web 服务器提供索引清单(如果已编入索引,则支持的子文件夹),以允许发现文件。初始分析 mp4 文件中包含的元数据后,需要通过左侧的复选框列选择应添加的视频。“添加音频/视频”按钮将为所有选择的文件创建媒体轨道,并将这些文件添加到右列中选择的所有比赛轮次中。 +jibeAngle=顺风转向角度 +tackAngle=迎风转向角度 +maneuverAngle=操作角度 multiUrlChangeMediaTrack=调整多个媒体轨道 URL multiUrlChangeReplace=替换为 multiUrlChangeFind=查找 @@ -2063,5 +2070,16 @@ multiUrlChangeSave=保存 URL 更改 multiUrlChangeNewURL=新 URL multiUrlNoPrefixWarning=未找到通用前缀,这通常意味着并非所有选定的视频都被托管在当前的相同位置。如继续操作,后果自负! multiUrlChangeExplain=此对话框将在媒体轨道 URL 的起始处批量替换通用部分。确保所有 URL 都以相同的前缀开头!也请注意新的 URL 列,然后在保存之前测试生成的 URL! +resetZoom=重置缩放 lastEvent=上次活动:{0} teaserOverallLinkToolTip=请查看整体系列赛,请点击黄色角落 +middleManeuverAngle=中间操作角度 +extrapolatedManeuverStartPosition=推断的操作开始位置 +projectedExtrapolatedManeuverStartPosition=推断的操作开始位置在中间操作角度线上的投影 +projectedManeuverEndPosition=操作结束位置在中间操作角度线上的投影 +bearingAtManeuverEndPosition=操作结束位置的航向角 +gender=性别 +manage2SailEventIdBox=Manage2Sail 活动编号 +manage2SailEventURLBox=Manage2Sail 活动 URL (json) +manage2SailEventIdBoxTooltip=包含 Manage2Sail 中活动编号的活动编号或 URL +manage2SailPort=左舷 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/JSDownloadUtils.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/JSDownloadUtils.java index 018fdabde91..d11168ed3d4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/JSDownloadUtils.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/JSDownloadUtils.java @@ -10,6 +10,7 @@ import com.google.gwt.typedarrays.shared.Int8Array; */ public class JSDownloadUtils { private static final Double REQUIRED_SIZE = 10000000.0; + protected static final Double MAX_MEMORY_USAGE_FOR_DL = 1024 * 1024 * 1024 * 2d; public interface JSDownloadCallback { void progress(Double current, Double total); @@ -43,7 +44,7 @@ public class JSDownloadUtils { getDataFast(url, callback, total, REQUIRED_SIZE); } else { //fallback will always work, if cors is supported, does not require any other server configuration - getDataSlow(url, callback, REQUIRED_SIZE); + getDataSlow(url, callback, REQUIRED_SIZE, MAX_MEMORY_USAGE_FOR_DL); } } }); @@ -53,75 +54,77 @@ public class JSDownloadUtils { * Fast file download method based on range requests, only the first and the last REQUIRED_SIZE is loaded. */ private native static void getDataFast(String url, JSDownloadCallback callback, Double length, Double REQUIRED_SIZE) /*-{ - try { - var xhr = new XMLHttpRequest(); - xhr.open("GET", url, true); - xhr.setRequestHeader("Range", "bytes=0-" + REQUIRED_SIZE); - xhr.responseType = "arraybuffer"; - xhr.onprogress = function(evt) { - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total); - } - xhr.error = function(error) { - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); - } - - xhr.onreadystatechange = function() { - var state = xhr.readyState; - if (state == 4) { - if (xhr.response) { - var startLength = xhr.response.byteLength; - var start = new Int8Array(xhr.response); + try { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.setRequestHeader("Range", "bytes=0-" + REQUIRED_SIZE); + xhr.responseType = "arraybuffer"; + xhr.onprogress = function(evt) { + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total); + } + xhr.error = function(error) { + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); + } - try { - var xhr2 = new XMLHttpRequest(); - xhr2.open("GET", url, true); - xhr2.setRequestHeader("Range", "bytes=" + (length - REQUIRED_SIZE)); - xhr2.responseType = "arraybuffer"; - xhr2.onprogress = function(evt) { - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total); - } - xhr2.error = function(error) { + xhr.onreadystatechange = function() { + var state = xhr.readyState; + if (state == 4) { + if (xhr.response) { + var startLength = xhr.response.byteLength; + var start = new Int8Array(xhr.response); + + try { + var xhr2 = new XMLHttpRequest(); + xhr2.open("GET", url, true); + xhr2.setRequestHeader("Range", "bytes=" + + (length - REQUIRED_SIZE) + "-"); + xhr2.responseType = "arraybuffer"; + xhr2.onprogress = function(evt) { + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total); + } + xhr2.error = function(error) { + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); + } + + xhr2.onreadystatechange = function() { + var state = xhr2.readyState; + if (state == 4) { + if (xhr2.response) { + var endLength = xhr2.response.byteLength; + var end = new Int8Array(xhr2.response); + var sparse = length - startLength + - endLength; + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::complete(Lcom/google/gwt/typedarrays/shared/Int8Array;Lcom/google/gwt/typedarrays/shared/Int8Array;Ljava/lang/Double;)(start, end, sparse); + } + } + }; + xhr2.send(); + } catch (error) { callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); } - - xhr2.onreadystatechange = function() { - var state = xhr2.readyState; - if (state == 4) { - if (xhr2.response) { - var endLength = xhr2.response.byteLength; - var end = new Int8Array(xhr2.response); - var sparse = length - startLength - endLength; - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::complete(Lcom/google/gwt/typedarrays/shared/Int8Array;Lcom/google/gwt/typedarrays/shared/Int8Array;Ljava/lang/Double;)(start, end, sparse); - } - } - }; - xhr2.send(); - } catch (error) { - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); } } - } - }; - xhr.send(); - } catch (error) { - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); - } -}-*/; + }; + xhr.send(); + } catch (error) { + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); + } + }-*/; /** * Determines if a server can deliver a file with range requests, and if so returns the size of the file */ private native static void getFileSizeIfFastPath(String url, JSSizeCallback callback) /*-{ - try { + try { var xhr = new XMLHttpRequest(); xhr.open("HEAD", url, true); xhr.responseType = "arraybuffer"; xhr.error = function(error) { callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSSizeCallback::size(Ljava/lang/Double;)(-1.0); } - + xhr.onreadystatechange = function() { - var state = xhr.readyState; + var state = xhr.readyState; if (state == 4) { if (xhr.response) { var length = xhr.getResponseHeader("Content-Length"); @@ -141,29 +144,36 @@ public class JSDownloadUtils { /** * If range requests are not possible, the whole file is downloaded and then slices to the required parts */ - private native static void getDataSlow(String url, JSDownloadCallback callback, Double REQUIRED_SIZE) /*-{ + private native static void getDataSlow(String url, JSDownloadCallback callback, Double REQUIRED_SIZE, + Double maxMemoryUsage) /*-{ try { var xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.responseType = "arraybuffer"; xhr.onprogress = function(evt) { + //ensure not more memory is used by the tab than commonly available in browsers + if (evt.total > maxMemoryUsage || evt.loaded > maxMemoryUsage) { + xhr.abort(); + } callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total); } xhr.error = function(error) { callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error); } - + xhr.onreadystatechange = function() { - var state = xhr.readyState; + var state = xhr.readyState; if (state == 4) { if (xhr.response) { var length = xhr.response.byteLength; - var start = new Int8Array(xhr.response.slice(0, REQUIRED_SIZE)); - var end = new Int8Array(xhr.response.slice(length - REQUIRED_SIZE)); + var start = new Int8Array(xhr.response.slice(0, + REQUIRED_SIZE)); + var end = new Int8Array(xhr.response.slice(length + - REQUIRED_SIZE)); var sparse = length - 2 * REQUIRED_SIZE; callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::complete(Lcom/google/gwt/typedarrays/shared/Int8Array;Lcom/google/gwt/typedarrays/shared/Int8Array;Ljava/lang/Double;)(start, end, sparse); } else { - callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)("No result"); + callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)("No result"); } } }; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java index ef40c4c0988..3bb2d03855b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java @@ -18,7 +18,6 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.media.client.Audio; import com.google.gwt.media.client.MediaBase; import com.google.gwt.typedarrays.shared.Int8Array; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; @@ -41,6 +40,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.media.MimeType; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput; import com.sap.sse.gwt.client.controls.datetime.DateTimeInput.Accuracy; @@ -400,7 +401,7 @@ public class NewMediaDialog extends DataEntryDialog { protected void mp4MetadataResult(VideoMetadataDTO result) { if (!result.isDownloadable()) { - Window.alert(stringMessages.couldNotDownload(mediaTrack.url)); + Notification.notify(stringMessages.couldNotDownload(mediaTrack.url), NotificationType.ERROR); manualMimeTypeSelection(result.getMessage(), mediaTrack); } else { mediaTrack.duration = result.getDuration(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java index bf5be5ca401..f0994abb40a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java @@ -11,11 +11,11 @@ import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.Button; @@ -43,6 +43,8 @@ import com.sap.sse.common.filter.Filter; import com.sap.sse.common.filter.FilterSet; import com.sap.sse.common.settings.AbstractSettings; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.shared.components.AbstractCompositeComponent; @@ -133,7 +135,7 @@ public class EditMarkPassingsPanel extends AbstractCompositeComponent, SafeHtml>(new AnchorCell()) { @Override public SafeHtml getValue(final Pair object) { @@ -216,7 +218,7 @@ public class EditMarkPassingsPanel extends AbstractCompositeComponent i private final RaceIdentifierToLeaderboardRaceColumnAndFleetMapper raceIdentifierToLeaderboardRaceColumnAndFleetMapper; protected boolean nonTrackingWarningWasDisplayed; + private final Set marksCurrentlyRequestedViaRemoteCall = new HashSet<>(); + public EditMarkPositionPanel(Component parent, ComponentContext context, final RaceMap raceMap, final SingleRaceLeaderboardPanel leaderboardPanel, RegattaAndRaceIdentifier selectedRaceIdentifier, String leaderboardName, final StringMessages stringMessages, @@ -735,10 +739,7 @@ public class EditMarkPositionPanel extends AbstractRaceChart i } raceMap.unregisterAllCourseMarkInfoWindowClickHandlers(); } else { - if (currentFixPositionChooser != null) { - currentFixPositionChooser.cancel(); - currentFixPositionChooser = null; - } + cancelFixPositionChooserAndNotification(); marksPanel.deselectMark(); selectedMark = null; if (sideBySideComponentViewer != null) { @@ -754,6 +755,17 @@ public class EditMarkPositionPanel extends AbstractRaceChart i super.setVisible(visible); } + private void cancelFixPositionChooserAndNotification() { + if (currentFixPositionChooser != null) { + currentFixPositionChooser.cancel(); + currentFixPositionChooser = null; + if (notificationTimer.isRunning()) { + notificationTimer.run(); + notificationTimer.cancel(); + } + } + } + private void checkIfTracking(Runnable continuation) { if (nonTrackingWarningWasDisplayed) { continuation.run(); @@ -761,14 +773,14 @@ public class EditMarkPositionPanel extends AbstractRaceChart i sailingService.checkIfRaceIsTracking(selectedRaceIdentifier, new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.serverError()); + Notification.notify(stringMessages.serverError(), Notification.NotificationType.ERROR); continuation.run(); } @Override public void onSuccess(Boolean result) { if (Boolean.FALSE.equals(result)) { - Window.alert(stringMessages.positionEditOnNonTrackingRace()); + Notification.notify(stringMessages.positionEditOnNonTrackingRace(), Notification.NotificationType.WARNING); nonTrackingWarningWasDisplayed = true; } continuation.run(); @@ -859,11 +871,10 @@ public class EditMarkPositionPanel extends AbstractRaceChart i } private void selectMark(MarkDTO mark) { - selectedMark = mark; - if (currentFixPositionChooser != null) { - currentFixPositionChooser.cancel(); - currentFixPositionChooser = null; + if (selectedMark != mark) { + cancelFixPositionChooserAndNotification(); } + selectedMark = mark; if (selectedMark != null) { if (marksFromToTimes.get(selectedMark) != null) { // For some reason the time slider does not change with this method only if you comment out line 430 and 432 in TimePanel it works @@ -905,24 +916,43 @@ public class EditMarkPositionPanel extends AbstractRaceChart i @Override public void onSelectionChange(SelectionChangeEvent event) { final MarkDTO mark = marksPanel.getSelectedMark(); + retrieveAndSelectMarkIfNecessary(mark, null); + } + + protected void retrieveAndSelectMarkIfNecessary(final MarkDTO mark, final Runnable callback) { if (mark != null && (marks.get(mark) == null || marks.get(mark).isEmpty())) { - if (mark != null) { - markPositionService.getMarkTrack(raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier), + + if (marksCurrentlyRequestedViaRemoteCall.add(mark)) { + markPositionService.getMarkTrack( + raceIdentifierToLeaderboardRaceColumnAndFleetMapper + .getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier), mark.getIdAsString(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { - errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage()); + marksCurrentlyRequestedViaRemoteCall.remove(mark); + errorReporter.reportError( + stringMessages.errorCommunicatingWithServer() + ": " + caught.getMessage()); } - + @Override public void onSuccess(MarkTrackDTO result) { + marksCurrentlyRequestedViaRemoteCall.remove(mark); createMarkTrackUi(mark, result.getFixes()); selectMark(mark); + if (callback != null) { + callback.run(); + } } }); } + else { + // remote call of the same mark already in progress -> ignore this request + } } else { selectMark(mark); + if (callback != null) { + callback.run(); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java index cae1ca24db7..219e12485f8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java @@ -85,22 +85,27 @@ public class MarksPanel extends AbstractCompositeComponent { @Override public void update(int index, final MarkDTO mark, String value) { final Date timePoint = parent.timer.getTime(); - select(mark); - if (parent.hasFixAtTimePoint(mark, timePoint)) { - parent.showNotification(stringMessages.pleaseSelectOtherTimepoint(), NotificationType.ERROR); - } else { - parent.createFixPositionChooserToAddFixToMark(mark, new Callback() { - @Override - public void onFailure(Exception reason) { - parent.resetCurrentFixPositionChooser(); + parent.retrieveAndSelectMarkIfNecessary(mark, new Runnable() { + + @Override + public void run() { + if (parent.hasFixAtTimePoint(mark, timePoint)) { + parent.showNotification(stringMessages.pleaseSelectOtherTimepoint(), NotificationType.ERROR); + } else { + parent.createFixPositionChooserToAddFixToMark(mark, new Callback() { + @Override + public void onFailure(final Exception reason) { + parent.resetCurrentFixPositionChooser(); + } + @Override + public void onSuccess(Position result) { + parent.addMarkFix(mark, timePoint, result); + parent.resetCurrentFixPositionChooser(); + } + }); } - @Override - public void onSuccess(Position result) { - parent.addMarkFix(mark, timePoint, result); - parent.resetCurrentFixPositionChooser(); - } - }); - } + } + }); } }); markTable.addColumn(addFixColumn); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 814aac67e3f..5e7d29b4d3e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -622,116 +622,123 @@ public class RaceMap extends AbstractCompositeComponent impleme loadLibraries.add(LoadLibrary.GEOMETRY); Runnable onLoad = new Runnable() { - @Override - public void run() { - MapOptions mapOptions = getMapOptions(showMapControls, /* wind up */ false); - map = new MapWidget(mapOptions); - rootPanel.add(map, 0, 0); - if (showHeaderPanel) { - Image sapLogo = createSAPLogo(); - rootPanel.add(sapLogo); - } - - map.setControls(ControlPosition.LEFT_TOP, topLeftControlsWrapperPanel); - adjustLeftControlsIndent(); + @Override + public void run() { + MapOptions mapOptions = getMapOptions(showMapControls, /* wind up */ false); + map = new MapWidget(mapOptions); + rootPanel.add(map, 0, 0); + if (showHeaderPanel) { + Image sapLogo = createSAPLogo(); + rootPanel.add(sapLogo); + } - RaceMap.this.raceMapImageManager.loadMapIcons(map); - map.setSize("100%", "100%"); - map.addZoomChangeHandler(new ZoomChangeMapHandler() { - @Override - public void onEvent(ZoomChangeMapEvent event) { - if (!autoZoomIn && !autoZoomOut && !orientationChangeInProgress) { - // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings - final List emptyList = Collections.emptyList(); - RaceMapZoomSettings clearedZoomSettings = new RaceMapZoomSettings(emptyList, settings.getZoomSettings().isZoomToSelectedCompetitors()); - settings = new RaceMapSettings(settings, clearedZoomSettings); - } - // TODO bug489 when in wind-up mode, avoid zooming out too far; perhaps zoom back in if zoomed out too far - } - }); - map.addDragEndHandler(new DragEndMapHandler() { - @Override - public void onEvent(DragEndMapEvent event) { - // stop automatic zoom after a manual drag event - autoZoomIn = false; - autoZoomOut = false; - final List emptyList = Collections.emptyList(); - RaceMapZoomSettings clearedZoomSettings = new RaceMapZoomSettings(emptyList, settings.getZoomSettings().isZoomToSelectedCompetitors()); - settings = new RaceMapSettings(settings, clearedZoomSettings); - } - }); - map.addIdleHandler(new IdleMapHandler() { - @Override - public void onEvent(IdleMapEvent event) { - // the "idle"-event is raised at the end of map-animations - if (autoZoomIn) { - // finalize zoom-in that was started with panTo() in zoomMapToNewBounds() - map.setZoom(autoZoomLevel); - autoZoomIn = false; - } - if (autoZoomOut) { - // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds() - map.panTo(autoZoomLatLngBounds.getCenter()); - autoZoomOut = false; - } - } - }); - map.addBoundsChangeHandler(new BoundsChangeMapHandler() { - @Override - public void onEvent(BoundsChangeMapEvent event) { - int newZoomLevel = map.getZoom(); - if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) { - removeTransitions(); - } - if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) { - streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); - } - if ((simulationOverlay != null) && !map.getBounds().equals(currentMapBounds)) { - simulationOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); - } - currentMapBounds = map.getBounds(); - currentZoomLevel = newZoomLevel; - headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); - } - }); - - // If there was a time change before the API was loaded, reset the time - if (lastTimeChangeBeforeInitialization != null) { - timeChanged(lastTimeChangeBeforeInitialization, null); - lastTimeChangeBeforeInitialization = null; - } - // Initialize streamlet canvas for wind visualization; it shouldn't be doing anything unless it's visible - streamletOverlay = new WindStreamletsRaceboardOverlay(getMap(), /* zIndex */ 0, - timer, raceIdentifier, sailingService, asyncActionsExecutor, stringMessages, coordinateSystem); - streamletOverlay.addToMap(); - if (settings.isShowWindStreamletOverlay()) { - streamletOverlay.setColors(settings.isShowWindStreamletColors()); - streamletOverlay.setVisible(true); - } + map.setControls(ControlPosition.LEFT_TOP, topLeftControlsWrapperPanel); + adjustLeftControlsIndent(); - if (isSimulationEnabled) { - // determine availability of polar diagram - setHasPolar(); - // initialize simulation canvas - simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor, coordinateSystem); - simulationOverlay.addToMap(); - showSimulationOverlay(settings.isShowSimulationOverlay()); - } - if (showHeaderPanel) { - createHeaderPanel(map); - } - if (showMapControls) { - createSettingsButton(map); - } - // Data has been initialized - RaceMap.this.isMapInitialized = true; - RaceMap.this.redraw(); - trueNorthIndicatorPanel.redraw(); - showAdditionalControls(map); - RaceMap.this.managedInfoWindow = new ManagedInfoWindow(map); - } + RaceMap.this.raceMapImageManager.loadMapIcons(map); + map.setSize("100%", "100%"); + map.addZoomChangeHandler(new ZoomChangeMapHandler() { + @Override + public void onEvent(ZoomChangeMapEvent event) { + if (!autoZoomIn && !autoZoomOut && !orientationChangeInProgress) { + // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will + // restore old settings + final List emptyList = Collections.emptyList(); + RaceMapZoomSettings clearedZoomSettings = new RaceMapZoomSettings(emptyList, + settings.getZoomSettings().isZoomToSelectedCompetitors()); + settings = new RaceMapSettings(settings, clearedZoomSettings); + } + // TODO bug489 when in wind-up mode, avoid zooming out too far; perhaps zoom back in if zoomed + // out too far + } + }); + map.addDragEndHandler(new DragEndMapHandler() { + @Override + public void onEvent(DragEndMapEvent event) { + // stop automatic zoom after a manual drag event + autoZoomIn = false; + autoZoomOut = false; + final List emptyList = Collections.emptyList(); + RaceMapZoomSettings clearedZoomSettings = new RaceMapZoomSettings(emptyList, + settings.getZoomSettings().isZoomToSelectedCompetitors()); + settings = new RaceMapSettings(settings, clearedZoomSettings); + } + }); + map.addIdleHandler(new IdleMapHandler() { + @Override + public void onEvent(IdleMapEvent event) { + // the "idle"-event is raised at the end of map-animations + if (autoZoomIn) { + // finalize zoom-in that was started with panTo() in zoomMapToNewBounds() + map.setZoom(autoZoomLevel); + autoZoomIn = false; + } + if (autoZoomOut) { + // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds() + map.panTo(autoZoomLatLngBounds.getCenter()); + autoZoomOut = false; + } + redraw(); + } + }); + map.addBoundsChangeHandler(new BoundsChangeMapHandler() { + @Override + public void onEvent(BoundsChangeMapEvent event) { + int newZoomLevel = map.getZoom(); + if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) { + removeTransitions(); + } + if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) { + streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); + } + if ((simulationOverlay != null) && !map.getBounds().equals(currentMapBounds)) { + simulationOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); + } + currentMapBounds = map.getBounds(); + currentZoomLevel = newZoomLevel; + headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); + } + }); + + // If there was a time change before the API was loaded, reset the time + if (lastTimeChangeBeforeInitialization != null) { + timeChanged(lastTimeChangeBeforeInitialization, null); + lastTimeChangeBeforeInitialization = null; + } + // Initialize streamlet canvas for wind visualization; it shouldn't be doing anything unless it's + // visible + streamletOverlay = new WindStreamletsRaceboardOverlay(getMap(), /* zIndex */ 0, timer, raceIdentifier, + sailingService, asyncActionsExecutor, stringMessages, coordinateSystem); + streamletOverlay.addToMap(); + if (settings.isShowWindStreamletOverlay()) { + streamletOverlay.setColors(settings.isShowWindStreamletColors()); + streamletOverlay.setVisible(true); + } + + if (isSimulationEnabled) { + // determine availability of polar diagram + setHasPolar(); + // initialize simulation canvas + simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, + sailingService, stringMessages, asyncActionsExecutor, coordinateSystem); + simulationOverlay.addToMap(); + showSimulationOverlay(settings.isShowSimulationOverlay()); + } + if (showHeaderPanel) { + createHeaderPanel(map); + } + if (showMapControls) { + createSettingsButton(map); + } + // Data has been initialized + RaceMap.this.isMapInitialized = true; + RaceMap.this.redraw(); + trueNorthIndicatorPanel.redraw(); + showAdditionalControls(map); + RaceMap.this.managedInfoWindow = new ManagedInfoWindow(map); + } }; - LoadApi.go(onLoad, loadLibraries, sensor, GoogleMapAPIKey.V3_PARAMS); + LoadApi.go(onLoad, loadLibraries, sensor, GoogleMapAPIKey.V3_PARAMS); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java index 564f80b57c9..b4506e6d70a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java @@ -12,6 +12,7 @@ import com.sap.sailing.gwt.ui.shared.ManeuverDTO; class ManeuverTableData { private final String competitorName; + private final String competitorColor; private final Date timePoint; private final Date timePointBefore; private final ManeuverType maneuverType; @@ -24,8 +25,9 @@ class ManeuverTableData { private final double directionChange; private final boolean markPassing; - ManeuverTableData(final CompetitorDTO competitor, final ManeuverDTO maneuver) { + ManeuverTableData(final CompetitorDTO competitor, final String competitorColor, final ManeuverDTO maneuver) { this.competitorName = competitor.getName(); + this.competitorColor = competitorColor; this.timePoint = maneuver.getTimePoint(); this.timePointBefore = maneuver.getTimePointBefore(); this.maneuverType = maneuver.getType(); @@ -43,6 +45,10 @@ class ManeuverTableData { return competitorName; } + public String getCompetitorColor() { + return competitorColor; + } + public Date getTimePoint() { return timePoint; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java index 5b588fb674f..dc90b3643be 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java @@ -38,8 +38,8 @@ import com.sap.sailing.domain.common.security.Permission; import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.actions.GetManeuversForCompetitorsAction; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter; +import com.sap.sailing.gwt.ui.client.RaceCompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; @@ -47,6 +47,7 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel.LeaderBoardStyle; import com.sap.sailing.gwt.ui.leaderboard.SortedCellTableWithStylableHeaders; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sse.common.Color; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util; import com.sap.sse.common.filter.Filter; @@ -75,7 +76,8 @@ public class ManeuverTablePanel extends AbstractCompositeComponent parent, ComponentContext context, final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, final RegattaAndRaceIdentifier raceIdentifier, final StringMessages stringMessages, - final CompetitorSelectionProvider competitorSelectionModel, final ErrorReporter errorReporter, + final RaceCompetitorSelectionProvider competitorSelectionModel, final ErrorReporter errorReporter, final Timer timer, final ManeuverTableSettings initialSettings, final TimeRangeWithZoomModel timeRangeWithZoomProvider, final LeaderBoardStyle style, final UserService userService) { @@ -106,10 +108,11 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createCompetitorColumn() { + private SortableColumn createCompetitorColumn() { InvertibleComparator comparator = new InvertibleComparatorAdapter() { @Override public int compare(ManeuverTableData o1, ManeuverTableData o2) { return o1.getCompetitorName().compareTo(o2.getCompetitorName()); } }; - return new SortableColumn(new TextCell(), SortingOrder.ASCENDING) { + return new SortableColumn(new AbstractCell() { + @Override + public void render(Context context, ManeuverTableData data, SafeHtmlBuilder sb) { + final String color = data.getCompetitorColor(); + final String divStyle = color == null ? "border: none;" : "border-bottom: 2px solid " + color + ";"; + sb.appendHtmlConstant("
"); + sb.appendEscaped(data.getCompetitorName()); + sb.appendHtmlConstant("
"); + } + }, SortingOrder.ASCENDING) { @Override public InvertibleComparator getComparator() { return comparator; @@ -282,8 +294,8 @@ public class ManeuverTablePanel extends AbstractCompositeComponent> entry : cachedData.entrySet()) { for (ManeuverDTO maneuver : entry.getValue()) { if (settings.getSelectedManeuverTypes().contains(maneuver.getType())) { - data.add(new ManeuverTableData(entry.getKey(), maneuver)); + final Color competitorColor = competitorSelectionModel.getColor(entry.getKey(), raceIdentifier); + data.add(new ManeuverTableData(entry.getKey(), competitorColor.getAsHtml(), maneuver)); } } } @@ -429,15 +442,12 @@ public class ManeuverTablePanel extends AbstractCompositeComponent { private final AsyncActionsExecutor asyncActionsExecutor; - private final RegattaAndRaceIdentifier raceIdentifier; private final SailingServiceAsync sailingService; private CachedManeuverTableDataProvider(final TimeRangeProvider timeRangeProvider, final Timer timer, - final RegattaAndRaceIdentifier raceIdentifier, final SailingServiceAsync sailingService, - final AsyncActionsExecutor asyncActionsExecutor) { + final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor) { super(timeRangeProvider, timer, m -> m.getTimePoint(), LOADING_OFFSET_TO_NEXT_MANEUVER_PROVIDER, true); this.asyncActionsExecutor = asyncActionsExecutor; - this.raceIdentifier = raceIdentifier; this.sailingService = sailingService; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java index d085f7e05c6..2e450a867cd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java @@ -31,7 +31,6 @@ import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.Header; import com.google.gwt.user.cellview.client.SafeHtmlHeader; import com.google.gwt.user.cellview.client.TextHeader; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbstractImagePrototype; import com.google.gwt.user.client.ui.Anchor; @@ -633,7 +632,7 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel { @Override public void onSuccess(Void noarg) { updateScoreCorrectionInformation(lastScoreCorrectionComment, lastScoreCorrectionTime); - Window.alert(stringMessages.successfullyUpdatedScores()); + Notification.notify(stringMessages.successfullyUpdatedScores(), NotificationType.SUCCESS); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java index cacc5f7fc61..65b9705bddd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java @@ -15,7 +15,6 @@ import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; @@ -35,6 +34,8 @@ import com.sap.sailing.gwt.ui.client.MediaTracksRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.progressbar.CustomProgressBar; public class MasterDataImportPanel extends VerticalPanel { @@ -268,7 +269,7 @@ public class MasterDataImportPanel extends VerticalPanel { buffer.append(regattaName + "\n"); } } - Window.alert(buffer.toString()); + Notification.notify(buffer.toString(), NotificationType.SUCCESS); } @@ -319,7 +320,7 @@ public class MasterDataImportPanel extends VerticalPanel { } private void showErrorAlert(String string) { - Window.alert(string); + Notification.notify(string, NotificationType.ERROR); } private void addContentToLeftPanel(VerticalPanel contentPanel) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java index e8621d61e5d..4b0d76037bf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java @@ -49,12 +49,10 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors { raceDetailsToShow.add(DetailType.RACE_TIME_TRAVELED); final SingleRaceLeaderboardSettings additiveSettings = LeaderboardSettingsFactory.getInstance().createNewSettingsWithCustomRaceDetails(raceDetailsToShow); ((RaceBoardComponentContext) leaderboardPanel.getComponentContext()).addModesPatching(leaderboardPanel, additiveSettings, new OnSettingsReloadedCallback() { - @Override public void onSettingsReloaded(SingleRaceLeaderboardSettings patchedSettings) { leaderboardPanel.updateSettings(patchedSettings); } - }); } @@ -79,14 +77,11 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors { defaultSettings.isShowEstimatedDuration(), defaultSettings.getStartCountDownFontSizeScaling(), defaultSettings.isShowManeuverLossVisualization()); - ((RaceBoardComponentContext) raceMap.getComponentContext()).addModesPatching(raceMap, additiveSettings, new OnSettingsReloadedCallback() { - @Override public void onSettingsReloaded(RaceMapSettings patchedSettings) { raceMap.updateSettings(patchedSettings); } - }); } @@ -129,17 +124,16 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors { stopReceivingLeaderboard(); adjustLeaderboardSettings(); } - if (adjustedLeaderboardSettings && tailLength != null) { - adjustMapSettings(); - } if (getLeaderboardForSpecificTimePoint() == null && tailLength != null && getLeaderboard() != null && getRaceColumn() != null) { loadLeaderboardForSpecificTimePoint(getLeaderboard().name, getRaceColumn().getName(), getTimer().getTime()); } if (!adjustedCompetitorSelection && getLeaderboardForSpecificTimePoint() != null && getCompetitorsInRace() != null) { stopReceivingCompetitorsInRace(); adjustedCompetitorSelection = true; + updateCompetitorSelection(); + } + if (adjustedLeaderboardSettings && tailLength != null && adjustedCompetitorSelection) { + adjustMapSettings(); } - updateCompetitorSelection(); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java index 4ac3bbd8d54..16d309f6e26 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java @@ -17,6 +17,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.DateFormat; @@ -221,13 +222,22 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi private VideoMetadataDTO checkMetadataByFullFileDownload(URL input) throws ParserConfigurationException, SAXException, IOException { - try (IsoFile isof = new IsoFile(Channels.newChannel(input.openStream()))) { - boolean canDownload = true; - Date recordStartedTimer = determineRecordingStart(isof); - Duration duration = determineDuration(isof); - boolean spherical = determine360(isof); - removeTempFiles(isof); - return new VideoMetadataDTO(canDownload, duration, spherical, recordStartedTimer, ""); + final File tmp = File.createTempFile("upload", "metadataCheck"); + try { + final ReadableByteChannel rbc = Channels.newChannel(input.openStream()); + try (FileOutputStream fos = new FileOutputStream(tmp)) { + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + try (IsoFile isof = new IsoFile(tmp)) { + final boolean canDownload = true; + final Date recordStartedTimer = determineRecordingStart(isof); + final Duration duration = determineDuration(isof); + final boolean spherical = determine360(isof); + removeTempFiles(isof); + return new VideoMetadataDTO(canDownload, duration, spherical, recordStartedTimer, ""); + } + } + } finally { + Files.delete(tmp.toPath()); } } @@ -276,8 +286,9 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi Field field = box.getClass().getDeclaredField("dataFile"); field.setAccessible(true); File data = (File) field.get(box); - data.delete(); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + Files.delete(data.toPath()); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException + | IOException e) { logger.log(Level.WARNING, "Could not delete mp4 temp files", e); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 5369edd52fa..d7298f5ae56 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1051,7 +1051,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); raceInfoDTO.isTracked = trackedRace != null ? true : false; if (raceLog != null) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(getService(), raceLog); + ReadonlyRaceState state = ReadonlyRaceStateImpl.getOrCreate(getService(), raceLog); TimePoint startTime = state.getStartTime(); if (startTime != null) { raceInfoDTO.startTime = startTime.asDate(); @@ -1508,7 +1508,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S assert from != null; TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); WindInfoForRaceDTO result = getAveragedWindInfo(new MillisecondsTimePoint(from), millisecondsStepWidth, numberOfFixes, - windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent */ true, includeCombinedWindForAllLegMiddles); + windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent FIXME why not pass through onlyUpToNewestEvent here??? */ true, includeCombinedWindForAllLegMiddles); return result; } @@ -1847,7 +1847,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S boolean extrapolate, LegIdentifier simulationLegIdentifier, byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID, Date timeToGetTheEstimatedDurationFor, boolean estimatedDurationRequired) throws NoWindException { - Duration estimatedDuration = null; final HashSet raceCompetitorIdsAsStrings; final TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); // if md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID is null, Arrays.equals will return false, and the @@ -1860,10 +1859,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceCompetitorIdsAsStrings.add(c.getId().toString()); } } - if(estimatedDurationRequired){ - estimatedDuration = getEstimationForTargetTime(timeToGetTheEstimatedDurationFor, estimatedDuration, trackedRace); + final Duration estimatedDuration; + if (estimatedDurationRequired) { + estimatedDuration = getEstimationForTargetTime(timeToGetTheEstimatedDurationFor, trackedRace); + } else { + estimatedDuration = null; } - final Map> boatPositions = getBoatPositionsInternal(raceIdentifier, fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate); final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date); @@ -1874,18 +1875,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S SimulationService simulationService = getService().getSimulationService(); simulationResultVersion = simulationService.getSimulationResultsVersion(simulationLegIdentifier); } - return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks, simulationResultVersion, raceCompetitorIdsAsStrings, estimatedDuration); } - private Duration getEstimationForTargetTime(Date time, Duration estimatedDuration, final TrackedRace trackedRace) { - if(trackedRace != null){ + private Duration getEstimationForTargetTime(Date time, final TrackedRace trackedRace) { + Duration estimatedDuration = null; + if (trackedRace != null) { try { estimatedDuration = trackedRace.getEstimatedTimeToComplete(new MillisecondsTimePoint(time)).getExpectedDuration(); } catch (NotEnoughDataHasBeenAddedException | NoWindException e) { - e.printStackTrace(); - } finally{ + logger.log(Level.WARNING, "Problem computing the estimated race duration", e); } } return estimatedDuration; @@ -2079,6 +2079,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceTimesInfo.newestTrackingEvent = trackedRace.getTimePointOfNewestEvent() == null ? null : trackedRace.getTimePointOfNewestEvent().asDate(); raceTimesInfo.endOfTracking = trackedRace.getEndOfTracking() == null ? null : trackedRace.getEndOfTracking().asDate(); raceTimesInfo.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate(); + raceTimesInfo.raceFinishedTime = trackedRace.getFinishedTime() == null ? null : trackedRace.getFinishedTime().asDate(); raceTimesInfo.delayToLiveInMs = trackedRace.getDelayToLiveInMillis(); Iterable>> markPassingsTimes = trackedRace.getMarkPassingsTimes(); @@ -2833,7 +2834,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Iterable configs = swissTimingAdapterPersistence.getSwissTimingConfigurations(); List result = new ArrayList(); for (SwissTimingConfiguration stConfig : configs) { - result.add(new SwissTimingConfigurationDTO(stConfig.getName(), stConfig.getJsonURL(), stConfig.getHostname(), stConfig.getPort())); + result.add(new SwissTimingConfigurationDTO(stConfig.getName(), stConfig.getJsonURL(), + stConfig.getHostname(), stConfig.getPort(), stConfig.getUpdateURL(), stConfig.getUpdateUsername(), + stConfig.getUpdatePassword())); } return result; } @@ -2871,9 +2874,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port) { + public void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) { if (!jsonURL.equalsIgnoreCase("test")) { - swissTimingAdapterPersistence.storeSwissTimingConfiguration(swissTimingFactory.createSwissTimingConfiguration(configName, jsonURL, hostname, port)); + swissTimingAdapterPersistence.storeSwissTimingConfiguration(swissTimingFactory.createSwissTimingConfiguration(configName, jsonURL, hostname, port, updateURL, updateUsername, updatePassword)); } } @@ -2889,7 +2892,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, - boolean trackWind, final boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception { + boolean trackWind, final boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, + String updateURL, String updateUsername, String updatePassword) throws InterruptedException, ParseException, Exception { logger.info("tracWithSwissTiming for regatta " + regattaToAddTo + " for race records " + rrs + " with hostname " + hostname + " and port " + port); Map cachedRegattaEntriesLists = new HashMap(); @@ -2914,7 +2918,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S getSwissTimingAdapter().addSwissTimingRace(getService(), regattaToAddTo, rr.raceId, rr.getName(), raceDescription, boatClass, hostname, port, startList, getRaceLogStore(), getRegattaLogStore(), - RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, useInternalMarkPassingAlgorithm, trackWind, correctWindByDeclination); + RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, useInternalMarkPassingAlgorithm, trackWind, + correctWindByDeclination, updateURL, updateUsername, updatePassword); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java index 8926b48cdd5..eab41710b60 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceTimesInfoDTO.java @@ -19,6 +19,7 @@ public class RaceTimesInfoDTO implements IsSerializable { public Date startOfTracking; public Date endOfTracking; public Date endOfRace; + public Date raceFinishedTime; public Date newestTrackingEvent; /** @@ -83,6 +84,10 @@ public class RaceTimesInfoDTO implements IsSerializable { public Date getEndOfRace() { return endOfRace; } + + public Date getFinishedTime() { + return raceFinishedTime; + } public void setEndOfRace(Date endOfRace) { this.endOfRace = endOfRace; @@ -118,7 +123,7 @@ public class RaceTimesInfoDTO implements IsSerializable { + ", legInfos=" + legInfos + ", markPassingTimes=" + markPassingTimes + ", startOfRace=" + startOfRace + ", startOfTracking=" + startOfTracking + ", endOfTracking=" - + endOfTracking + ", endOfRace=" + endOfRace + + endOfTracking + ", endOfRace=" + endOfRace + ", finishedTime=" + raceFinishedTime + ", newestTrackingEvent=" + newestTrackingEvent + ", currentServerTime=" + currentServerTime + ", delayToLiveInMs=" + delayToLiveInMs + "]"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java index 45ebd1fb69b..5d95cc9e2d3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingConfigurationDTO.java @@ -8,14 +8,33 @@ public class SwissTimingConfigurationDTO implements IsSerializable { private String hostname; private Integer port; + /** + * The URL to use for updates fed back to the SwissTiming server, such as changes of race timings + * or course updates. + */ + private String updateURL; + + /** + * The username to use as part of the credentials for requests to the {@link #updateURL}. + */ + private String updateUsername; + + /** + * The password to use as part of the credentials for requests to the {@link #updateURL}. + */ + private String updatePassword; + public SwissTimingConfigurationDTO() {} - public SwissTimingConfigurationDTO(String name, String jsonURL, String hostname, Integer port) { + public SwissTimingConfigurationDTO(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) { super(); this.name = name; this.jsonURL = jsonURL; this.hostname = hostname; this.port = port; + this.updateURL = updateURL; + this.updateUsername = updateUsername; + this.updatePassword = updatePassword; } public String getName() { @@ -33,5 +52,16 @@ public class SwissTimingConfigurationDTO implements IsSerializable { public Integer getPort() { return port; } - + + public String getUpdateURL() { + return updateURL; + } + + public String getUpdateUsername() { + return updateUsername; + } + + public String getUpdatePassword() { + return updatePassword; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java index fc7852d9dcf..9407c7cae94 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java @@ -58,6 +58,8 @@ import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternDisplay; import com.sap.sailing.simulator.util.SailingSimulatorConstants; import com.sap.sse.common.impl.RGBColor; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.player.Timer; @@ -998,7 +1000,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ } } else { - Window.alert("No course set, please initialize the course with Start-End input"); + Notification.notify("No course set, please initialize the course with Start-End input", NotificationType.ERROR); } } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java index 9200abde2d4..bb521d61c82 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/SwissTimingConnectivityParamsLoadAndStoreTest.java @@ -53,7 +53,8 @@ public class SwissTimingConnectivityParamsLoadAndStoreTest extends AbstractConne final SwissTimingTrackingConnectivityParameters stParams = new SwissTimingTrackingConnectivityParameters( hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, SwissTimingFactory.INSTANCE, new SwissTimingAdapterFactoryImpl().getOrCreateSwissTimingAdapter(domainObjectFactory.getBaseDomainFactory()).getSwissTimingDomainFactory(), - /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination); + /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, + /* updateURL */ null, /* updateUsername */ null, /* updatePassword */ null); // store mongoObjectFactory.addConnectivityParametersForRaceToRestore(stParams); // load @@ -103,7 +104,8 @@ public class SwissTimingConnectivityParamsLoadAndStoreTest extends AbstractConne final SwissTimingTrackingConnectivityParameters stParams = new SwissTimingTrackingConnectivityParameters( hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, SwissTimingFactory.INSTANCE, new SwissTimingAdapterFactoryImpl().getOrCreateSwissTimingAdapter(domainObjectFactory.getBaseDomainFactory()).getSwissTimingDomainFactory(), - /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination); + /* raceLogStore */ null, /* regattaLogStore */ null, useInternalMarkPassingAlgorithm, trackWind, correctWindDirectionByMagneticDeclination, + /* updateURL */ null, /* updateUsername */ null, /* updatePassword */ null); // store mongoObjectFactory.addConnectivityParametersForRaceToRestore(stParams); // load diff --git a/java/com.sap.sailing.polars.datamining.shared/.project b/java/com.sap.sailing.polars.datamining.shared/.project index b6b59d79740..6dc505ab246 100644 --- a/java/com.sap.sailing.polars.datamining.shared/.project +++ b/java/com.sap.sailing.polars.datamining.shared/.project @@ -21,12 +21,12 @@
- com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/BackendPolarsBoatClassRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/BackendPolarsBoatClassRetrievalProcessor.java index 420be5961ba..9d37dbd8d94 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/BackendPolarsBoatClassRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/BackendPolarsBoatClassRetrievalProcessor.java @@ -1,9 +1,11 @@ package com.sap.sailing.polars.datamining.components; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ExecutorService; -import java.util.stream.Collectors; +import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.polars.datamining.data.HasBackendPolarBoatClassContext; import com.sap.sailing.polars.datamining.data.impl.BoatClassWithBackendPolarContext; @@ -20,11 +22,15 @@ public class BackendPolarsBoatClassRetrievalProcessor extends AbstractRetrievalP @Override protected Iterable retrieveData(RacingEventService element) { + Set data = new HashSet<>(); PolarDataService polarDataService = element.getPolarDataService(); - return polarDataService.getAllBoatClassesWithPolarSheetsAvailable() - .stream() - .map(bc -> new BoatClassWithBackendPolarContext(bc, polarDataService)) - .collect(Collectors.toSet()); + for (BoatClass boatClass : polarDataService.getAllBoatClassesWithPolarSheetsAvailable()) { + if (isAborted()) { + break; + } + data.add(new BoatClassWithBackendPolarContext(boatClass, polarDataService)); + } + return data; } } diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarCompetitorRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarCompetitorRetrievalProcessor.java index 76303f5dff1..537fe2ab383 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarCompetitorRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarCompetitorRetrievalProcessor.java @@ -25,6 +25,9 @@ public class PolarCompetitorRetrievalProcessor extends AbstractRetrievalProcesso TrackedRace trackedRace = element.getTrackedRace(); Set competitorWithContext = new HashSet<>(); for (Competitor competitor : trackedRace.getRace().getCompetitors()) { + if (isAborted()) { + break; + } competitorWithContext.add(new CompetitorWithPolarContext(competitor, trackedRace, element.getLeg(), element)); } return competitorWithContext; diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarFleetRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarFleetRetrievalProcessor.java index c0dda64feab..9cd650c4a61 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarFleetRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarFleetRetrievalProcessor.java @@ -25,6 +25,9 @@ public class PolarFleetRetrievalProcessor extends AbstractRetrievalProcessor fleetWithContext = new HashSet<>(); RaceColumn raceColumn = element.getRaceColumn(); for (Fleet fleet : raceColumn.getFleets()) { + if (isAborted()) { + break; + } fleetWithContext.add(new FleetWithPolarContext(fleet, raceColumn, element)); } return fleetWithContext; diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarGPSFixRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarGPSFixRetrievalProcessor.java index 7ff4a486859..29668562f42 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarGPSFixRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarGPSFixRetrievalProcessor.java @@ -59,6 +59,9 @@ public class PolarGPSFixRetrievalProcessor extends AbstractRetrievalProcessor fixes = track.getFixes(startTime, true, finishTime, false); for (GPSFixMoving fix : fixes) { + if (isAborted()) { + break; + } WindWithConfidence> wind = trackedRace.getWindWithConfidence( fix.getPosition(), fix.getTimePoint(), diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardGroupRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardGroupRetrievalProcessor.java index f825b0c536d..2c5af8790ad 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardGroupRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardGroupRetrievalProcessor.java @@ -1,9 +1,11 @@ package com.sap.sailing.polars.datamining.components; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ExecutorService; -import java.util.stream.Collectors; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.polars.datamining.data.HasLeaderboardGroupPolarContext; import com.sap.sailing.polars.datamining.data.impl.LeaderboardGroupWithPolarContext; import com.sap.sailing.server.RacingEventService; @@ -19,11 +21,14 @@ public class PolarLeaderboardGroupRetrievalProcessor extends AbstractRetrievalPr @Override protected Iterable retrieveData(RacingEventService element) { - return element.getLeaderboardGroups() - .values() - .stream() - .map(lg -> new LeaderboardGroupWithPolarContext(lg)) - .collect(Collectors.toSet()); + Set data = new HashSet<>(); + for (LeaderboardGroup leaderboardGroup : element.getLeaderboardGroups().values()) { + if (isAborted()) { + break; + } + data.add(new LeaderboardGroupWithPolarContext(leaderboardGroup)); + } + return data; } } diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardRetrievalProcessor.java index 12e94917ff6..6e0e591fe7c 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLeaderboardRetrievalProcessor.java @@ -23,6 +23,9 @@ public class PolarLeaderboardRetrievalProcessor extends AbstractRetrievalProcess protected Iterable retrieveData(HasLeaderboardGroupPolarContext element) { Set leaderboardsWithContext = new HashSet<>(); for (Leaderboard leaderboard : element.getLeaderboardGroup().getLeaderboards()) { + if (isAborted()) { + break; + } leaderboardsWithContext.add(new LeaderboardWithPolarContext(leaderboard, element)); } return leaderboardsWithContext; diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLegRetrievalProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLegRetrievalProcessor.java index 84350cc7c47..b6107d19d45 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLegRetrievalProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/PolarLegRetrievalProcessor.java @@ -35,6 +35,9 @@ public class PolarLegRetrievalProcessor extends AbstractRetrievalProcessor raceColumnWithContext = new HashSet<>(); Leaderboard leaderboard = element.getLeaderboard(); for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + if (isAborted()) { + break; + } raceColumnWithContext.add(new RaceColumnWithPolarContext(raceColumn, element)); } return raceColumnWithContext; diff --git a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/aggregators/PolarBackendDataAggregationProcessor.java b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/aggregators/PolarBackendDataAggregationProcessor.java index 10a9e8eceef..86a3216bc25 100644 --- a/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/aggregators/PolarBackendDataAggregationProcessor.java +++ b/java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/components/aggregators/PolarBackendDataAggregationProcessor.java @@ -100,10 +100,17 @@ public class PolarBackendDataAggregationProcessor extends AbstractParallelGroupe } catch (NotEnoughDataHasBeenAddedException e) { hasDownwindAngleData = false; } + for (int angleInDeg = 0; angleInDeg < 360; angleInDeg++) { + if (isAborted()) { + break; + } int convertedAngle = angleInDeg > 180 ? angleInDeg - 360 : angleInDeg ; try { for (int x = 0; x < 30; x++) { + if (isAborted()) { + break; + } SpeedWithConfidence speed = polarDataService.getSpeed(boatClass, new KnotSpeedImpl(x), new DegreeBearingImpl(convertedAngle)); if (speed.getConfidence() > 0.1) { hasDataForAngle[angleInDeg] = true; @@ -127,6 +134,9 @@ public class PolarBackendDataAggregationProcessor extends AbstractParallelGroupe private void setArrayValuesForFunction(PolynomialFunction function, double[] yOverWindSpeed) { for (int x = 0; x < 30; x++) { + if (isAborted()) { + break; + } yOverWindSpeed[x] = function.value(x); } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index a6b97c92076..7ac6063042a 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -170,8 +170,10 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT if (closestTwsTwa == null) { result = new Pair<>(0.0, null); } else { - double minDiffDeg = Math.abs(Math.abs(Math.abs(closestTwsTwa.getObject().getBearing().getDegrees() * 2) - - Math.abs(courseChangeDeg))); + double targetManeuverAngle = getManeuverAngleInDegreesFromTwa( + closestTwsTwa.getObject().getBearing().getDegrees(), maneuverType); + double minDiffDeg = Math.abs(Math.abs(targetManeuverAngle) + - Math.abs(courseChangeDeg)); result = new Pair<>(1. / (1. + (minDiffDeg / 10.) * (minDiffDeg / 10.)), closestTwsTwa); } return result; @@ -189,7 +191,7 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT : courseChangeDeg >= 0 ? Tack.STARBOARD : Tack.PORT)) { double targetManeuverAngle = getManeuverAngleInDegreesFromTwa( trueWindSpeedAndAngle.getObject().getBearing().getDegrees(), type); - double diff = Math.abs(targetManeuverAngle) - Math.abs(courseChangeDeg); + double diff = Math.abs(Math.abs(targetManeuverAngle) - Math.abs(courseChangeDeg)); if (diff < minDiff) { minDiff = diff; closestTwsTwa = trueWindSpeedAndAngle; diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 81f77630d00..f867c318c06 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -288,7 +288,7 @@ public class PolarDataMiner { } else if (legType.equals(LegType.DOWNWIND)) { CubicEquation downWindEquation = new CubicEquation(0.0003, -0.0373, 1.5213, -2.1309 - speedOverGround.getKnots()); - int angle = 30 * tackFactor; + int angle = 150 * tackFactor; solveAndAddResults(resultSet, downWindEquation, angle); } return resultSet; diff --git a/java/com.sap.sailing.selenium.test/.classpath b/java/com.sap.sailing.selenium.test/.classpath index 39faae28fbe..1bbc2f48e83 100644 --- a/java/com.sap.sailing.selenium.test/.classpath +++ b/java/com.sap.sailing.selenium.test/.classpath @@ -5,6 +5,5 @@ - diff --git a/java/com.sap.sailing.selenium.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.selenium.test/META-INF/MANIFEST.MF index ede5abfad5a..b76bf165bcc 100644 --- a/java/com.sap.sailing.selenium.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.selenium.test/META-INF/MANIFEST.MF @@ -7,7 +7,7 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.hamcrest;bundle-version="1.1.0", org.junit;bundle-version="4.8.2", - org.openqa.selenium.osgi;bundle-version="2.40.0", + org.openqa.selenium.osgi;bundle-version="3.13.0", com.sap.sse.mongodb;bundle-version="1.0.0", org.mongodb.mongo-java-driver;bundle-version="2.13.0", com.sap.sailing.domain.common, diff --git a/java/com.sap.sailing.selenium.test/ci-test-environment.xml b/java/com.sap.sailing.selenium.test/ci-test-environment.xml index 52c20fe0142..6830888dae7 100644 --- a/java/com.sap.sailing.selenium.test/ci-test-environment.xml +++ b/java/com.sap.sailing.selenium.test/ci-test-environment.xml @@ -44,12 +44,43 @@ webdriver.enable.native.events true - + + + + + + + --> + + --> + + + + + + diff --git a/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (No Proxy, GWT Codesvr).launch b/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (No Proxy, GWT Codesvr).launch index aa09fadd838..b823c8ba200 100644 --- a/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (No Proxy, GWT Codesvr).launch +++ b/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (No Proxy, GWT Codesvr).launch @@ -14,13 +14,7 @@ - - - - - - - + diff --git a/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy, GWT Codesvr).launch b/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy, GWT Codesvr).launch index 93b80d065c2..64f44cf3a8e 100644 --- a/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy, GWT Codesvr).launch +++ b/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy, GWT Codesvr).launch @@ -12,5 +12,5 @@ - + diff --git a/java/com.sap.sailing.selenium.test/local-test-environment.xml b/java/com.sap.sailing.selenium.test/local-test-environment.xml old mode 100755 new mode 100644 index a6bc5f0da0a..cedae7bcc1c --- a/java/com.sap.sailing.selenium.test/local-test-environment.xml +++ b/java/com.sap.sailing.selenium.test/local-test-environment.xml @@ -28,22 +28,65 @@ - http://code.google.com/p/selenium/wiki/SafariDriver - http://code.google.com/p/selenium/wiki/RemoteWebDriverServer - NOTE: For the Chrome and the InternetExplorer driver an additional "server", which acts as a bridge between the + NOTE: For the Chrome, Firefox and InternetExplorer driver an additional "server", which acts as a bridge between the browser and the driver, is needed. --> + webdriver.chrome.driver C:\apps\ChromeSeleniumDriver\chromedriver.exe + + + webdriver.gecko.driver + /path/to/geckodriver.exe + + + - c:\Program Files (x86)\Mozilla Firefox 24\firefox.exe - - - + /path/to/firefox.exe + --> + + @@ -61,4 +104,16 @@ --> + + + diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/ElementSearchCondition.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/ElementSearchCondition.java index 7012013b1d4..de94d6521bc 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/ElementSearchCondition.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/ElementSearchCondition.java @@ -1,8 +1,8 @@ package com.sap.sailing.selenium.core; -import org.openqa.selenium.SearchContext; +import java.util.function.Function; -import com.google.common.base.Function; +import org.openqa.selenium.SearchContext; /** *

Models a condition that might reasonably be expected to eventually evaluate to something that is neither diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/HeadlessChromeDriver.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/HeadlessChromeDriver.java new file mode 100644 index 00000000000..5c916e1fc80 --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/HeadlessChromeDriver.java @@ -0,0 +1,26 @@ +package com.sap.sailing.selenium.core; + +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeDriverService; +import org.openqa.selenium.chrome.ChromeOptions; + +/** + *Specific {@link ChromeDriver} that is configured to start Chrome in headless mode. In theory, you do not need a + * specific subclass of {@link ChromeDriver} but we currently can't apply specific command line options using XML based + * configuration using {@link TestEnvironmentConfiguration}. So this is currently just a simple workaround but not a + * long-term solution. + */ +public class HeadlessChromeDriver extends ChromeDriver { + + public HeadlessChromeDriver(Capabilities capabilities) { + super(ChromeDriverService.createDefaultService(), constructChromeOptions(capabilities)); + } + + private static ChromeOptions constructChromeOptions(Capabilities capabilities) { + final ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.merge(capabilities); + chromeOptions.addArguments("--headless", "--window-size=1440,900"); + return chromeOptions; + } +} diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/HeadlessFirefoxDriver.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/HeadlessFirefoxDriver.java new file mode 100644 index 00000000000..0ec19b60ef6 --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/HeadlessFirefoxDriver.java @@ -0,0 +1,32 @@ +package com.sap.sailing.selenium.core; + +import java.util.Objects; + +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.firefox.FirefoxBinary; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxOptions; + +/** + * Specific {@link FirefoxDriver} that is configured to start Firefox in headless mode. In theory, you do not need a + * specific subclass of {@link FirefoxDriver} but we currently can't apply specific command line options using XML based + * configuration using {@link TestEnvironmentConfiguration}. So this is currently just a simple workaround but not a + * long-term solution. + */ +public class HeadlessFirefoxDriver extends FirefoxDriver { + + public HeadlessFirefoxDriver(Capabilities desiredCapabilities) { + super(constructFirefoxOptions(desiredCapabilities)); + + } + + private static FirefoxOptions constructFirefoxOptions(Capabilities desiredCapabilities) { + final FirefoxOptions firefoxOptions = new FirefoxOptions(Objects.requireNonNull(desiredCapabilities, "No capabilities seen")); + FirefoxBinary firefoxBinary = new FirefoxBinary(); + // Window size is currently being ignored in headless mode + // Documentation says it should work: https://developer.mozilla.org/en-US/Firefox/Headless_mode + firefoxBinary.addCommandLineOptions("-headless"/*, "--window-size=1440,900"*/); + firefoxOptions.setBinary(firefoxBinary); + return firefoxOptions; + } +} diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumElementLocator.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumElementLocator.java index 2c6a6e28124..df0f01ea71f 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumElementLocator.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumElementLocator.java @@ -1,8 +1,9 @@ package com.sap.sailing.selenium.core; import java.lang.reflect.Field; +import java.time.Duration; import java.util.List; -import java.util.concurrent.TimeUnit; +import java.util.function.Function; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; @@ -11,8 +12,6 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; import org.openqa.selenium.support.ui.FluentWait; -import com.google.common.base.Function; - /** *

An element locator, which will lazily locate and wait for an element or an element list to appear, by polling the * UI on a regular basis. It is possible to define the maximum amount of time to wait for the element or the list of @@ -66,8 +65,8 @@ public class SeleniumElementLocator implements ElementLocator { this.context = context; this.wait = new FluentWait<>(this.context); - this.wait.withTimeout(timeOutSeconds, TimeUnit.SECONDS); - this.wait.pollingEvery(intervalMillis, TimeUnit.MILLISECONDS); + this.wait.withTimeout(Duration.ofSeconds(timeOutSeconds)); + this.wait.pollingEvery(Duration.ofMillis(intervalMillis)); this.wait.ignoring(NoSuchElementException.class); Annotations annotations = new Annotations(field); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumFieldDecorator.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumFieldDecorator.java index 2fa72519710..9e0f1c446b6 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumFieldDecorator.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumFieldDecorator.java @@ -9,8 +9,7 @@ import java.lang.reflect.Type; import java.util.List; import org.openqa.selenium.WebElement; - -import org.openqa.selenium.internal.Locatable; +import org.openqa.selenium.interactions.internal.Locatable; import org.openqa.selenium.internal.WrapsElement; import org.openqa.selenium.support.pagefactory.ElementLocator; diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/WindowManager.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/WindowManager.java index a7edd5ef488..bcc70d59ac2 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/WindowManager.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/WindowManager.java @@ -1,10 +1,11 @@ package com.sap.sailing.selenium.core; import java.util.Set; +import java.util.function.BiConsumer; +import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebDriver.TargetLocator; /** *

@@ -25,20 +26,8 @@ public class WindowManager { */ public WindowManager(WebDriver driver) { this.driver = driver; - } - - /** - *

- * - * @param url - */ - public void switchTo(String url) { - WebDriverWindow window = findWindow(url); - if(window == null) - throw new RuntimeException("Window not found"); - - window.switchToWindow(); + setWindowMaximized(); } /** @@ -47,60 +36,24 @@ public class WindowManager { * @return * */ - public WebDriverWindow getCurrentWindow() { + private WebDriverWindow getCurrentWindow() { return new WebDriverWindow(this.driver, this.driver.getWindowHandle()); } - /** - *

- * - *

Note: If no window is found with the specified URL,

- * - * @param url - * - * @return - * - */ - public WebDriverWindow findWindow(String url) { - if(url == null) - throw new IllegalArgumentException(); - - TargetLocator locator = this.driver.switchTo(); - - for(String handle : this.driver.getWindowHandles()) { - locator.window(handle); - - if(url.equals(this.driver.getCurrentUrl())) - return new WebDriverWindow(this.driver, handle); - } - - return null; - } - /** *

* * @return */ - public WebDriverWindow openNewWindow() { + private WebDriverWindow openNewWindow() { return openNewWindow(false); } - public WebDriverWindow openNewWindow(boolean focus) { + private WebDriverWindow openNewWindow(boolean focus) { return openNewWindow("", focus); } - /** - *

- * - * @param url - * @return - */ - public WebDriverWindow openNewWindow(String url) { - return openNewWindow(url, false); - } - - public WebDriverWindow openNewWindow(String url, boolean focus) { + private WebDriverWindow openNewWindow(String url, boolean focus) { WebDriverWindow window = new WebDriverWindow(this.driver, createWindow(url)); if(focus) { @@ -134,4 +87,37 @@ public class WindowManager { return null; } + + public void withExtraWindow(BiConsumer defaultAndExtraWindow) { + final WebDriverWindow defaultWindow = getCurrentWindow(); + final WebDriverWindow extraWindow = openNewWindow(); + extraWindow.switchToWindow(); + setWindowMaximized(); + defaultWindow.switchToWindow(); + try { + defaultAndExtraWindow.accept(defaultWindow, extraWindow); + } finally { + try { + extraWindow.close(); + } catch (Exception e) { + // This call may fail depending on the WebDriver being used + } + defaultWindow.switchToWindow(); + } + } + + private void setWindowMaximized() { + try { + driver.manage().window().maximize(); + } catch (Exception e) { + // Depending on the combination of OS and WebDriver implementation this may fail + // e.g. chrome with xvfb can't do this successfully. + try { + // Trying to set a proper screen size as fallback that should usable with all modern screens + driver.manage().window().setSize(new Dimension(1440, 900)); + } catch (Exception exc) { + // In this case we just can't change the window + } + } + } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/HostPage.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/HostPage.java index c928a649578..b4c341394c5 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/HostPage.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/HostPage.java @@ -3,6 +3,7 @@ package com.sap.sailing.selenium.pages; import java.net.URI; import java.net.URISyntaxException; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; /** @@ -18,9 +19,9 @@ public abstract class HostPage extends PageObject { protected static final String NO_CODE_SERVER_PARAMTER_VALUE = ""; //$NON-NLS-1$ /** - *

The default timeout of 60 seconds for the initialization of the page object.

+ *

The default timeout of 120 seconds for the initialization of the page object.

*/ - protected static final int DEFAULT_PAGE_LOAD_TIMEOUT = 120; + protected static final int DEFAULT_PAGE_LOAD_TIMEOUT_IN_SECONDS = 120; public static final String getGWTCodeServerAndLocale() { StringBuilder queryBuilder = new StringBuilder("locale=en"); @@ -64,13 +65,12 @@ public abstract class HostPage extends PageObject { */ @Override protected void initElements() { - waitForAjaxRequests(getPageLoadTimeOut(), 5); - + waitForAjaxRequests(getPageLoadTimeOutInSeconds(), 5 /* seconds */); super.initElements(); } - protected int getPageLoadTimeOut() { - return DEFAULT_PAGE_LOAD_TIMEOUT; + protected int getPageLoadTimeOutInSeconds() { + return DEFAULT_PAGE_LOAD_TIMEOUT_IN_SECONDS; } protected interface HostPageSupplier { @@ -80,4 +80,8 @@ public abstract class HostPage extends PageObject { public String getCurrentUrl() { return driver.getCurrentUrl(); } + + protected void scrollToTop() { + ((JavascriptExecutor) driver).executeScript("window.scrollTo(0, 0);"); + } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/PageObject.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/PageObject.java index f5b00515f8e..3809fdd6fcd 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/PageObject.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/PageObject.java @@ -1,18 +1,19 @@ package com.sap.sailing.selenium.pages; import java.text.MessageFormat; +import java.time.Duration; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import org.openqa.selenium.Alert; import org.openqa.selenium.By; -import org.openqa.selenium.NoAlertPresentException; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -21,8 +22,6 @@ import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.WebDriverWait; -import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.sap.sailing.selenium.core.AjaxCallsComplete; import com.sap.sailing.selenium.core.AjaxCallsExecuted; import com.sap.sailing.selenium.core.BySeleniumId; @@ -60,7 +59,7 @@ public class PageObject { public static final int DEFAULT_WAIT_TIMEOUT_SECONDS = 120; - public static final int DEFAULT_POLLING_INTERVAL = 5; + public static final int DEFAULT_POLLING_INTERVAL = 1; private static final MessageFormat TAB_PANEL_EXPRESSION = new MessageFormat( ".//div[contains(@class, \"gwt-TabBarItem\")]/div[text()=\"{0}\"]/.."); @@ -132,15 +131,15 @@ public class PageObject { * * @param input * - * @param timeout + * @param timeoutInSeconds * - * @param polling + * @param pollingEverySoManySeconds * * @return * */ - public static FluentWait createFluentWait(T input, int timeout, int polling) { - return createFluentWait(input, timeout, polling, Collections.>emptyList()); + public static FluentWait createFluentWait(T input, int timeoutInSeconds, int pollingEverySoManySeconds) { + return createFluentWait(input, timeoutInSeconds, pollingEverySoManySeconds, Collections.>emptyList()); } /** @@ -168,20 +167,20 @@ public class PageObject { * * @param input * - * @param timeout + * @param timeoutInSeconds * - * @param polling + * @param pollingEverySoManySeconds * * @param exceptions * * @return * */ - public static FluentWait createFluentWait(T input, int timeout, int polling, + public static FluentWait createFluentWait(T input, int timeoutInSeconds, int pollingEverySoManySeconds, Collection> exceptions) { FluentWait wait = new FluentWait<>(input); - wait.withTimeout(timeout, TimeUnit.SECONDS); - wait.pollingEvery(polling, TimeUnit.SECONDS); + wait.withTimeout(Duration.ofSeconds(timeoutInSeconds)); + wait.pollingEvery(Duration.ofSeconds(pollingEverySoManySeconds)); wait.ignoreAll(exceptions); return wait; @@ -223,7 +222,7 @@ public class PageObject { /** *

Initialize the page object. The default implementation use a factory to lazily initialize the elements - * (annotated fields) of the page object using the timeout duration returned by {@link #getPageLoadTimeOut()}. + * (annotated fields) of the page object using the timeout duration returned by {@link #getPageLoadTimeOutInSeconds()}. * To get a field lazily initialized you have to annotate the field either with {@link FindBy} or with * {@link FindBys}. If the element never changes (that is, that the same instance in the DOM can always be used) * it is also possible to use a cache for the lookup by using the annotation {@code CacheLookup} in addition.

@@ -431,15 +430,15 @@ public class PageObject { * and polling interval. In reality, the interval may be greater as the cost of actually evaluating the condition * is not factored in.

* - * @param timeout - * The timeout duration for the waiting. - * @param polling - * The interval in which the check should be performed. + * @param timeoutInSeconds + * The timeout duration for the waiting in seconds. + * @param pollingEverySoManySeconds + * The interval in seconds in which the check should be performed. * @throws org.openqa.selenium.TimeoutException * if the timeout expires. */ - protected void waitForAjaxRequests(int timeout, int polling) { - waitForAjaxRequests(AjaxCallsComplete.CATEGORY_GLOBAL, timeout, polling); + protected void waitForAjaxRequests(int timeoutInSeconds, int pollingEverySoManySeconds) { + waitForAjaxRequests(AjaxCallsComplete.CATEGORY_GLOBAL, timeoutInSeconds, pollingEverySoManySeconds); } /** @@ -449,15 +448,15 @@ public class PageObject { * * @param category * The category of Ajax requests to wait for. - * @param timeout - * The timeout duration for the waiting. - * @param polling - * The interval in which the check should be performed. + * @param timeoutInSeconds + * The timeout duration for the waiting in seconds. + * @param pollingEverySoManySeconds + * The interval in seconds in which the check should be performed. * @throws org.openqa.selenium.TimeoutException * if the timeout expires. */ - protected void waitForAjaxRequests(String category, int timeout, int polling) { - FluentWait wait = createFluentWait(this.driver, timeout, polling); + protected void waitForAjaxRequests(String category, int timeoutInSeconds, int pollingEverySoManySeconds) { + FluentWait wait = createFluentWait(this.driver, timeoutInSeconds, pollingEverySoManySeconds); wait.until(new AjaxCallsComplete(category)); } @@ -484,7 +483,7 @@ public class PageObject { webDriverWait.until(ExpectedConditions.presenceOfElementLocated(new BySeleniumId(seleniumId))); } - protected void waitUntil(Predicate predicate) { + protected void waitUntil(Function predicate) { WebDriverWait webDriverWait = new WebDriverWait(driver, DEFAULT_LOOKUP_TIMEOUT); webDriverWait.until(predicate); } @@ -579,50 +578,85 @@ public class PageObject { /** * Waits for an alert box to appear and accepts the alert. If no alert shows up, an Exception is thrown. */ - protected void waitForAlertAndAccept() throws InterruptedException { - waitForAlertAndAccept(DEFAULT_WAIT_TIMEOUT_SECONDS); + protected void waitForAlertAndAccept() { + waitForAlertAndAccept(DEFAULT_WAIT_TIMEOUT_SECONDS); } - + /** * Waits for an alert box to appear and accepts the alert. If no alert shows up, an Exception is thrown. */ - protected void waitForAlertAndAccept(int timeoutInSeconds) throws InterruptedException { - int i = 0; - while (i < timeoutInSeconds) { - i++; - try { - Alert alert = driver.switchTo().alert(); - alert.accept(); - return; - } catch (NoAlertPresentException e) { - Thread.sleep(1000); - } - } - throw new NoAlertPresentException(); + protected void waitForAlertAndAccept(int timeoutInSeconds) { + final Alert expectedAlert = new WebDriverWait(driver, timeoutInSeconds).until(ExpectedConditions.alertIsPresent()); + expectedAlert.accept(); + } + + /** + * Waits for a notification to appear and dismisses the notification by clicking on it. If no notification shows up, an Exception is thrown. + */ + protected void waitForNotificationAndDismiss() { + waitForNotificationAndDismiss(DEFAULT_WAIT_TIMEOUT_SECONDS, null); } /** - * Waits for an alert box to appear and dismisses the alert. If no alert shows up, an Exception is thrown. + * Waits for a specific notification to appear and dismisses the notification by clicking on it. If no notification shows up, an Exception is thrown. */ - protected void waitForAlertAndDismiss() throws InterruptedException { - waitForAlertAndDismiss(DEFAULT_WAIT_TIMEOUT_SECONDS); + protected void waitForNotificationAndDismiss(String expectedNotificationMessage) { + waitForNotificationAndDismiss(DEFAULT_WAIT_TIMEOUT_SECONDS, expectedNotificationMessage); + } + + /** + * Waits for an notification to appear and dismisses the notification by clicking on it. If no notification shows up, an Exception is thrown. + */ + protected void waitForNotificationAndDismiss(int timeoutInSeconds, String expectedNotificationMessage) { + WebDriverWait wait = new WebDriverWait(driver, timeoutInSeconds); + wait.until(new Function() { + + @Override + public Boolean apply(WebDriver t) { + boolean clickedNotifications = false; + try { + List notificationBar = driver.findElements(By.id("notificationBar")); + if (!notificationBar.isEmpty()) { + // we got the enclosing panel + List notifications = notificationBar.get(0).findElements(By.cssSelector("*")); + if (!notifications.isEmpty()) { + for (WebElement messageElement : notifications) { + if (expectedNotificationMessage == null + || messageElement.getText().contains(expectedNotificationMessage)) { + messageElement.click(); + clickedNotifications = true; + } + } + } + } + } catch (Exception e) { + // This call can fail temporarily while notifications are being updated + } + return clickedNotifications; + } + }); } - /** - * Waits for an alert box to appear and dismisses the alert. If no alert shows up, an Exception is thrown. - */ - protected void waitForAlertAndDismiss(int timeoutInSeconds) throws InterruptedException { - int i = 0; - while (i < timeoutInSeconds) { - i++; - try { - Alert alert = driver.switchTo().alert(); - alert.accept(); - return; - } catch (NoAlertPresentException e) { - Thread.sleep(1000); + protected void scrollToView(WebElement webElement) { + ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(false);", webElement); + } + + public boolean isElementEntirelyVisible(WebElement element) { + try { + if (element.isDisplayed()) { + final int windowWidth = driver.manage().window().getSize().getWidth(); + if (windowWidth >= element.getLocation().x + + element.getSize().width) { + return true; + } else { + return false; + } + } else { + return false; } + } catch (Exception e) { + // The element may currently only partially visible which makes some of the calls fail + return false; } - throw new NoAlertPresentException(); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/ActionsHelper.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/ActionsHelper.java index 1948e54fac6..76528e6bdb0 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/ActionsHelper.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/ActionsHelper.java @@ -1,5 +1,7 @@ package com.sap.sailing.selenium.pages.adminconsole; +import java.util.function.Function; + import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.NoAlertPresentException; @@ -9,7 +11,6 @@ import org.openqa.selenium.WebDriver.TargetLocator; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.FluentWait; -import com.google.common.base.Function; import com.sap.sailing.selenium.pages.PageObject; public class ActionsHelper { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/advanced/MasterDataImportPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/advanced/MasterDataImportPO.java index 17c266a9053..2c31036b3b7 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/advanced/MasterDataImportPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/advanced/MasterDataImportPO.java @@ -55,6 +55,6 @@ public class MasterDataImportPO extends PageArea { } new Select(leaderBoardGroupListBox).selectByValue(eventName); importBtn.click(); - waitForAlertAndDismiss(); + waitForNotificationAndDismiss(); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/igtimi/IgtimiAccountsManagementPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/igtimi/IgtimiAccountsManagementPanelPO.java index cced2f9086d..a560c72b046 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/igtimi/IgtimiAccountsManagementPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/igtimi/IgtimiAccountsManagementPanelPO.java @@ -41,6 +41,6 @@ public class IgtimiAccountsManagementPanelPO extends PageArea { addIgtimiAccountDialog.setPassword(password); addIgtimiAccountDialog.pressOk(); - waitForAlertAndAccept(10); + waitForNotificationAndDismiss(); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java index ff2252823c4..acfa69abe74 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java @@ -1,15 +1,16 @@ package com.sap.sailing.selenium.pages.adminconsole.regatta; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; - import com.sap.sailing.selenium.pages.PageArea; - import com.sap.sailing.selenium.pages.adminconsole.ActionsHelper; - import com.sap.sailing.selenium.pages.gwt.CellTablePO; import com.sap.sailing.selenium.pages.gwt.DataEntryPO; import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO; @@ -85,4 +86,17 @@ public class RegattaDetailsCompositePO extends PageArea { private CellTablePO getSeriesTable() { return new GenericCellTablePO<>(this.driver, this.seriesTable, DataEntryPO.class); } + + public List getRaceNames(String seriesName) { + final DataEntryPO seriesEntry = findSeries(seriesName); + final String racesColumnContent = seriesEntry.getColumnContent("Races"); + if (racesColumnContent != null && ! racesColumnContent.isEmpty()) { + return Arrays.asList(racesColumnContent.split(", ")); + } + return Collections.emptyList(); + } + + public void waitForRacesOfSeries(final String series, final List races) { + waitUntil(() -> getRaceNames(series).equals(races)); + } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaStructureManagementPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaStructureManagementPanelPO.java index 10683cc2646..fbfbb3dc18a 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaStructureManagementPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaStructureManagementPanelPO.java @@ -1,7 +1,10 @@ package com.sap.sailing.selenium.pages.adminconsole.regatta; +import java.util.function.Function; + import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; @@ -64,7 +67,7 @@ public class RegattaStructureManagementPanelPO extends PageArea { } private DefaultRegattaLeaderboardCreateDialogPO createDefaultRegattaLeaderboard() { - WebElement dialog = findElementBySeleniumId(this.driver, "CreateDefaultRegattaLeaderboardDialog"); //$NON-NLS-1$ + final WebElement dialog = waitForElementBySeleniumId(this.driver, "CreateDefaultRegattaLeaderboardDialog", 10); //$NON-NLS-1$ return new DefaultRegattaLeaderboardCreateDialogPO(this.driver, dialog); } @@ -80,9 +83,14 @@ public class RegattaStructureManagementPanelPO extends PageArea { } public RegattaDetailsCompositePO getRegattaDetails() { - if (this.regattaDetails.isDisplayed()) { - return new RegattaDetailsCompositePO(this.driver, this.regattaDetails); - } - return null; + return new WebDriverWait(driver, DEFAULT_WAIT_TIMEOUT_SECONDS).until(new Function() { + @Override + public RegattaDetailsCompositePO apply(WebDriver t) { + if (regattaDetails.isDisplayed()) { + return new RegattaDetailsCompositePO(driver, regattaDetails); + } + return null; + } + }); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/SeriesEditDialogPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/SeriesEditDialogPO.java index 911fe9cb730..217c8a43ae8 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/SeriesEditDialogPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/SeriesEditDialogPO.java @@ -6,6 +6,7 @@ import java.util.List; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.Select; import com.sap.sailing.selenium.core.BySeleniumId; @@ -121,9 +122,12 @@ public class SeriesEditDialogPO extends DataEntryDialogPO { for(WebElement editorRow : getRaceNameEditors()) { WebElement valueTextBox = findElementBySeleniumId(editorRow, "ValueTextBox"); - if(races.contains(valueTextBox.getAttribute("value"))) { + if (races.contains(valueTextBox.getAttribute("value"))) { WebElement removeButton = findElementBySeleniumId(editorRow, "RemoveButton"); - removeButton.click(); + // A simple "removeButton.click();" does not work on GeckoDriver here. + // This only presses the button down but a click isn't detected. + // It seems that the mouseup event isn't correctly fired, why we use this workaround. + new Actions(driver).sendKeys(removeButton, "\13").perform(); } } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java index 8767e0b2746..faa3e8c047d 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java @@ -6,13 +6,13 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.function.Function; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.FluentWait; -import com.google.common.base.Function; import com.sap.sailing.domain.common.BoatClassMasterdata; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java index 751a810f612..e811fd9c504 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java @@ -1,16 +1,17 @@ package com.sap.sailing.selenium.pages.adminconsole.tractrac; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Set; -import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedCondition; -import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.Select; import com.sap.sailing.domain.common.BoatClassMasterdata; @@ -45,7 +46,7 @@ public class TracTracEventManagementPanelPO extends PageArea { @Override public int hashCode() { - return Objects.hash(this.boatClass, this.eventName, this.raceName); + return Objects.hash(BoatClassMasterdata.resolveBoatClass(this.boatClass), this.eventName, this.raceName); } @Override @@ -228,44 +229,53 @@ public class TracTracEventManagementPanelPO extends PageArea { } public void startTrackingForRace(TrackableRaceDescriptor race) { - //startTrackingForRaces(Arrays.asList(race)); - CellTablePO table = getTrackableRacesTable(); - DataEntryPO entryToSelect = null; - for (DataEntryPO entry : table.getEntries()) { - TrackableRaceDescriptor entryDiscribtor = new TrackableRaceDescriptor(entry.getColumnContent("Event"), - entry.getColumnContent("Race"), entry.getColumnContent("Boat Class")); - if (race.equals(entryDiscribtor)) { - entryToSelect = entry; - break; - } - } - table.selectEntry(entryToSelect); - startTrackingAndWaitForAjaxRequests(); + startTrackingForRacesInternal(Collections.singletonList(race), null, false); } - public void startTrackingForRaces(List races) { - List racesToProcess = new ArrayList<>(races); - CellTablePO table = getTrackableRacesTable(); + public void startTrackingForRacesAndAcceptDefaultRegattaWarning(TrackableRaceDescriptor race) { + startTrackingForRacesInternal(Collections.singletonList(race), null, true); + } + + public void startTrackingForRaceAndAwaitBoatClassError(TrackableRaceDescriptor race, String expectedBoatClass) { + startTrackingForRacesInternal(Collections.singletonList(race), expectedBoatClass, false); + } + + public void startTrackingForRaces(Collection races) { + startTrackingForRacesInternal(races, null, false); + } + + private void startTrackingForRacesInternal(Collection races, + String expectedBoatClassErrorOrNull, boolean awaitDefaultRegattaAlert) { + final Set racesToProcess = new HashSet<>(races); + final CellTablePO table = getTrackableRacesTable(); table.selectEntries(e -> racesToProcess.remove(new TrackableRaceDescriptor(e.getColumnContent("Event"), - e.getColumnContent("Race"), e.getColumnContent("Boat Class")))); - if(!racesToProcess.isEmpty()) { + e.getColumnContent("Race"), e.getColumnContent("Boat Class"))), racesToProcess::isEmpty); + if (!racesToProcess.isEmpty()) { throw new IllegalStateException("Not all given races where selected"); } - startTrackingAndWaitForAjaxRequests(); + startTrackingForSelectedRaces(expectedBoatClassErrorOrNull, awaitDefaultRegattaAlert); } public void startTrackingForAllRaces() { getTrackableRacesTable().selectAllEntries(); - startTrackingAndWaitForAjaxRequests(); + startTrackingForSelectedRaces(null, false); } - private void startTrackingAndWaitForAjaxRequests() { - this.startTrackingButton.click(); - ExpectedCondition condition = ExpectedConditions.alertIsPresent(); - if (condition.apply(this.driver) == null) { + private void startTrackingForSelectedRaces(String expectedBoatClassErrorOrNull, boolean awaitDefaultRegattaAlert) { + this.startTracking(); + + if (awaitDefaultRegattaAlert) { + waitForAlertAndAccept(); + } else if (expectedBoatClassErrorOrNull == null) { waitForAjaxRequests(); + } else { + waitForSelectedRacesContainDifferentBoatClassesError(expectedBoatClassErrorOrNull); } } + + private void startTracking() { + this.startTrackingButton.click(); + } public TrackedRacesListPO getTrackedRacesList() { return new TrackedRacesListPO(this.driver, this.trackedRacesListComposite); @@ -281,4 +291,11 @@ public class TracTracEventManagementPanelPO extends PageArea { if(input.isSelected() != selected) input.click(); } + + private void waitForSelectedRacesContainDifferentBoatClassesError(String boatClass) { + String message = String.format("The selected races contain boat classes which are not the same as " + + "the boat class '%s' of the selected regatta.", boatClass); + + waitForNotificationAndDismiss(message); + } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java index 030bc232771..c6ad80bb74e 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java @@ -1,11 +1,12 @@ package com.sap.sailing.selenium.pages.adminconsole.wind; +import java.util.function.Function; + import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.FluentWait; -import com.google.common.base.Function; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.PageArea; diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayConfiguration.java index d2842d0ba10..eaf9b5aca8e 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayConfiguration.java @@ -2,9 +2,7 @@ package com.sap.sailing.selenium.pages.autoplay; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.WebDriverWait; -import com.google.common.base.Predicate; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.PageArea; @@ -31,15 +29,8 @@ public class AutoPlayConfiguration extends PageArea { ListBoxPO.create(driver, configurationSelectionBox).selectOptionByLabel(mode); ListBoxPO.create(driver, eventSelectionBox).selectOptionByLabel(event); ListBoxPO.create(driver, leaderboardSelectionBox).selectOptionByLabel(leaderboard); - WebDriverWait wait = new WebDriverWait(driver, 10); //wait till the page processed the changes - wait.until(new Predicate() { - - @Override - public boolean apply(WebDriver arg0) { - return getConfiguredUrl() != null && !getConfiguredUrl().isEmpty(); - } - }); + waitUntil(() -> getConfiguredUrl() != null && !getConfiguredUrl().isEmpty()); } public String getConfiguredUrl() { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java index 2dfb1fa7fdd..6eb5e109618 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java @@ -2,9 +2,7 @@ package com.sap.sailing.selenium.pages.autoplay; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.WebDriverWait; -import com.google.common.base.Function; import com.sap.sailing.selenium.pages.PageArea; import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO; @@ -13,35 +11,9 @@ public class AutoPlayLeaderboardView extends PageArea { super(driver, element); } - public LeaderboardTablePO getLeaderBoard() { - WebElement leaderboardCellTable = new WebDriverWait(driver, 30).until(new Function() { - @Override - public WebElement apply(WebDriver driver) { - WebElement leaderboardCellTable = findElementOrNullBySeleniumId("LeaderboardCellTable"); - if (leaderboardCellTable != null) { - try { - if (leaderboardCellTable.isDisplayed()) { - final int windowWidth = driver.manage().window().getSize().getWidth(); - if (windowWidth >= leaderboardCellTable.getLocation().x - + leaderboardCellTable.getSize().width) { - return leaderboardCellTable; - } else { - return null; - } - } else { - return null; - } - } catch (Exception e) { - // The element may currently only partially visible which makes some of the calls fail - // In this case it is necessary to wait for the next loop - return null; - } - } else { - return null; - } - } - }); - return new LeaderboardTablePO(this.driver, leaderboardCellTable); + public LeaderboardTablePO getLeaderBoardWithData() { + LeaderboardTablePO leaderboardTablePO = new LeaderboardTablePO(this.driver, findElementBySeleniumId("LeaderboardCellTable")); + leaderboardTablePO.waitForTableToShowData(); + return leaderboardTablePO; } - } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayPage.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayPage.java index c37d75e80f6..d06aa9931e9 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayPage.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayPage.java @@ -4,7 +4,9 @@ import java.io.UnsupportedEncodingException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; +import com.google.common.base.Function; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.HostPage; @@ -21,9 +23,6 @@ public class AutoPlayPage extends HostPage { @FindBy(how = BySeleniumId.class, using = "AutoPlayStartView") private WebElement autoPlayStartView; - @FindBy(how = BySeleniumId.class, using = "LeaderboardView") - private WebElement autoPlayLeaderboardView; - @FindBy(how = BySeleniumId.class, using = "IdleNextUpView") private WebElement idleNextUpView; @@ -76,7 +75,22 @@ public class AutoPlayPage extends HostPage { public AutoPlayLeaderboardView goToAutoPlayClassicUrl(WebDriver driver, String url) { driver.get(url); //$NON-NLS-1$ - return new AutoPlayLeaderboardView(driver, autoPlayLeaderboardView); + final WebElement leaderboardViewElement = new WebDriverWait(driver, 30).until(new Function() { + @Override + public WebElement apply(WebDriver driver) { + final WebElement leaderboardViewElement = findElementOrNullBySeleniumId("LeaderboardView"); + if (leaderboardViewElement != null) { + if (isElementEntirelyVisible(leaderboardViewElement)) { + return leaderboardViewElement; + } else { + return null; + } + } else { + return null; + } + } + }); + return new AutoPlayLeaderboardView(driver, leaderboardViewElement); } public AutoPlayUpcomingView goToAutoPlaySixtyInchUrl(WebDriver webDriver, String url) { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java index f36ebdfeb09..d1bb387e61f 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java @@ -1,13 +1,13 @@ package com.sap.sailing.selenium.pages.autoplay; import java.util.List; +import java.util.function.Function; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.WebDriverWait; -import com.google.common.base.Function; import com.sap.sailing.selenium.pages.PageArea; public class AutoPlayUpcomingView extends PageArea { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/common/DataEntryDialogPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/common/DataEntryDialogPO.java index 2fa9d9ec354..0e3859c3e84 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/common/DataEntryDialogPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/common/DataEntryDialogPO.java @@ -1,10 +1,14 @@ package com.sap.sailing.selenium.pages.common; +import java.util.function.BooleanSupplier; + import org.openqa.selenium.Alert; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; @@ -25,6 +29,9 @@ public abstract class DataEntryDialogPO extends PageArea { protected DataEntryDialogPO(WebDriver driver, WebElement element) { super(driver, element); + + // This ensures that we wait until the dialog is opened and not just attached to the DOM + waitUntil(element::isDisplayed); } @Override @@ -52,23 +59,41 @@ public abstract class DataEntryDialogPO extends PageArea { } public void pressOk() { - pressOk(false); + pressOk(false, true); } - public void pressOk(boolean accept) { + public void pressOk(boolean acceptAlert, boolean waitForAjaxRequests) { + // This generically triggers revalidation in dialogs to ensure that the ok button gets enabled + ((JavascriptExecutor) driver).executeScript("!!document.activeElement ? document.activeElement.blur() : 0"); + + scrollToView(this.okButton); + // Browsers may use smooth scrolling + waitUntil(() -> isElementEntirelyVisible(this.okButton) && this.okButton.isEnabled()); this.okButton.click(); - ExpectedCondition condition = ExpectedConditions.alertIsPresent(); - Alert alert = condition.apply(this.driver); - - if(alert != null && accept) { + if (acceptAlert) { + final Alert alert = new WebDriverWait(driver, DEFAULT_WAIT_TIMEOUT_SECONDS) + .until(ExpectedConditions.alertIsPresent()); alert.accept(); } - if(alert == null || accept) { + if (waitForAjaxRequests) { // Wait, since we do a callback usually waitForAjaxRequests(); } + + // This waits until the dialog is physically closed to make sure further don't fail because the dialog still covers other elements + waitUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + try { + return !((WebElement) context).isDisplayed(); + } catch (StaleElementReferenceException e) { + // When the element was removed from the DOM, it isn't displayed anymore + return true; + } + } + }); } public void pressCancel() { @@ -78,15 +103,7 @@ public abstract class DataEntryDialogPO extends PageArea { public void pressMakeDefault() { WebElement element = findElementBySeleniumId(ID_MAKE_DEFAULT_BUTTON); element.click(); - ExpectedCondition condition = ExpectedConditions.alertIsPresent(); - Alert alert = condition.apply(this.driver); - - if(alert != null) { - alert.accept(); - } else { - waitForAjaxRequests(); - } - + waitForNotificationAndDismiss(); } public boolean isMakeDefaultButtonVisible() { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java index 504c3020a09..f38431dddcd 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.function.BooleanSupplier; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; @@ -258,13 +259,16 @@ public abstract class CellTablePO extends PageArea { * *

Note: If an entry is not contained in the table it will not be selected.

*/ - public void selectEntries(Predicate toSelectPredicate) { + public void selectEntries(Predicate toSelectPredicate, BooleanSupplier canAbort) { for (T entry : getEntries()) { if(toSelectPredicate.test(entry)) { entry.appendToSelection(); } else { entry.deselect(); } + if (canAbort.getAsBoolean()) { + break; + } } } @@ -274,7 +278,7 @@ public abstract class CellTablePO extends PageArea { *

Note: If an entry is not contained in the table it will not be selected.

*/ public void selectAllEntries() { - selectEntries(e -> true); + selectEntries(e -> true, Boolean.FALSE::booleanValue); } /** @@ -343,4 +347,8 @@ public abstract class CellTablePO extends PageArea { return LOADING_ANIMATION_IMAGE.equals(image); } + + public void waitForTableToShowData() { + waitUntil(() -> !getRows().isEmpty()); + } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java index c28a34889b7..9f87f0edd8c 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java @@ -4,16 +4,12 @@ import java.util.List; import org.openqa.selenium.By; import org.openqa.selenium.By.ByXPath; +import org.openqa.selenium.Dimension; import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Action; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.interactions.CompositeAction; -import org.openqa.selenium.interactions.HasInputDevices; -import org.openqa.selenium.interactions.KeyDownAction; -import org.openqa.selenium.interactions.KeyUpAction; -import org.openqa.selenium.interactions.Keyboard; -import org.openqa.selenium.interactions.Mouse; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.common.AttributeHelper; @@ -82,11 +78,33 @@ public class DataEntryPO extends CellTableRowPO { } protected Action getSelectAction() { - Actions actions = new Actions(this.driver); - actions.moveToElement(getElementForSelect(), 1, 1); + final Actions actions = new Actions(this.driver); + final WebElement elementForSelect = getElementForSelect(); + moveToUpperLeftCorner(actions, elementForSelect); actions.click(); - return actions.build(); + final CompositeAction compositeAction = new CompositeAction(); + compositeAction.addAction(new Action() { + @Override + public void perform() { + scrollToView(elementForSelect); + } + }); + compositeAction.addAction(actions.build()); + return compositeAction; + } + + /** + * It seems that the JavaDoc of {@link Actions#moveToElement(WebElement, int, int)} is not consistent to the + * WebDriver specification. The specification says the int params are relative to the center of the element, while + * the JavaDoc says it's relative to the upper-left corner. GeckoDriver implements the specification. This method + * ensures consistent behavior no matter if the implementation is a legacy one or one that conforms to the + * specification. + */ + private void moveToUpperLeftCorner(Actions actions, WebElement webElement) { + actions.moveToElement(webElement); + final Dimension size = webElement.getSize(); + actions.moveByOffset(-size.width / 2 + 1, -size.height / 2 + 1); } /** @@ -110,35 +128,49 @@ public class DataEntryPO extends CellTableRowPO { protected Action getModifiedSelectAction() { Actions actions = new Actions(this.driver); + final WebElement elementToSelect; + final boolean controlClick; if (table.getColumnHeaders().get(0).equals("\u2713")) { // it's a checkbox column - actions.moveToElement(this.columns.isEmpty() ? getWebElement() : this.columns.get(0)); - actions.click(); + elementToSelect = this.columns.isEmpty() ? getWebElement() : this.columns.get(0); + controlClick = false; } else { + elementToSelect = getElementForSelect(); + controlClick = true; + } + if (controlClick) { actions.keyDown(Keys.CONTROL); - actions.moveToElement(getElementForSelect(), 1, 1); - actions.click(); + } + moveToUpperLeftCorner(actions, elementToSelect); + actions.moveToElement(elementToSelect, 1, 1); + actions.click(); + if (controlClick) { actions.keyUp(Keys.CONTROL); } - return actions.build(); + final CompositeAction compositeAction = new CompositeAction(); + compositeAction.addAction(new Action() { + @Override + public void perform() { + scrollToView(elementToSelect); + } + }); + compositeAction.addAction(actions.build()); + return compositeAction; } protected CompositeAction getModifiedCompositeActionAction() { - HasInputDevices devices = (HasInputDevices) this.driver; - - final Mouse mouse = devices.getMouse(); - final Keyboard keyboard = devices.getKeyboard(); - return new CompositeAction() { @Override public void perform() { - Action pressControl = new KeyDownAction(keyboard, mouse, Keys.CONTROL); - pressControl.perform(); + Actions actions = new Actions(driver); + actions.keyDown(Keys.CONTROL); + actions.perform(); super.perform(); - Action releaseControl = new KeyUpAction(keyboard, mouse, Keys.CONTROL); - releaseControl.perform(); + actions = new Actions(driver); + actions.keyUp(Keys.CONTROL); + actions.perform(); } }; } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DateAndTimeInputPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DateAndTimeInputPO.java index 51e005687f3..54dae6eb885 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DateAndTimeInputPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DateAndTimeInputPO.java @@ -4,11 +4,11 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import com.sap.sailing.selenium.core.BySeleniumId; -import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.PageArea; public class DateAndTimeInputPO extends PageArea { @@ -16,15 +16,17 @@ public class DateAndTimeInputPO extends PageArea { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM d, y"); private static final DateFormat TIME_FORMAT_MINUTES = new SimpleDateFormat("h:mm a"); private static final DateFormat TIME_FORMAT_SECONDS = new SimpleDateFormat("h:mm:ss a"); + private static final DateFormat ISO_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + private static final DateFormat ISO_TIME_FORMAT_MINUTES = new SimpleDateFormat("HH:mm"); + private static final DateFormat ISO_TIME_FORMAT_SECONDS = new SimpleDateFormat("HH:mm:ss"); + private static final DateFormat ISO_DATE_TIME_FORMAT_MINUTES = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm"); + private static final DateFormat ISO_DATE_TIME_FORMAT_SECONDS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - @FindBy(how = BySeleniumId.class, using = "dateInput") - private WebElement dateInput; - - @FindBy(how = BySeleniumId.class, using = "timeInput") - private WebElement timeInput; + private final WebElement element; private DateAndTimeInputPO(WebDriver driver, WebElement element) { super(driver, element); + this.element = element; } /** @@ -39,8 +41,27 @@ public class DateAndTimeInputPO extends PageArea { * @see WebElement#sendKeys(CharSequence...) */ public void setValue(Date date, boolean enterSeconds) { - this.setValue(dateInput, date, DATE_FORMAT); - this.setValue(timeInput, date, enterSeconds ? TIME_FORMAT_SECONDS : TIME_FORMAT_MINUTES); + if (isFieldOfType(element, "datetime-local")) { + this.setValueNative(element, date, enterSeconds ? ISO_DATE_TIME_FORMAT_SECONDS : ISO_DATE_TIME_FORMAT_MINUTES); + } else { + final WebElement dateInput = element.findElement(new BySeleniumId("dateInput")); + final WebElement timeInput = element.findElement(new BySeleniumId("timeInput")); + if (isFieldOfType(dateInput, "date")) { + this.setValueNative(dateInput, date, ISO_DATE_FORMAT); + } else { + this.setValue(dateInput, date, DATE_FORMAT); + } + + if (isFieldOfType(timeInput, "time")) { + this.setValueNative(timeInput, date, enterSeconds ? ISO_TIME_FORMAT_SECONDS : ISO_TIME_FORMAT_MINUTES); + } else { + this.setValue(timeInput, date, enterSeconds ? TIME_FORMAT_SECONDS : TIME_FORMAT_MINUTES); + } + } + } + + private boolean isFieldOfType(WebElement inputToCheck, String type) { + return type.equals(inputToCheck.getAttribute("type")); } private void setValue(WebElement input, Date date, DateFormat format) { @@ -49,6 +70,12 @@ public class DateAndTimeInputPO extends PageArea { input.sendKeys(value); input.sendKeys("\t"); // ensure popup closing! } + + private void setValueNative(WebElement input, Date date, DateFormat format) { + final String value = format.format(date); + final JavascriptExecutor javascriptExecutor = ((JavascriptExecutor) driver); + javascriptExecutor.executeScript("arguments[0].value = arguments[1];", input, value); + } public static DateAndTimeInputPO create(WebDriver driver, WebElement element) { return new DateAndTimeInputPO(driver, element); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardPage.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardPage.java index 3d0ce8d1d63..6f1c51babfb 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardPage.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardPage.java @@ -129,6 +129,7 @@ public class LeaderboardPage extends HostPage { } public LeaderboardSettingsDialogPO getLeaderboardSettings() { + scrollToTop(); this.leaderboardSettingsButton.click(); return new LeaderboardSettingsDialogPO(this.driver, findElementBySeleniumId(this.driver, "LeaderboardSettingsDialog")); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java index dd7a042e728..6adaeb9ec78 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java @@ -7,14 +7,10 @@ import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.WebDriverWait; -import com.google.common.base.Predicate; import com.sap.sailing.selenium.pages.common.CSSHelper; - import com.sap.sailing.selenium.pages.gwt.CellTablePO; import com.sap.sailing.selenium.pages.gwt.DataEntryPO; - import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO.LeaderboardEntry; public class LeaderboardTablePO extends CellTablePO { @@ -195,13 +191,4 @@ public class LeaderboardTablePO extends CellTablePO { private WebElement getExpansionButton(WebElement header) { return header.findElement(By.xpath(EXPANSION_BUTTON_XPATH)); } - - public void waitForLeaderboardToHaveData() { - new WebDriverWait(driver, 30).until(new Predicate() { - @Override - public boolean apply(WebDriver driver) { - return !getRows().isEmpty(); - } - }); - } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/RaceBoardPage.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/RaceBoardPage.java index ddd9c286b77..f80fbd7f2b5 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/RaceBoardPage.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/RaceBoardPage.java @@ -3,9 +3,11 @@ package com.sap.sailing.selenium.pages.raceboard; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.function.BooleanSupplier; +import java.util.function.Function; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; @@ -20,6 +22,7 @@ public class RaceBoardPage extends HostPageWithAuthentication { @FindBy(how = BySeleniumId.class, using = "raceMapSettingsButton") private WebElement raceMapSettingsButton; + private boolean doneInit; /** * Navigates to the given home URL and provides the corresponding {@link PageObject}. @@ -42,20 +45,60 @@ public class RaceBoardPage extends HostPageWithAuthentication { private RaceBoardPage(WebDriver driver) { super(driver); } + + @Override + protected void waitForAjaxRequests(int timeout, int polling) { + // since the raceboard continually loads new data, we cannot wait for it to finish initially + if (doneInit) { + super.waitForAjaxRequests(timeout, polling); + } + } + + @Override + protected void initElements() { + super.initElements(); + doneInit = true; + + // wait untill initial rendering of racemap & compilation ect, as default ajax based wait won't work here + WebDriverWait webDriverWait = new WebDriverWait(driver, 300); + webDriverWait.until(new Function() { + @Override + public Boolean apply(WebDriver t) { + try { + return raceMapSettingsButton.isDisplayed() && raceMapSettingsButton.getLocation().y > 100; + } catch (Exception e) { + // RaceMap cause multiple reflows and the element may temporarily not be in the viewport + return false; + } + } + }); + } + public MapSettingsPO openMapSettings() { + waitUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + try { + return raceMapSettingsButton.isDisplayed() && raceMapSettingsButton.getLocation().y > 100; + } catch (Exception e) { + // RaceMap cause multiple reflows and the element may temporarily not be in the viewport + return false; + } + } + }); raceMapSettingsButton.click(); waitUntil(new BooleanSupplier() { - @Override public boolean getAsBoolean() { WebElement settingsDialog = null; try { settingsDialog = findElementBySeleniumId("raceMapSettings"); + boolean exists = settingsDialog != null; + // exists, and was actually rendered (to apply the values) + return (exists && settingsDialog.isDisplayed()); } catch(Exception e) { } - boolean exists = settingsDialog != null; - // exists, and was actually rendered (to apply the values) - return (exists && settingsDialog.getSize().height > 50); + return false; } }); return new MapSettingsPO(driver, findElementBySeleniumId("raceMapSettings")); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java index a77a7f01b37..de34a683102 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java @@ -18,20 +18,23 @@ import org.junit.Rule; import org.junit.rules.TestWatchman; import org.junit.runner.RunWith; import org.junit.runners.model.FrameworkMethod; +import org.openqa.selenium.By; import org.openqa.selenium.Cookie; -import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.Keys; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.html5.WebStorage; import org.openqa.selenium.remote.Augmenter; import org.openqa.selenium.remote.RemoteWebDriver; -import com.sap.sse.common.Duration; -import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sailing.selenium.core.Managed; import com.sap.sailing.selenium.core.SeleniumRunner; import com.sap.sailing.selenium.core.TestEnvironment; import com.sap.sailing.selenium.core.WindowManager; +import com.sap.sailing.selenium.pages.PageObject; +import com.sap.sse.common.Duration; +import com.sap.sse.common.impl.MillisecondsTimePoint; /** *

Abstract base class for unit tests with Selenium. This class is already annotated as required to get executed @@ -83,16 +86,37 @@ public abstract class AbstractSeleniumTest { } catch (IOException exception) { throw new RuntimeException(exception); } - // clear local storage + + // To be able to access LocalStorage we need to load a page having the target origin getWebDriver().get(contextRoot); - ((JavascriptExecutor)getWebDriver()).executeScript("window.localStorage.clear();"); + + // clear local storage + final WebStorage webStorage = (WebStorage)getWebDriver(); + webStorage.getLocalStorage().clear(); + + // extending the timeout of notifications to 100s to prevent timing failures + webStorage.getLocalStorage().setItem("sse.notification.customTimeOutInSeconds", + Integer.toString(PageObject.DEFAULT_WAIT_TIMEOUT_SECONDS)); + + try { + // In IE 11 we sometimes see the problem that IE somehow automatically changes the zoom level to 75%. + // Selenium tests with InternetExplorerDriver fail if the zoom level is not set to 100% due to the fact that coordinates determined aren't correct. + // With this we enforce a zoom level of 100% before running a test. + // To make this work correctly you also need to set InternetExplorerDriver.IGNORE_ZOOM_SETTING to true (this should be pre-configured in local-test-environment.xml when activating IE driver) + getWebDriver().findElement(By.tagName("html")).sendKeys(Keys.chord(Keys.CONTROL, "0")); + } catch (Exception e) { + } } protected void setUpAuthenticatedSession() { + // To be able to set a cookie we need to load a page having the target origin + getWebDriver().get(getContextRoot()); + logger.info("Authenticating session..."); Cookie sessionCookie = authenticate(getContextRoot()); getWebDriver().get(getContextRoot() + "index.html"); // initialize web driver so setting a cookie for the local domain is possible - getWebDriver().manage().addCookie(sessionCookie); + final Cookie cookieWithoutDomain = new Cookie(sessionCookie.getName(), sessionCookie.getValue(), null, sessionCookie.getPath(), sessionCookie.getExpiry(), sessionCookie.isSecure(), sessionCookie.isHttpOnly()); + getWebDriver().manage().addCookie(cookieWithoutDomain); logger.info("...obtained session cookie "+sessionCookie); } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java index 4075640702c..a232724042e 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java @@ -13,8 +13,6 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; -import com.sap.sailing.selenium.core.WebDriverWindow; -import com.sap.sailing.selenium.core.WindowManager; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.connectors.SmartphoneTrackingEventManagementPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; @@ -36,10 +34,6 @@ import com.sap.sailing.selenium.pages.gwt.DataEntryPO; import com.sap.sailing.selenium.test.AbstractSeleniumTest; public class TestRefreshableSelectionModel extends AbstractSeleniumTest { - private static WindowManager manager; - private static WebDriverWindow windowForEdit; - private static WebDriverWindow windowForSelection; - // TestMaintenanceOfSelectionAfterDataChanges private CompetitorEntry competitorEntry; private CompetitorEntry competitorEntryToSelect; @@ -66,9 +60,6 @@ public class TestRefreshableSelectionModel extends AbstractSeleniumTest { @Before public void setUp() { clearState(getContextRoot()); - manager = this.environment.getWindowManager(); - windowForEdit = manager.getCurrentWindow(); - windowForSelection = manager.openNewWindow(); super.setUp(); } @@ -80,85 +71,87 @@ public class TestRefreshableSelectionModel extends AbstractSeleniumTest { @Test public void testMaintenanceOfSelectionAfterDataChanges() { - windowForEdit.switchToWindow(); - final TrackedRacesCompetitorsPanelPO competitorsPanel = goToCompetitorsPanel(); - - for (int i = 0; i < 2; i++) { + this.environment.getWindowManager().withExtraWindow((windowForSelection, windowForEdit) -> { + windowForEdit.switchToWindow(); + final TrackedRacesCompetitorsPanelPO competitorsPanel = goToCompetitorsPanel(); + + for (int i = 0; i < 2; i++) { + TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddCompetitorButton(); + dialog.setNameTextBox("" + System.currentTimeMillis()); + dialog.setShortNameTextBox("" + System.currentTimeMillis()); + dialog.pressOk(); + } + TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddCompetitorButton(); - dialog.setNameTextBox("" + System.currentTimeMillis()); - dialog.setShortNameTextBox("" + System.currentTimeMillis()); + final String name = "" + System.currentTimeMillis(); + dialog.setNameTextBox(name); + final String shortName = "" + System.currentTimeMillis(); + dialog.setShortNameTextBox(shortName); dialog.pressOk(); - } - - TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddCompetitorButton(); - final String name = "" + System.currentTimeMillis(); - dialog.setNameTextBox(name); - final String shortName = "" + System.currentTimeMillis(); - dialog.setShortNameTextBox(shortName); - dialog.pressOk(); - - boolean found = false; - for (final CompetitorEntry it : competitorsPanel.getCompetitorTable().getEntries()) { - String itName = it.getName(); - if (itName.equals(name)) { - found = true; - competitorEntry = it; - break; + + boolean found = false; + for (final CompetitorEntry it : competitorsPanel.getCompetitorTable().getEntries()) { + String itName = it.getName(); + if (itName.equals(name)) { + found = true; + competitorEntry = it; + break; + } } - } - assertTrue(found); - - windowForSelection.switchToWindow(); - TrackedRacesCompetitorsPanelPO competitorPanelForSelection = goToCompetitorsPanel(); - found = false; - for (final CompetitorEntry it : competitorPanelForSelection.getCompetitorTable().getEntries()) { - String itName = it.getName(); - if (itName.equals(name)) { - found = true; - competitorEntryToSelect = it; - break; + assertTrue(found); + + windowForSelection.switchToWindow(); + TrackedRacesCompetitorsPanelPO competitorPanelForSelection = goToCompetitorsPanel(); + found = false; + for (final CompetitorEntry it : competitorPanelForSelection.getCompetitorTable().getEntries()) { + String itName = it.getName(); + if (itName.equals(name)) { + found = true; + competitorEntryToSelect = it; + break; + } } - } - assertTrue(found); - competitorPanelForSelection.getCompetitorTable().selectEntry(competitorEntryToSelect); - - assertEquals(1, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size()); - assertEquals(name, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getName()); - assertEquals(shortName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getShortName()); - // change competitor - windowForEdit.switchToWindow(); - dialog = competitorEntry.clickEditButton(); - final String changedName = "" + System.currentTimeMillis(); - dialog.setNameTextBox(changedName); - final String changedShortName = "" + System.currentTimeMillis(); - dialog.setShortNameTextBox(changedShortName); - dialog.pressOk(); - - // assert selection - windowForSelection.switchToWindow(); - - competitorPanelForSelection.pushRefreshButton(); - WebDriverWait waitTimer = new WebDriverWait(competitorPanelForSelection.driver, 10); - ExpectedCondition condition = new ExpectedCondition() { - @Override - public Boolean apply(WebDriver arg0) { - return competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size() == 1; + assertTrue(found); + competitorPanelForSelection.getCompetitorTable().selectEntry(competitorEntryToSelect); + + assertEquals(1, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size()); + assertEquals(name, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getName()); + assertEquals(shortName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getShortName()); + // change competitor + windowForEdit.switchToWindow(); + dialog = competitorEntry.clickEditButton(); + final String changedName = "" + System.currentTimeMillis(); + dialog.setNameTextBox(changedName); + final String changedShortName = "" + System.currentTimeMillis(); + dialog.setShortNameTextBox(changedShortName); + dialog.pressOk(); + + // assert selection + windowForSelection.switchToWindow(); + + competitorPanelForSelection.pushRefreshButton(); + WebDriverWait waitTimer = new WebDriverWait(competitorPanelForSelection.driver, 10); + ExpectedCondition condition = new ExpectedCondition() { + @Override + public Boolean apply(WebDriver arg0) { + return competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size() == 1; + } + }; + waitTimer.until(condition); + + assertEquals(1, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size()); + for (final CompetitorEntry it : competitorPanelForSelection.getCompetitorTable().getEntries()) { + String itName = it.getName(); + if (itName.equals(changedName)) { + found = true; + competitorEntryToSelect = it; + break; + } } - }; - waitTimer.until(condition); - - assertEquals(1, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size()); - for (final CompetitorEntry it : competitorPanelForSelection.getCompetitorTable().getEntries()) { - String itName = it.getName(); - if (itName.equals(changedName)) { - found = true; - competitorEntryToSelect = it; - break; - } - } - assertTrue(found); - assertEquals(changedName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getName()); - assertEquals(changedShortName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getShortName()); + assertTrue(found); + assertEquals(changedName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getName()); + assertEquals(changedShortName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getShortName()); + }); } private void setUpTestRefreshOfDependingUIElements() { @@ -201,62 +194,63 @@ public class TestRefreshableSelectionModel extends AbstractSeleniumTest { @Test public void testRefreshOfDependingUIElements() { - windowForSelection.switchToWindow(); - setUpTestRefreshOfDependingUIElements(); - AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); - SmartphoneTrackingEventManagementPanelPO smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); - CellTablePO leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); - // select leaderboard - DataEntryPO entryToSelect = null; - for(DataEntryPO entry : leaderboards.getEntries()) { - if(entry.getColumnContent("Name").equals(LEADERBOARD)) { - entryToSelect = entry; - break; + this.environment.getWindowManager().withExtraWindow((windowForSelection, windowForEdit) -> { + windowForSelection.switchToWindow(); + setUpTestRefreshOfDependingUIElements(); + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + SmartphoneTrackingEventManagementPanelPO smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); + CellTablePO leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); + // select leaderboard + DataEntryPO entryToSelect = null; + for(DataEntryPO entry : leaderboards.getEntries()) { + if(entry.getColumnContent("Name").equals(LEADERBOARD)) { + entryToSelect = entry; + break; + } } - } - assertNotNull(entryToSelect); - leaderboards.selectEntry(entryToSelect); - - RaceColumnTableWrapperPO raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); - CellTablePO raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); - final int anzRaceColumns = raceColumnTable.getEntries().size(); - assertEquals(5, anzRaceColumns); - - // Open a second window & setup second window - windowForEdit.switchToWindow(); - - AdminConsolePage adminConsoleForEdit = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); - RegattaStructureManagementPanelPO regattaStructure = adminConsoleForEdit.goToRegattaStructure(); - - RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); - SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); - seriesDialog.addRaces(6, 7); - seriesDialog.pressOk(); - - windowForSelection.switchToWindow(); - adminConsole.goToTracTracEvents(); - smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); - leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); - // select leaderboard - entryToSelect = null; - for(DataEntryPO entry : leaderboards.getEntries()) { - if(entry.getColumnContent("Name").equals(LEADERBOARD)) { - entryToSelect = entry; - break; + assertNotNull(entryToSelect); + leaderboards.selectEntry(entryToSelect); + + RaceColumnTableWrapperPO raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); + CellTablePO raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); + final int anzRaceColumns = raceColumnTable.getEntries().size(); + assertEquals(5, anzRaceColumns); + + // Open a second window & setup second window + windowForEdit.switchToWindow(); + + AdminConsolePage adminConsoleForEdit = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + RegattaStructureManagementPanelPO regattaStructure = adminConsoleForEdit.goToRegattaStructure(); + + RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); + SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); + seriesDialog.addRaces(6, 7); + seriesDialog.pressOk(); + + windowForSelection.switchToWindow(); + adminConsole.goToTracTracEvents(); + smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); + leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); + // select leaderboard + entryToSelect = null; + for(DataEntryPO entry : leaderboards.getEntries()) { + if(entry.getColumnContent("Name").equals(LEADERBOARD)) { + entryToSelect = entry; + break; + } } - } - assertNotNull(entryToSelect); - leaderboards.selectEntry(entryToSelect); - - raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); - raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); - final int newAnzRaceColumns = raceColumnTable.getEntries().size(); - assertEquals(7, newAnzRaceColumns); + assertNotNull(entryToSelect); + leaderboards.selectEntry(entryToSelect); + + raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); + raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); + final int newAnzRaceColumns = raceColumnTable.getEntries().size(); + assertEquals(7, newAnzRaceColumns); + }); } @Test public void testMaintenanceOfSelectionAfterFilteringTrackedracesOnLeaderboardConfigPanel() { - windowForSelection.switchToWindow(); setUpTestRefreshOfDependingUIElements(); AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); @@ -267,12 +261,7 @@ public class TestRefreshableSelectionModel extends AbstractSeleniumTest { tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL); tracTracEvents.setReggataForTracking(this.regatta); tracTracEvents.setTrackSettings(false, false, false); - // TODO: There exists a bug in Selenium with key modifiers (Issue 3734 and 6817), so we can't use multi - // selection (Firefox on Windows) - //tracTracEvents.startTrackingForRaces(this.trackableRaces); - for(int i =0; i<2;i++) { - tracTracEvents.startTrackingForRace(trackableRaces.get(i)); - } + tracTracEvents.startTrackingForRaces(this.trackableRaces); leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java index e401c6f74ab..75b6bb44e61 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java @@ -15,9 +15,9 @@ import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfig import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO.RaceDescriptor; import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaDetailsCompositePO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO; import com.sap.sailing.selenium.pages.adminconsole.regatta.SeriesEditDialogPO; -import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; import com.sap.sailing.selenium.pages.adminconsole.tracking.RaceColumnTableWrapperPO; import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO; import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO.TrackedRaceDescriptor; @@ -132,11 +132,6 @@ public class TestSmartphoneTrackingEventManagementPanel extends AbstractSelenium tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL); tracTracEvents.setReggataForTracking(this.regatta); tracTracEvents.setTrackSettings(false, false, false); - // TODO: There exists a bug in Selenium with key modifiers (Issue 3734 and 6817), so we can't use multi - // selection (Firefox on Windows) - //tracTracEvents.startTrackingForRaces(this.trackableRaces); - for(TrackableRaceDescriptor race : this.trackableRaces) { - tracTracEvents.startTrackingForRace(race); - } + tracTracEvents.startTrackingForRaces(this.trackableRaces); } } \ No newline at end of file diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java index 76f8cadf204..509bb5d6a98 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java @@ -1,13 +1,10 @@ package com.sap.sailing.selenium.test.adminconsole; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Test; -import org.openqa.selenium.Alert; -import org.openqa.selenium.WebDriver.TargetLocator; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; @@ -87,13 +84,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL); tracTracEvents.setReggataForTracking(DEFAULT_REGATTA); tracTracEvents.setTrackSettings(false, false, false); - tracTracEvents.startTrackingForRace(this.trackableRace); - TargetLocator locator = getWebDriver().switchTo(); - Alert alert = locator.alert(); - String text = alert.getText(); - alert.dismiss(); - String message = "There is at least one regatta for the selected boat classes."; - assertThat(text, containsString(message)); + tracTracEvents.startTrackingForRacesAndAcceptDefaultRegattaWarning(this.trackableRace); } @Test @@ -108,13 +99,6 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL); tracTracEvents.setReggataForTracking(idm2013Descriptor); tracTracEvents.setTrackSettings(false, false, false); - tracTracEvents.startTrackingForRace(this.trackableRace); - TargetLocator locator = getWebDriver().switchTo(); - Alert alert = locator.alert(); - String text = alert.getText(); - alert.dismiss(); - String message = String.format("The selected races contain boat classes which are not the same as " + - "the boat class '%s' of the selected regatta.", IDM_2013_BOAT_CLASS); - assertThat(text, containsString(message)); + tracTracEvents.startTrackingForRaceAndAwaitBoatClassError(this.trackableRace, IDM_2013_BOAT_CLASS); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java index 52db4e94fde..04023ea3ca8 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java @@ -6,12 +6,15 @@ import static org.junit.Assert.assertTrue; import java.util.Date; import java.util.List; +import java.util.function.Function; import javax.xml.bind.DatatypeConverter; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.support.ui.WebDriverWait; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.event.EventConfigurationPanelPO; @@ -117,9 +120,27 @@ public class TestAutoPlay extends AbstractSeleniumTest { assertTrue("Url does not contain proper title " + url, url.contains("lbwh.saph.title=Leaderboard%3A+BMW+Cup+(J80)")); assertTrue("Url does not contain proper name " + url, url.contains("name=BMW+Cup+(J80)")); AutoPlayLeaderboardView autoplayPage = page.goToAutoPlayClassicUrl(getWebDriver(), url); - LeaderboardTablePO leaderBoard = autoplayPage.getLeaderBoard(); - leaderBoard.waitForLeaderboardToHaveData(); - List races = leaderBoard.getRaceNames(); + LeaderboardTablePO leaderBoard = autoplayPage.getLeaderBoardWithData(); + final List races = new WebDriverWait(getWebDriver(), 20).until(new Function>() { + @Override + public List apply(WebDriver arg0) { + try { + final List races = leaderBoard.getRaceNames(); + final List result; + if (races != null && !races.isEmpty()) { + result = races; + } else { + // The table may seem to be empty while the leaderboard updates itself + result = null; + } + return result; + } catch (Exception e) { + // While the leaderboard updates its contents, errors may occur while reading data via selenium + // In this case we need to try this in the next loop again + return null; + } + } + }); Assert.assertTrue(races.contains("R1")); Assert.assertTrue(races.contains("R2")); Assert.assertTrue(races.contains("R3")); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index 80739d87854..d1099f09de1 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -11,8 +11,6 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import com.sap.sailing.selenium.core.WebDriverWindow; -import com.sap.sailing.selenium.core.WindowManager; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO; @@ -73,117 +71,113 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { @Test public void testDynamicRaceLinking() { - // Open a second window which we use for the leaderboard later - WindowManager manager = this.environment.getWindowManager(); - WebDriverWindow leaderboardWindow = manager.getCurrentWindow(); - WebDriverWindow adminConsoleWindow = manager.openNewWindow(); - - // Open the leaderboard and check for "empty" leaderboard - LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, false); - LeaderboardTablePO table = leaderboard.getLeaderboardTable(); - List races = table.getRaceNames(); - - assertThat(races.size(), equalTo(5)); - assertThat(table.getEntries().size(), equalTo(28)); // the regatta already has the races linked; regatta leaderboard obtains competitors from regatta - adminConsoleWindow.switchToWindow(); - AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); - LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); - LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); - - Integer[] expectedPointsForFindelJens = new Integer[] {2, 18, 12, 4, 7}; - Integer[] expectedRankForFindelJens = new Integer[] {2, 9, 11, 7, 6}; // Bogacki with no score in R1 expected to end up at end of leaderboard - - // Link the races and check the leaderboard again - for (int i = 0; i < 5; i++) { - leaderboardDetails.linkRace(this.leaderboardRaces.get(i), this.trackedRaces.get(i)); - leaderboardWindow.switchToWindow(); - leaderboard.refresh(); - List allEntries = table.getEntries(); - LeaderboardEntry findelJens = table.getEntry("8875"); - Integer points = findelJens.getPointsForRace(String.format("D%s", i + 1)); - Integer rank = findelJens.getTotalRank(); - // Assertions - assertThat("Number of competitors does not match", - allEntries.size(), equalTo(28)); - assertThat("Points for race 'D" + (i + 1) + "' do not match for competitor '8875' (Findel, Jens)", - points, equalTo(expectedPointsForFindelJens[i])); - assertThat("Total rank after " + (i + 1) + " race(s) does not match for competitor '8875' (Findel, Jens)", - rank, equalTo(expectedRankForFindelJens[i])); + this.environment.getWindowManager().withExtraWindow((leaderboardWindow, adminConsoleWindow) -> { + // Open the leaderboard and check for "empty" leaderboard + LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, false); + LeaderboardTablePO table = leaderboard.getLeaderboardTable(); + List races = table.getRaceNames(); + + assertThat(races.size(), equalTo(5)); + assertThat(table.getEntries().size(), equalTo(28)); // the regatta already has the races linked; regatta leaderboard obtains competitors from regatta adminConsoleWindow.switchToWindow(); - } + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); + LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); + + Integer[] expectedPointsForFindelJens = new Integer[] {2, 18, 12, 4, 7}; + Integer[] expectedRankForFindelJens = new Integer[] {2, 9, 11, 7, 6}; // Bogacki with no score in R1 expected to end up at end of leaderboard + + // Link the races and check the leaderboard again + for (int i = 0; i < 5; i++) { + leaderboardDetails.linkRace(this.leaderboardRaces.get(i), this.trackedRaces.get(i)); + leaderboardWindow.switchToWindow(); + leaderboard.refresh(); + List allEntries = table.getEntries(); + LeaderboardEntry findelJens = table.getEntry("8875"); + Integer points = findelJens.getPointsForRace(String.format("D%s", i + 1)); + Integer rank = findelJens.getTotalRank(); + // Assertions + assertThat("Number of competitors does not match", + allEntries.size(), equalTo(28)); + assertThat("Points for race 'D" + (i + 1) + "' do not match for competitor '8875' (Findel, Jens)", + points, equalTo(expectedPointsForFindelJens[i])); + assertThat("Total rank after " + (i + 1) + " race(s) does not match for competitor '8875' (Findel, Jens)", + rank, equalTo(expectedRankForFindelJens[i])); + adminConsoleWindow.switchToWindow(); + } + }); } @Test public void testDynamicRaceDeletion() { - WindowManager manager = this.environment.getWindowManager(); - WebDriverWindow adminConsoleWindow = manager.getCurrentWindow(); - WebDriverWindow leaderboardWindow = manager.openNewWindow(); - // Go to the administration console and link all 5 races - AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); - LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); - LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); - leaderboardDetails.linkRace(this.leaderboardRaces.get(0), this.trackedRaces.get(0)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(1), this.trackedRaces.get(1)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(2), this.trackedRaces.get(2)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(3), this.trackedRaces.get(3)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(4), this.trackedRaces.get(4)); - // Open the leaderboard in our second window - leaderboardWindow.switchToWindow(); - LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, false); - LeaderboardTablePO table = leaderboard.getLeaderboardTable(); - // Go back to the administration console and delete third race - adminConsoleWindow.switchToWindow(); - RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); - RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); - SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); - seriesDialog.deleteRace("D3"); - seriesDialog.pressOk(true); - // Now we can check the result with our expectation - leaderboardWindow.switchToWindow(); - leaderboard.refresh(); - assertThat("Race names do not match after deletion of race 'D3'", - table.getRaceNames(), equalTo(Arrays.asList("D1", "D2", "D4", "D5"))); + this.environment.getWindowManager().withExtraWindow((adminConsoleWindow, leaderboardWindow) -> { + // Go to the administration console and link all 5 races + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); + LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); + leaderboardDetails.linkRace(this.leaderboardRaces.get(0), this.trackedRaces.get(0)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(1), this.trackedRaces.get(1)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(2), this.trackedRaces.get(2)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(3), this.trackedRaces.get(3)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(4), this.trackedRaces.get(4)); + // Open the leaderboard in our second window + leaderboardWindow.switchToWindow(); + LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, false); + LeaderboardTablePO table = leaderboard.getLeaderboardTable(); + // Go back to the administration console and delete third race + adminConsoleWindow.switchToWindow(); + RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); + RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); + SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); + seriesDialog.deleteRace("D3"); + seriesDialog.pressOk(true, false); + final List expectedRaces = Arrays.asList("D1", "D2", "D4", "D5"); + regattaDetails.waitForRacesOfSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME, expectedRaces); + // Now we can check the result with our expectation + leaderboardWindow.switchToWindow(); + leaderboard.refresh(); + assertThat("Race names do not match after deletion of race 'D3'", table.getRaceNames(), + equalTo(expectedRaces)); + }); } @Ignore("This test belongs to bug 1892 and currently fails. It is currently enabled on branch bug1892.") @Test public void testDynamicRenamingOfRace() { - WindowManager manager = this.environment.getWindowManager(); - WebDriverWindow adminConsoleWindow = manager.getCurrentWindow(); - WebDriverWindow leaderboardWindow = manager.openNewWindow(); - - // Go to the administration console and link all 5 races - AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); - LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); - LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); - - leaderboardDetails.linkRace(this.leaderboardRaces.get(0), this.trackedRaces.get(0)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(1), this.trackedRaces.get(1)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(2), this.trackedRaces.get(2)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(3), this.trackedRaces.get(3)); - leaderboardDetails.linkRace(this.leaderboardRaces.get(4), this.trackedRaces.get(4)); - - // Open the leaderboard in our second window - leaderboardWindow.switchToWindow(); - - LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, false); - LeaderboardTablePO table = leaderboard.getLeaderboardTable(); - - // Go back to the administration console and rename the first race - adminConsoleWindow.switchToWindow(); - - RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); - RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); - SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); - seriesDialog.renameRace("D1", "Q"); - seriesDialog.pressOk(true); - - // Now we can check the result with our expectation - leaderboardWindow.switchToWindow(); - leaderboard.refresh(); - List races = table.getRaceNames(); - assertThat("Race names do not match after renaming race 'D1' to 'Q'", - races, equalTo(Arrays.asList("Q", "D2", "D3", "D4", "D5"))); + this.environment.getWindowManager().withExtraWindow((adminConsoleWindow, leaderboardWindow) -> { + // Go to the administration console and link all 5 races + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); + LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(this.regatta.toString()); + + leaderboardDetails.linkRace(this.leaderboardRaces.get(0), this.trackedRaces.get(0)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(1), this.trackedRaces.get(1)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(2), this.trackedRaces.get(2)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(3), this.trackedRaces.get(3)); + leaderboardDetails.linkRace(this.leaderboardRaces.get(4), this.trackedRaces.get(4)); + + // Open the leaderboard in our second window + leaderboardWindow.switchToWindow(); + + LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, false); + LeaderboardTablePO table = leaderboard.getLeaderboardTable(); + + // Go back to the administration console and rename the first race + adminConsoleWindow.switchToWindow(); + + RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); + RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); + SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); + seriesDialog.renameRace("D1", "Q"); + seriesDialog.pressOk(true, false); + + // Now we can check the result with our expectation + leaderboardWindow.switchToWindow(); + leaderboard.refresh(); + List races = table.getRaceNames(); + assertThat("Race names do not match after renaming race 'D1' to 'Q'", + races, equalTo(Arrays.asList("Q", "D2", "D3", "D4", "D5"))); + }); } private void configureLeaderboard() { @@ -209,20 +203,10 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL); tracTracEvents.setReggataForTracking(this.regatta); tracTracEvents.setTrackSettings(false, false, false); - // TODO: There exists a bug in Selenium with key modifiers (Issue 3734 and 6817), so we can't use multi - // selection (Firefox on Windows) - //tracTracEvents.startTrackingForRaces(this.trackableRaces); - for(TrackableRaceDescriptor race : this.trackableRaces) { - tracTracEvents.startTrackingForRace(race); - } + tracTracEvents.startTrackingForRaces(trackableRaces); TrackedRacesListPO trackedRacesList = tracTracEvents.getTrackedRacesList(); trackedRacesList.waitForTrackedRaces(this.trackedRaces, Status.FINISHED); // TracAPI puts REPLAY races into FINISHED mode when done loading - // TODO: There exists a bug in Selenium with key modifiers (Issue 3734 and 6817), so we can't use multi - // selection (Firefox on Windows) - //trackedRacesList.stopTracking(this.trackedRaces); - for (TrackedRaceDescriptor race : this.trackedRaces) { - trackedRacesList.stopTracking(race); - } + trackedRacesList.stopTracking(this.trackedRaces); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestSwitchingOffStartOfRaceInference.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestSwitchingOffStartOfRaceInference.java index ce286462c33..6f8a664a726 100755 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestSwitchingOffStartOfRaceInference.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestSwitchingOffStartOfRaceInference.java @@ -11,8 +11,6 @@ import java.util.List; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.selenium.core.WebDriverWindow; -import com.sap.sailing.selenium.core.WindowManager; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO; @@ -62,33 +60,31 @@ public class TestSwitchingOffStartOfRaceInference extends AbstractSeleniumTest { @Test public void testCorrectDisplayOfRaceColumnWithAndWithoutStartTimeInference() { - WindowManager manager = this.environment.getWindowManager(); - WebDriverWindow adminConsoleWindow = manager.getCurrentWindow(); - WebDriverWindow leaderboardWindow = manager.openNewWindow(); - leaderboardWindow.switchToWindow(); - LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, /* race details */ false); - LeaderboardTablePO leaderboardTable = leaderboard.getLeaderboardTable(); - List races = leaderboardTable.getRaceNames(); - assertThat("Expected only D3", races, equalTo(Arrays.asList("D3"))); - int d3ColumnIndex = leaderboardTable.getColumnIndex("D3"); - for (LeaderboardEntry e : leaderboardTable.getEntries()) { - String raceColumnContent = e.getColumnContent(d3ColumnIndex); - assertTrue(Integer.parseInt(raceColumnContent) > 0); // all competitors have a positive score in R3 - } - adminConsoleWindow.switchToWindow(); - // Go to the administration console and unset the "useStartTimeInference" flag - RegattaStructureManagementPanelPO regattaManagementPanel = adminConsole.goToRegattaStructure(); - RegattaListCompositePO regattaList = regattaManagementPanel.getRegattaList(); - RegattaEditDialogPO regattaEditDialog = regattaList.editRegatta(regatta); - regattaEditDialog.setUseStartTimeInference(false); - regattaEditDialog.pressOk(); - leaderboardWindow.switchToWindow(); - leaderboard.refresh(); - for (LeaderboardEntry e : leaderboardTable.getEntries()) { - String raceColumnContent = e.getColumnContent(d3ColumnIndex); - assertEquals("", raceColumnContent); // all competitors have an empty score because there is no start time anymore - } - leaderboardWindow.close(); + this.environment.getWindowManager().withExtraWindow((adminConsoleWindow, leaderboardWindow) -> { + leaderboardWindow.switchToWindow(); + LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, /* race details */ false); + LeaderboardTablePO leaderboardTable = leaderboard.getLeaderboardTable(); + List races = leaderboardTable.getRaceNames(); + assertThat("Expected only D3", races, equalTo(Arrays.asList("D3"))); + int d3ColumnIndex = leaderboardTable.getColumnIndex("D3"); + for (LeaderboardEntry e : leaderboardTable.getEntries()) { + String raceColumnContent = e.getColumnContent(d3ColumnIndex); + assertTrue(Integer.parseInt(raceColumnContent) > 0); // all competitors have a positive score in R3 + } + adminConsoleWindow.switchToWindow(); + // Go to the administration console and unset the "useStartTimeInference" flag + RegattaStructureManagementPanelPO regattaManagementPanel = adminConsole.goToRegattaStructure(); + RegattaListCompositePO regattaList = regattaManagementPanel.getRegattaList(); + RegattaEditDialogPO regattaEditDialog = regattaList.editRegatta(regatta); + regattaEditDialog.setUseStartTimeInference(false); + regattaEditDialog.pressOk(); + leaderboardWindow.switchToWindow(); + leaderboard.refresh(); + for (LeaderboardEntry e : leaderboardTable.getEntries()) { + String raceColumnContent = e.getColumnContent(d3ColumnIndex); + assertEquals("", raceColumnContent); // all competitors have an empty score because there is no start time anymore + } + }); } private AdminConsolePage configureRegattaAndLeaderboard() { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java index 79070cb3237..46c078b499e 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java @@ -77,7 +77,6 @@ public class SettingsTest extends AbstractSeleniumTest { // Modes settings were formerly patched on the user defaults but are now patched on top of the system defaults. // In addition, there are now different settigns keys per mode so that settigns for one mode do not have an effect on other modes. // To verify this, there are new tests below but these do not test ReceBoard's embedded Leaderboard. - // FIXME uncomment leaderboard test code when selenium infrastructure gets an upgrade /** * Verifies the settings storage of the raceboard. Checks the precedences of url, context specific settings, mode * settings and global settings. @@ -95,35 +94,29 @@ public class SettingsTest extends AbstractSeleniumTest { RaceBoardPage raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, BMW_CUP_REGATTA, String.format(BMW_RACE, 1), "PLAYER"); - //FIXME uncomment the leaderboard code when selenium on remote build server displays the raceboard properly + DetailCheckboxInfo[] detailsToSelect = new DetailCheckboxInfo[] { + // Overall details -// DetailCheckboxInfo[] detailsToSelect = new DetailCheckboxInfo[] { -// // Overall details -// -// // Race details -// DetailCheckboxInfo.RACE_GAP_TO_LEADER, -// DetailCheckboxInfo.RACE_CURRENT_SPEED_OVER_GROUND, -// DetailCheckboxInfo.DISPLAY_LEGS, -// -// // Race Start Analysis -// -// // Leg Details -// DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, -// -// // Maneuvers -// DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, -// DetailCheckboxInfo.PENALTY_CIRCLE -// -// }; -// -// LeaderboardSettingsDialogPO leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); -// LeaderboardSettingsPanelPO leaderboardSettingsPanelPO = -// leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); -// DetailCheckboxInfo[] selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); -// Assert.assertArrayEquals(detailsToSelect, selectedDetails); -// leaderboardSettingsPanelPO.setRefreshInterval(2); -// leaderboardSettingsDialog.pressMakeDefault(); -// leaderboardSettingsDialog.pressCancel(); + // Race details + DetailCheckboxInfo.RACE_GAP_TO_LEADER, DetailCheckboxInfo.RACE_CURRENT_SPEED_OVER_GROUND, + DetailCheckboxInfo.DISPLAY_LEGS, + + // Leg Details + DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, + + // Maneuvers + DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, DetailCheckboxInfo.PENALTY_CIRCLE + + }; + + LeaderboardSettingsDialogPO leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); + LeaderboardSettingsPanelPO leaderboardSettingsPanelPO = leaderboardSettingsDialog + .getLeaderboardSettingsPanelPO(); + DetailCheckboxInfo[] selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); + Assert.assertArrayEquals(detailsToSelect, selectedDetails); + leaderboardSettingsPanelPO.setRefreshInterval(2); + leaderboardSettingsDialog.pressMakeDefault(); + leaderboardSettingsDialog.pressCancel(); MapSettingsPO mapSettings = raceboard.openMapSettings(); // Verify initial mode settings @@ -136,58 +129,38 @@ public class SettingsTest extends AbstractSeleniumTest { raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, BMW_CUP_REGATTA, String.format(BMW_RACE, 1), "WINNING_LANES"); -// detailsToSelect = new DetailCheckboxInfo[] { -// // Overall details -// -// // Race details -// DetailCheckboxInfo.RACE_GAP_TO_LEADER, //merged from user settings -// DetailCheckboxInfo.RACE_DISTANCE, -// DetailCheckboxInfo.RACE_TIME, -// DetailCheckboxInfo.RACE_CURRENT_SPEED_OVER_GROUND, //merged from user settings -// DetailCheckboxInfo.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR, -// DetailCheckboxInfo.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR, -// -// // Race Start Analysis -// -// // Leg Details -// DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, -// -// // Maneuvers -// DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, -// DetailCheckboxInfo.PENALTY_CIRCLE -// -// }; -// -// -// leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); -// leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); -// selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); -// Assert.assertArrayEquals(detailsToSelect, selectedDetails); -// -// detailsToSelect = new DetailCheckboxInfo[] { -// // Overall details -// -// // Race details -// DetailCheckboxInfo.RACE_GAP_TO_LEADER, -// DetailCheckboxInfo.RACE_DISTANCE, -// DetailCheckboxInfo.RACE_TIME, -// DetailCheckboxInfo.RACE_CURRENT_SPEED_OVER_GROUND, -// DetailCheckboxInfo.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, //new value -// DetailCheckboxInfo.DISPLAY_LEGS, //new value which should override mode settings -// -// // Race Start Analysis -// -// // Leg Details -// DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, -// -// // Maneuvers -// DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, -// DetailCheckboxInfo.PENALTY_CIRCLE -// -// }; -// leaderboardSettingsPanelPO.selectDetailsAndDeselectOther(detailsToSelect); -// leaderboardSettingsPanelPO.setRefreshInterval(1); -// leaderboardSettingsDialog.pressOk(); + detailsToSelect = new DetailCheckboxInfo[] { + // Race details + DetailCheckboxInfo.RACE_DISTANCE, DetailCheckboxInfo.RACE_TIME, + DetailCheckboxInfo.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR, + DetailCheckboxInfo.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR, + // Leg Details + DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, + // Maneuvers + DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, DetailCheckboxInfo.PENALTY_CIRCLE + + }; + + leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); + leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); + selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); + Assert.assertArrayEquals(detailsToSelect, selectedDetails); + + detailsToSelect = new DetailCheckboxInfo[] { + // Race details + DetailCheckboxInfo.RACE_GAP_TO_LEADER, DetailCheckboxInfo.RACE_DISTANCE, DetailCheckboxInfo.RACE_TIME, + DetailCheckboxInfo.RACE_CURRENT_SPEED_OVER_GROUND, + DetailCheckboxInfo.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, + DetailCheckboxInfo.DISPLAY_LEGS, + // Leg Details + DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, + // Maneuvers + DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, DetailCheckboxInfo.PENALTY_CIRCLE + + }; + leaderboardSettingsPanelPO.selectDetailsAndDeselectOther(detailsToSelect); + leaderboardSettingsPanelPO.setRefreshInterval(1); + leaderboardSettingsDialog.pressOk(false, false); mapSettings = raceboard.openMapSettings(); // verify default mode settings override custom user settings @@ -198,21 +171,22 @@ public class SettingsTest extends AbstractSeleniumTest { Assert.assertFalse(mapSettings.isTransparentHoverlines()); mapSettings.setWindUp(false); mapSettings.setTransparentHoverlines(false); - mapSettings.pressOk(); + + mapSettings.pressOk(false, false); raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, BMW_CUP_REGATTA, String.format(BMW_RACE, 1), "WINNING_LANES"); -// leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); -// leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); -// selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); -// -// //verify highest precedence of document settings -// Assert.assertArrayEquals(detailsToSelect, selectedDetails); -// //verify that document settings are able to override custom user settings by a system default value -// Assert.assertEquals(1, leaderboardSettingsPanelPO.getRefreshInterval()); -// -// leaderboardSettingsDialog.pressCancel(); + leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); + leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); + selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); + + // verify highest precedence of document settings + Assert.assertArrayEquals(detailsToSelect, selectedDetails); + // verify that document settings are able to override custom user settings by a system default value + Assert.assertEquals(1, leaderboardSettingsPanelPO.getRefreshInterval()); + + leaderboardSettingsDialog.pressCancel(); mapSettings = raceboard.openMapSettings(); // Verify that mode settings are overridden by document settings @@ -222,35 +196,29 @@ public class SettingsTest extends AbstractSeleniumTest { //FIXME uncomment when START_ANALYSIS mode can be handled by remote CI server // verify that custom document settings override mode settings of other modes -// raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, -// BMW_CUP_REGATTA, -// String.format(BMW_RACE, 1), "START_ANALYSIS"); -// leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); -// leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); -// selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); -// -// detailsToSelect = new DetailCheckboxInfo[] { -// DetailCheckboxInfo.RACE_GAP_TO_LEADER, //start analysis mode -// DetailCheckboxInfo.RACE_CURRENT_SPEED_OVER_GROUND, //user settings -// DetailCheckboxInfo.DISPLAY_LEGS, //document settings which override mode settings -// DetailCheckboxInfo.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, //document settings -// DetailCheckboxInfo.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_START, //start analysis mode -// DetailCheckboxInfo.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, //start analysis mode -// DetailCheckboxInfo.DISTANCE_TO_START_AT_RACE_START, //start analysis mode -// DetailCheckboxInfo.SPEED_OVER_GROUND_AT_RACE_START, //start analysis mode -// DetailCheckboxInfo.SPEED_OVER_GROUND_WHEN_STARTING, //start analysis mode -// DetailCheckboxInfo.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_STARTING, //start analysis mode -// DetailCheckboxInfo.START_TACK, //start analysis mode -// -// DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, -// -// DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, DetailCheckboxInfo.PENALTY_CIRCLE -// }; -// Assert.assertArrayEquals(detailsToSelect, selectedDetails); -// leaderboardSettingsDialog.pressCancel(); -// -// mapSettings = raceboard.openMapSettings(); -// Assert.assertFalse(mapSettings.isWindUp()); + raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, BMW_CUP_REGATTA, + String.format(BMW_RACE, 1), "START_ANALYSIS"); + leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog(); + leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO(); + selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails(); + + detailsToSelect = new DetailCheckboxInfo[] { DetailCheckboxInfo.RACE_GAP_TO_LEADER, // start analysis mode + DetailCheckboxInfo.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_START, // start analysis mode + DetailCheckboxInfo.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, // start analysis mode + DetailCheckboxInfo.DISTANCE_TO_START_AT_RACE_START, // start analysis mode + DetailCheckboxInfo.SPEED_OVER_GROUND_AT_RACE_START, // start analysis mode + DetailCheckboxInfo.SPEED_OVER_GROUND_WHEN_STARTING, // start analysis mode + DetailCheckboxInfo.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_STARTING, // start analysis mode + DetailCheckboxInfo.START_TACK, // start analysis mode + + DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN, + + DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, DetailCheckboxInfo.PENALTY_CIRCLE }; + Assert.assertArrayEquals(detailsToSelect, selectedDetails); + leaderboardSettingsDialog.pressCancel(); + + mapSettings = raceboard.openMapSettings(); + Assert.assertTrue(mapSettings.isWindUp()); } /** @@ -687,7 +655,7 @@ public class SettingsTest extends AbstractSeleniumTest { // open settings dialog of configurated leaderboard with default values LeaderboardSettingsDialogPO leaderboardSettingsDialog = leaderboardPage.getLeaderboardSettings(); - LeaderboardSettingsPanelPO leaderboardSettingsPanel = leaderboardPage.getLeaderboardSettings() + LeaderboardSettingsPanelPO leaderboardSettingsPanel = leaderboardSettingsDialog .getLeaderboardSettingsPanelPO(); // store default values for checks later diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java index fa01070bb1b..b82873b29dc 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java @@ -130,14 +130,14 @@ public class EstimationDataSerializationDeserializationTest { Bearing relativeBearingToNextMarkAfterManeuver = new DegreeBearingImpl(10.01); boolean markPassing = true; Distance closestDistanceToMark = new MeterDistance(3.0); - Double deviationFromTargetTackAngle = 23.30; - Double deviationFromTargetJibeAngle = 22.30; + Double targetTackAngle = 23.30; + Double targetJibeAngle = 22.30; CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl( maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver, - relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark, - deviationFromTargetTackAngle, deviationFromTargetJibeAngle); + relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark, targetTackAngle, + targetJibeAngle); CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer( new ManeuverMainCurveWithEstimationDataJsonSerializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), @@ -149,10 +149,8 @@ public class EstimationDataSerializationDeserializationTest { new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer()); CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json); - assertEquals(deviationFromTargetJibeAngle, - deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees()); - assertEquals(deviationFromTargetTackAngle, - deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees()); + assertEquals(targetJibeAngle, deserialized.getTargetJibeAngleInDegrees()); + assertEquals(targetTackAngle, deserialized.getTargetTackAngleInDegrees()); assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark()); assertEquals(maneuverPosition, deserialized.getPosition()); assertEquals(tackingCount, deserialized.getTackingCount()); @@ -310,14 +308,14 @@ public class EstimationDataSerializationDeserializationTest { boolean markPassing = false; DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556); Distance closestDistanceToMark = null; - Double deviationFromTargetTackAngle = null; - Double deviationFromTargetJibeAngle = null; + Double targetTackAngle = null; + Double targetJibeAngle = null; CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl( maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver, - relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark, - deviationFromTargetTackAngle, deviationFromTargetJibeAngle); + relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark, targetTackAngle, + targetJibeAngle); CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer( new ManeuverMainCurveWithEstimationDataJsonSerializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), @@ -329,10 +327,8 @@ public class EstimationDataSerializationDeserializationTest { new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer()); CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json); - assertEquals(deviationFromTargetJibeAngle, - deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees()); - assertEquals(deviationFromTargetTackAngle, - deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees()); + assertEquals(targetJibeAngle, deserialized.getTargetJibeAngleInDegrees()); + assertEquals(targetTackAngle, deserialized.getTargetTackAngleInDegrees()); assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark()); assertEquals(maneuverPosition, deserialized.getPosition()); assertEquals(tackingCount, deserialized.getTackingCount()); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java index 886f0e3c745..fb80f6228cd 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java @@ -67,16 +67,15 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER); Double closestDistanceToMarkInMeters = (Double) object .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.CLOSEST_DISTANCE_TO_MARK); - Double deviationFromTargetTackAngle = (Double) object - .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_TACK_ANGLE); - Double deviationFromTargetJibeAngle = (Double) object - .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_JIBE_ANGLE); + Double targetTackAngle = (Double) object + .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.TARGET_TACK_ANGLE); + Double targetJibeAngle = (Double) object + .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.TARGET_JIBE_ANGLE); return new CompleteManeuverCurveWithEstimationDataImpl(position, mainCurve, curveWithUnstableCourseAndSpeed, wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind, convertBearing(relativeBearingToNextMarkBeforeManeuver), convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing, - convertDistance(closestDistanceToMarkInMeters), deviationFromTargetTackAngle, - deviationFromTargetJibeAngle); + convertDistance(closestDistanceToMarkInMeters), targetTackAngle, targetJibeAngle); } private Bearing convertBearing(Double degrees) { diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java index eb2089ea0d5..7b7d9032416 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java @@ -26,7 +26,7 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; * */ public class CompetitorTrackWithEstimationDataJsonSerializer extends AbstractTrackedRaceDataJsonSerializer { - public static final String elements = "elements"; + public static final String ELEMENTS = "elements"; public static final String BOAT_CLASS = "boatClass"; public static final String COMPETITOR_NAME = "competitorName"; public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds"; @@ -67,27 +67,27 @@ public class CompetitorTrackWithEstimationDataJsonSerializer extends AbstractTra for (Competitor competitor : trackedRace.getRace().getCompetitors()) { ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo(); - TimePoint from = null; - TimePoint to = null; - if (startBeforeStartLineInSeconds != null) { - from = trackTimeInfo.getTrackStartTimePoint() - .minus(new MillisecondsDurationImpl(startBeforeStartLineInSeconds * 1000L)); - } else if (startAfterFinishLineInSeconds != null) { - from = trackTimeInfo.getTrackEndTimePoint() - .plus(new MillisecondsDurationImpl(startAfterFinishLineInSeconds * 1000L)); - } else { - from = trackTimeInfo.getTrackStartTimePoint(); - } - if (endAfterFinishLineInSeconds != null) { - to = trackTimeInfo.getTrackEndTimePoint() - .plus(new MillisecondsDurationImpl(endAfterFinishLineInSeconds * 1000L)); - } else if (endBeforeStartLineInSeconds != null) { - to = trackTimeInfo.getTrackStartTimePoint() - .minus(new MillisecondsDurationImpl(endBeforeStartLineInSeconds * 1000L)); - } else { - to = trackTimeInfo.getTrackEndTimePoint(); - } if (trackTimeInfo != null) { + TimePoint from = null; + TimePoint to = null; + if (startBeforeStartLineInSeconds != null) { + from = trackTimeInfo.getTrackStartTimePoint() + .minus(new MillisecondsDurationImpl(startBeforeStartLineInSeconds * 1000L)); + } else if (startAfterFinishLineInSeconds != null) { + from = trackTimeInfo.getTrackEndTimePoint() + .plus(new MillisecondsDurationImpl(startAfterFinishLineInSeconds * 1000L)); + } else { + from = trackTimeInfo.getTrackStartTimePoint(); + } + if (endAfterFinishLineInSeconds != null) { + to = trackTimeInfo.getTrackEndTimePoint() + .plus(new MillisecondsDurationImpl(endAfterFinishLineInSeconds * 1000L)); + } else if (endBeforeStartLineInSeconds != null) { + to = trackTimeInfo.getTrackStartTimePoint() + .minus(new MillisecondsDurationImpl(endBeforeStartLineInSeconds * 1000L)); + } else { + to = trackTimeInfo.getTrackEndTimePoint(); + } final JSONObject forCompetitorJson = new JSONObject(); byCompetitorJson.add(forCompetitorJson); forCompetitorJson.put(COMPETITOR_NAME, competitor.getName()); @@ -111,7 +111,7 @@ public class CompetitorTrackWithEstimationDataJsonSerializer extends AbstractTra : trackTimeInfo.getTrackEndTimePoint().asMillis()); forCompetitorJson.put(MARK_PASSINGS_COUNT, getMarkPassingsCount(trackedRace, competitor)); forCompetitorJson.put(WAYPOINTS_COUNT, getWaypointsCount(trackedRace)); - forCompetitorJson.put(elements, + forCompetitorJson.put(ELEMENTS, elementsJsonSerializer.serialize(trackedRace, competitor, from, to, trackTimeInfo)); } } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java index e1b807cf388..a00ac2d01b8 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java @@ -24,8 +24,8 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer public static final String RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER = "relativeBearingToNextMarkBeforeManeuver"; public static final String RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER = "relativeBearingToNextMarkAfterManeuver"; public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters"; - public static final String DEVIATION_FROM_TARGET_TACK_ANGLE = "deviationFromTargetTackAngleInDegrees"; - public static final String DEVIATION_FROM_TARGET_JIBE_ANGLE = "deviationFromTargetJibeAngleInDegrees"; + public static final String TARGET_TACK_ANGLE = "targetTackAngleInDegrees"; + public static final String TARGET_JIBE_ANGLE = "targetJibeAngleInDegrees"; private final ManeuverCurveBoundariesJsonSerializer mainCurveSerializer; private final ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer; @@ -64,10 +64,8 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer : maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver().getDegrees()); result.put(CLOSEST_DISTANCE_TO_MARK, maneuverWithEstimationData.getDistanceToClosestMark() == null ? null : maneuverWithEstimationData.getDistanceToClosestMark().getMeters()); - result.put(DEVIATION_FROM_TARGET_TACK_ANGLE, - maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees()); - result.put(DEVIATION_FROM_TARGET_JIBE_ANGLE, - maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees()); + result.put(TARGET_TACK_ANGLE, maneuverWithEstimationData.getTargetTackAngleInDegrees()); + result.put(TARGET_JIBE_ANGLE, maneuverWithEstimationData.getTargetJibeAngleInDegrees()); return result; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GPSFixMovingJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GPSFixMovingJsonSerializer.java index 9abbbd260c0..7cbdef8ecb1 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GPSFixMovingJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GPSFixMovingJsonSerializer.java @@ -2,19 +2,25 @@ package com.sap.sailing.server.gateway.serialization.impl; import org.json.simple.JSONObject; +import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.server.gateway.deserialization.TypeBasedJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer; import com.sap.sailing.server.gateway.serialization.JsonSerializer; public class GPSFixMovingJsonSerializer implements JsonSerializer { + @Override public JSONObject serialize(GPSFixMoving object) { + return serialize(object, object.getSpeed()); + } + + public JSONObject serialize(GPSFixMoving object, SpeedWithBearing speedWithCourse) { JSONObject result = new GPSFixJsonSerializer().serialize(object); result.put(TypeBasedJsonDeserializer.FIELD_TYPE, GPSFixMovingJsonDeserializer.TYPE); - result.put(GPSFixMovingJsonDeserializer.FIELD_BEARING_DEG, object.getSpeed().getBearing().getDegrees()); - result.put(GPSFixMovingJsonDeserializer.FIELD_SPEED_KNOTS, object.getSpeed().getKnots()); + result.put(GPSFixMovingJsonDeserializer.FIELD_BEARING_DEG, speedWithCourse.getBearing().getDegrees()); + result.put(GPSFixMovingJsonDeserializer.FIELD_SPEED_KNOTS, speedWithCourse.getKnots()); return result; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java index a4dbff6c389..684004762c7 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java @@ -62,7 +62,9 @@ public class GpsFixesWithEstimationDataJsonSerializer implements CompetitorTrack track.lockForRead(); try { for (GPSFixMoving gpsFix : track.getFixes(from, true, to, true)) { - JSONObject serializedGpsFix = gpsFixMovingJsonSerializer.serialize(gpsFix); + SpeedWithBearing speedWithBearing = smoothFixes ? track.getEstimatedSpeed(gpsFix.getTimePoint()) + : gpsFix.getSpeed(); + JSONObject serializedGpsFix = gpsFixMovingJsonSerializer.serialize(gpsFix, speedWithBearing); if (addWind) { Wind wind = trackedRace.getWind(gpsFix.getPosition(), gpsFix.getTimePoint()); JSONObject serializedWind = wind == null ? null : windJsonSerializer.serialize(wind); @@ -71,8 +73,6 @@ public class GpsFixesWithEstimationDataJsonSerializer implements CompetitorTrack if (addNextWaypoint) { Distance closestDistanceToMark = estimationDataSupportDecoratorImpl .getClosestDistanceToMark(gpsFix.getTimePoint()); - SpeedWithBearing speedWithBearing = smoothFixes ? track.getEstimatedSpeed(gpsFix.getTimePoint()) - : gpsFix.getSpeed(); Bearing relativeBearingToNextMark = speedWithBearing == null ? null : estimationDataSupportDecoratorImpl.getRelativeBearingToNextMark(gpsFix.getTimePoint(), speedWithBearing.getBearing()); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java index 5ad592fbc2b..0b87b9c86e4 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RaceStateSerializer.java @@ -45,7 +45,7 @@ public class RaceStateSerializer implements JsonSerializer result; if (leaderboard instanceof HasRegattaLike && raceLog != null) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog); + ReadonlyRaceState state = ReadonlyRaceStateImpl.getOrCreate(/* race log resolver */ this, raceLog); result = new com.sap.sse.common.Util.Triple(state.getStartTime(), raceLog.getCurrentPassId(), state.getRacingProcedure().getType()); } else { @@ -3652,7 +3652,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes Leaderboard leaderboard = getLeaderboardByName(leaderboardName); final Triple result; if (leaderboard instanceof HasRegattaLike && raceLog != null) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog); + ReadonlyRaceState state = ReadonlyRaceStateImpl.getOrCreate(/* race log resolver */ this, raceLog); result = new com.sap.sse.common.Util.Triple<>(state.getFinishingTime(), state.getFinishedTime(), raceLog.getCurrentPassId()); } else { diff --git a/java/com.sap.sailing.simulator.test/.project b/java/com.sap.sailing.simulator.test/.project index 348d1ccbb59..6e91001fe19 100644 --- a/java/com.sap.sailing.simulator.test/.project +++ b/java/com.sap.sailing.simulator.test/.project @@ -22,12 +22,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -35,6 +35,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.simulator/.project b/java/com.sap.sailing.simulator/.project index 3f09d9050b0..f86820f24df 100644 --- a/java/com.sap.sailing.simulator/.project +++ b/java/com.sap.sailing.simulator/.project @@ -21,12 +21,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index daafe6c3709..f5a4d1f5b0d 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -22,6 +22,15 @@

Release Notes - Administration Console

+

July 2018

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

July 2018

  • When adding a YouTube video for tracked races ("Manage Media" in RaceBoard.html or "Tracked Races > Audio & Video" in AdminConsole.html) the respective video metadata is now read using YouTube API v3. @@ -32,6 +41,23 @@ leaving the value empty and confirming the dialog.
  • Up to 15 discards can now be configured for series and leaderboards. This enables, e.g., "Wednesday Night" scenarios where over the season, say, 20 races are run but due to changing participation only, say, the five best ones shall be scored.
  • +
  • There is a new scoring scheme available to be selected upon regatta creation. + The scoring scheme is named "Low Point System, First with two wins in medals is winner" and introduces the following specifics compared to the standard low point scheme: +
      +
    • Medal series does not feature doubling of points in the medal series by default.
    • +
    • A competitor having two wins in the medal series is treated as overall winner. + If there is a carry forward column in the medal series, having one point there also counts as a win which means the best competitor in the qualification needs a win less than any other finalist to be the overall winner.
    • +
    • If the medal series isn't started yet or no competitor has collected two wins, scoring by points is used. + This also applies if the regatta is aborted (e.g. due to weather conditions) before any competitor could win two races in the medal series.
    • +
    • If two competitors have the same amount of points in the medal series the tie breaking criteria is the carry forward column of the medal series. + If there is no carry forward column in the medal series, the last medal race is used as tie breaking criteria. + For any non-medal series, the standard low point rules apply.
    • +
    +
  • +
  • A TracAPI upgrade from version 3.10.1 to 3.11.0 unifies the handling of course updates and solves an issue + observed with updating mark passing instructions.
  • +
  • Bug fix for mark position editor, now providing consistent behavior during adding of new mark fixes
  • +
  • Data Mining now more aggressively terminates running queries if needed.

June 2018

diff --git a/java/com.sap.sse.common/.project b/java/com.sap.sse.common/.project index 8aff8541cda..c28bb7a5e5a 100644 --- a/java/com.sap.sse.common/.project +++ b/java/com.sap.sse.common/.project @@ -21,12 +21,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestAbortingHeavyLoadQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestAbortingHeavyLoadQuery.java new file mode 100644 index 00000000000..3e6a5494596 --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestAbortingHeavyLoadQuery.java @@ -0,0 +1,524 @@ +package com.sap.sse.datamining.impl; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sse.datamining.Query; +import com.sap.sse.datamining.QueryState; +import com.sap.sse.datamining.components.AdditionalResultDataBuilder; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.components.ProcessorInstruction; +import com.sap.sse.datamining.components.ProcessorInstructionHandler; +import com.sap.sse.datamining.data.QueryResult; +import com.sap.sse.datamining.impl.components.AbstractParallelProcessor; +import com.sap.sse.datamining.impl.components.AbstractProcessorInstruction; +import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.impl.components.ProcessorInstructionPriority; +import com.sap.sse.datamining.impl.components.aggregators.ParallelGroupedDataCollectingAsSetProcessor; +import com.sap.sse.datamining.shared.GroupKey; +import com.sap.sse.datamining.shared.data.QueryResultState; +import com.sap.sse.datamining.shared.impl.GenericGroupKey; +import com.sap.sse.datamining.test.util.components.StatefulBlockingInstruction; +import com.sap.sse.datamining.test.util.components.StatefulProcessorInstruction; + +/** + * Integration test aborting a query with simulated heavy load instructions. Uses a highly customized processor chain + * (based on the processors/instructions used in production) that allows detailed introspection and logging of the + * execution process. The test is used to verify the behavior of processors, if such a heavy load query is aborted + * during the process. For example that no instructions are scheduled after the query was aborted and the behavior of + * already scheduled instructions that may or may not have been started.
+ *
+ * The test is configurable regarding the number of threads for the executor, the number of data elements/heavy load + * instructions, the duration of heavy load instructions, the time to wait before the query is aborted and the time + * given to the executor to finish the remaining instructions. Note that the test can fail for some configurations, due + * to race conditions that would be very hard to check for.
+ *
+ * The test optionally records the process in order of the execution (using a concurrent message queue), which is + * printed to the console before assertions are performed. This isn't used by the assertions, but can be used to get a + * better understanding of what happens during the execution.
+ *
+ * See {@link #initialize()} for more details about the processor chain. + * + * @author Lennart Hensler + */ +public class TestAbortingHeavyLoadQuery { + + // Test Configuration ---------------------------------------------------------------------------------------- + /** + * Number of threads in the executor. Determines the number of elements (and thus heavy load instructions) + * retrieved for each group. This means that each group should have a runtime of {@value #HeavyLoadInstructionTotalDuration}ms, + * since the single instructions are executed concurrently. + */ + private static final int ExecutorPoolSize = Math.max(3, Runtime.getRuntime().availableProcessors()); + + /** The number of groups contained in the initial data source. */ + private static final int DataSourceSize = 2000; + private static final String GroupKeyPrefix = "G"; + + /** The time a step of a heavy load instruction blocks the executing thread (using {@link Thread#sleep(long)}). */ + private static final long HeavyLoadInstructionStepDuration = 50; + /** The number of steps a heavy load instruction performs */ + private static final int HeavyLoadInstructionNumberOfSteps = 10; + /** The total time a heavy load instruction blocks the executing thread (using {@link Thread#sleep(long)}). */ + private static final long HeavyLoadInstructionTotalDuration = HeavyLoadInstructionStepDuration * HeavyLoadInstructionNumberOfSteps; + + /** The number of milliseconds to wait before {@link Query#abort()} is called. */ + private static final long AbortQueryDelay = (long) (HeavyLoadInstructionTotalDuration * 5.45); + /** The time given to the executor to complete all unfinished instructions */ + private static final long TerminationTimeout = HeavyLoadInstructionStepDuration * 2; + //------------------------------------------------------------------------------------------------------------ + + // Execution Recording Configuration ------------------------------------------------------------------------- + /** Enables concurrent logging of the query execution and prints the current record before assertions. */ + private static final boolean RecordExecution = true; + private static final SimpleDateFormat DateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); + private ConcurrentLinkedQueue executionRecord; + //------------------------------------------------------------------------------------------------------------ + + // Test State - Initialized in initialize() ------------------------------------------------------------------ + private ExecutorService executor; + private Query> query; + private Collection> processors; + private Set> unfinishedInstructions; + //------------------------------------------------------------------------------------------------------------ + + @Test + public void testAbortingHeavyLoadQuery() throws InterruptedException, ExecutionException { + // Executing query in separate thread + RunnableFuture>> queryTask = new FutureTask<>(() -> { + logExecution("Starting query execution"); + long start = System.currentTimeMillis(); + QueryResult> result = query.run(); + long duration = System.currentTimeMillis() - start; + logExecution("Finished query in " + duration + "ms"); + return result; + }); + Thread worker = new Thread(queryTask, "Worker"); + worker.start(); + do { + Thread.sleep(10); + } while (query.getState() != QueryState.RUNNING); + + // Aborting query and waiting for completion + Thread.sleep(AbortQueryDelay); + logExecution("Aborting query"); + query.abort(); + QueryResult> result = queryTask.get(); + + // The query execution returned, so all processors are aborted (checked below). + // The number of unfinished instructions mustn't change after this point + int unfinishedInstructionsCount = unfinishedInstructions.size(); + boolean unfinishedInstructionWasFinished = false; + Set> runningInstructions = new HashSet<>(); + Set> notStartedInstructions = new HashSet<>(); + for (StatefulProcessorInstruction instruction : unfinishedInstructions) { + if (instruction.runWasCalled()) { + runningInstructions.add(instruction); + } else { + notStartedInstructions.add(instruction); + } + unfinishedInstructionWasFinished |= instruction.computeResultWasFinished(); + } + logExecution(unfinishedInstructionsCount + " unfinished instructions left - " + + runningInstructions.size() + " running, " + notStartedInstructions.size() + " not started"); + printExecutionRecord(); + assertThat("Number of unfinished instructions changed", runningInstructions.size() + notStartedInstructions.size(), is(unfinishedInstructionsCount)); + assertFalse("Unfinished instructions expected, but at least one was completed", unfinishedInstructionWasFinished); + + // Checking query, result and processor chain state + assertThat(query.getState(), is(QueryState.ABORTED)); + assertThat(result.getState(), is(QueryResultState.ABORTED)); + assertTrue("The result is not empty", result.isEmpty()); + for (Processor processor : processors) { + assertTrue("Processor wasn't aborted", processor.isAborted()); + } + + // Execution of unfinished instructions + executor.shutdown(); + boolean terminated = executor.awaitTermination(TerminationTimeout, TimeUnit.MILLISECONDS); + printExecutionRecord(); + assertTrue("The executor didn't terminate in the given time", terminated); + + // Verify that the collection of unfinished instructions didn't change + assertThat("Number of unfinished instructions changed", unfinishedInstructions.size(), is(unfinishedInstructionsCount)); + for (StatefulProcessorInstruction instruction : unfinishedInstructions) { + boolean previouslyContained = runningInstructions.contains(instruction) || notStartedInstructions.contains(instruction); + assertTrue("A new instruction has been scheduled after aborting the query", previouslyContained); + } + for (StatefulProcessorInstruction instruction : runningInstructions) { + assertTrue("A previously running instruction was removed from unfinished instructions", unfinishedInstructions.contains(instruction)); + } + for (StatefulProcessorInstruction instruction : notStartedInstructions) { + assertTrue("A previously unstarted instruction was removed from unfinished instructions", unfinishedInstructions.contains(instruction)); + } + + // Checking state of unfinished instructions + for (StatefulProcessorInstruction instruction : runningInstructions) { + assertTrue("computeResult() of a running unfinished instruction wasn't called", instruction.computeResultWasCalled()); + assertTrue("computeResult() of a running unfinished instruction didn't finish", instruction.computeResultWasFinished()); + if (instruction instanceof StatefulBlockingInstruction) { + StatefulBlockingInstruction blockingInstruction = (StatefulBlockingInstruction) instruction; + assertTrue("computeResult() of a running heavy load instruction wasn't aborted", blockingInstruction.computeResultWasAborted()); + } + } + for (StatefulProcessorInstruction instruction : notStartedInstructions) { + assertTrue("run() of an unstarted unfinished instruction wasn't called", instruction.runWasCalled()); + + assertFalse("computeResult() of an unstarted unfinished instruction was called", instruction.computeResultWasCalled()); + assertFalse("computeResult() of an unstarted unfinished instruction was finished", instruction.computeResultWasFinished()); + } + } + + private void printExecutionRecord() { + if (RecordExecution) { + Collection snapshot = new ArrayList<>(executionRecord); + executionRecord.clear(); + for (String string : snapshot) { + System.out.println(string); + } + } + } + + private void logExecution(String message) { + if (RecordExecution) { + String timeString = DateFormatter.format(new Date()); + executionRecord.add(timeString + " - " + Thread.currentThread().getName() + ": " + message); + } + } + + /** + * Initializes the {@link ExecutorService}, the execution record, helper collections (e.g. the processors in the + * chain or the unfinished instructions) and most importantly the processor chain for the query, which works as + * follows: + *
    + *
  1. + * The initial data source is a collection of strings from {@value #GroupKeyPrefix}n to + * {@value #GroupKeyPrefix}n-1, denoting a group. n is specified in {@link #DataSourceSize}. + *
  2. + *
  3. + * x (specified by {@link #ExecutorPoolSize}) {@link Element elements} are retrieved for each + * group, where the elements name is set to the received string and the elements value is the current value + * of x. + *
  4. + *
  5. + * A heavy load instruction for each element is scheduled, which blocks the running thread for + * {@value #HeavyLoadInstructionTotalDuration}ms. + *
  6. + *
  7. + * Each element is grouped by its name and its value is used as value for the {@link GroupedDataEntry}. + *
  8. + *
  9. The grouped data is collected as set.
  10. + *
+ * This results in n * x data elements (and heavy load instructions), with x instructions + * executed concurrently.
+ *
+ * Each processor creates {@link StatefulProcessorInstruction} or uses {@link StatefulInstructionWrapper}, which allows + * to verify if instruction.run() was called, its computation started and its computation finished.
+ *
+ * A concurrent set ({@link ConcurrentHashMap#newKeySet()}) is used to track the unfinished instructions. An instruction + * is added to the set upon its construction and is removed when the instruction finished callback method is called. + */ + @Before + @SuppressWarnings("unchecked") + public void initialize() { + if (RecordExecution) { + executionRecord = new ConcurrentLinkedQueue<>(); + } + + executor = new DataMiningExecutorService(ExecutorPoolSize); +// executor = new ThreadPoolExecutor(ExecutorPoolSize, ExecutorPoolSize, 0, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>()); +// executor = new ThreadPoolExecutor(ExecutorPoolSize, ExecutorPoolSize, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + processors = new ArrayList<>(); + unfinishedInstructions = ConcurrentHashMap.newKeySet(); + + Class> dataSourceType = (Class>)(Class) Iterable.class; + Class> groupedType = (Class>)(Class) GroupedDataEntry.class; + Class> resultType = (Class>)(Class) HashSet.class; + + Collection dataSource = new ArrayList<>(DataSourceSize); + for (int i = 0; i < DataSourceSize; i++) { + dataSource.add(GroupKeyPrefix + i); + } + query = new ProcessorQuery, Iterable>(dataSource, resultType) { + @Override + protected Processor, ?> createChainAndReturnFirstProcessor(Processor>, Void> resultReceiver) { + Processor, Map>> aggregator = new ParallelGroupedDataCollectingAsSetProcessor(executor, Collections.singleton(resultReceiver)) { + @Override + protected ProcessorInstruction>> createInstruction(GroupedDataEntry element) { + AbstractProcessorInstruction>> instruction = (AbstractProcessorInstruction>>) super.createInstruction(element); + StatefulProcessorInstruction>> statefulInstruction = new StatefulInstructionWrapper<>(instruction); + unfinishedInstructions.add(statefulInstruction); + return statefulInstruction; + } + + @Override + protected void storeElement(GroupedDataEntry element) { + logExecution("Storing " + element); + super.storeElement(element); + } + + @Override + public void afterInstructionFinished(ProcessorInstruction>> instruction) { + super.afterInstructionFinished(instruction); + if(canProcessElements()) unfinishedInstructions.remove(instruction); + } + }; + + Processor> grouper = new AbstractParallelProcessor>(Element.class, groupedType, executor, Collections.singleton(aggregator)) { + @Override + protected ProcessorInstruction> createInstruction(Element element) { + StatefulProcessorInstruction> instruction = new StatefulProcessorInstruction>(this, ProcessorInstructionPriority.Grouping) { + @Override + protected GroupedDataEntry internalComputeResult() throws Exception { + logExecution("Grouping " + element); + return new GroupedDataEntry<>(new GenericGroupKey<>(element.getName()), element.getValue()); + } + }; + unfinishedInstructions.add(instruction); + return instruction; + } + + @Override + public void afterInstructionFinished(ProcessorInstruction> instruction) { + super.afterInstructionFinished(instruction); + if(canProcessElements()) unfinishedInstructions.remove(instruction); + } + + @Override + protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { } + }; + + Processor heavyLoadProcessor = new AbstractParallelProcessor(Element.class, Element.class, executor, Collections.singleton(grouper)) { + @Override + protected ProcessorInstruction createInstruction(Element element) { + StatefulProcessorInstruction instruction = new HeavyLoadInstruction(this, + ProcessorInstructionPriority.Extraction, HeavyLoadInstructionStepDuration, + HeavyLoadInstructionNumberOfSteps, element, TestAbortingHeavyLoadQuery.this::logExecution); + unfinishedInstructions.add(instruction); + return instruction; + } + + @Override + public void afterInstructionFinished(ProcessorInstruction instruction) { + super.afterInstructionFinished(instruction); + if(canProcessElements()) unfinishedInstructions.remove(instruction); + } + + @Override + protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { } + }; + + Processor retriever1 = new AbstractRetrievalProcessor(String.class, Element.class, executor, Collections.singleton(heavyLoadProcessor), 1) { + @Override + protected ProcessorInstruction createInstruction(String element) { + AbstractProcessorInstruction instruction = (AbstractProcessorInstruction) super.createInstruction(element); + StatefulProcessorInstruction statefulInstruction = new StatefulInstructionWrapper<>(instruction); + unfinishedInstructions.add(statefulInstruction); + return statefulInstruction; + } + + @Override + protected Iterable retrieveData(String element) { + int count = ExecutorPoolSize; + logExecution("Retrieving " + count + " elements for " + element); + Collection data = new ArrayList<>(); + for (int i = 0; i < count; i++) { + data.add(new Element(element, i)); + } + return data; + } + + @Override + public void instructionSucceeded(Element result) { + logExecution("Group retrieval finished"); + super.instructionSucceeded(result); + } + + @Override + public void afterInstructionFinished(ProcessorInstruction instruction) { + super.afterInstructionFinished(instruction); + if(canProcessElements()) unfinishedInstructions.remove(instruction); + } + }; + Processor, String> retriever0 = new AbstractRetrievalProcessor, String>(dataSourceType, String.class, executor, Collections.singleton(retriever1), 0) { + @Override + protected ProcessorInstruction createInstruction(Iterable element) { + AbstractProcessorInstruction instruction = (AbstractProcessorInstruction) super.createInstruction(element); + StatefulProcessorInstruction statefulInstruction = new StatefulInstructionWrapper<>(instruction); + unfinishedInstructions.add(statefulInstruction); + return statefulInstruction; + } + + @Override + protected Iterable retrieveData(Iterable element) { + logExecution("Retrieving data from data source"); + return element; + } + + @Override + public void instructionSucceeded(String result) { + logExecution("Data source retrieval finished"); + super.instructionSucceeded(result); + } + + @Override + public void afterInstructionFinished(ProcessorInstruction instruction) { + super.afterInstructionFinished(instruction); + if(canProcessElements()) unfinishedInstructions.remove(instruction); + } + }; + + processors.add(retriever0); + processors.add(retriever1); + processors.add(heavyLoadProcessor); + processors.add(grouper); + processors.add(aggregator); + + return retriever0; + } + }; + } + + private static class HeavyLoadInstruction extends StatefulBlockingInstruction { + + private final Consumer recorder; + + public HeavyLoadInstruction(ProcessorInstructionHandler handler, ProcessorInstructionPriority priority, + long stepDuration, int numberOfSteps, Element result, Consumer recorder) { + super(handler, priority, stepDuration, numberOfSteps, result); + this.recorder = recorder; + } + + @Override + public void run() { + recorder.accept("Executing heavy load instruction for " + result); + super.run(); + } + + @Override + protected void actionBeforeBlock() { + recorder.accept("Starting work for heavy load instruction for " + result); + } + + @Override + protected void actionBeforeAbort() { + recorder.accept("Aborting heavy load instruction for " + result); + } + + @Override + protected void actionAfterBlock() { + recorder.accept("Finished heavy load instruction for " + result); + } + + } + + private static class StatefulInstructionWrapper extends StatefulProcessorInstruction { + + private static Method computeResult; + + private final AbstractProcessorInstruction instruction; + + public StatefulInstructionWrapper(AbstractProcessorInstruction instruction) { + super(instruction.getHandler(), instruction.getPriority()); + this.instruction = instruction; + } + + @Override + @SuppressWarnings("unchecked") + protected ResultType internalComputeResult() throws Exception { + if (computeResult == null) { + computeResult = AbstractProcessorInstruction.class.getDeclaredMethod("computeResult"); + computeResult.setAccessible(true); + } + return (ResultType) computeResult.invoke(instruction); + } + + } + + @After + public void resetComputeResultAccessibility() throws SecurityException, NoSuchMethodException { + AbstractProcessorInstruction.class.getDeclaredMethod("computeResult").setAccessible(false); + } + + /** + * Simple data type consisting of a name and value. + */ + private static class Element { + + private final String name; + private final int value; + + public Element(String name, int value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return getName() + "-" + getValue(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + value; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Element other = (Element) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (value != other.value) + return false; + return true; + } + + } + +} diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java index 4b6774ff492..6fe9d1c6bd3 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java @@ -63,7 +63,7 @@ public class TestProcessorQuery { Collection> resultReceivers = new ArrayList<>(); resultReceivers.add(new AbortResultReceiver(resultReceiver)); return new BlockingProcessor, Double>((Class>)(Class) Iterable.class, Double.class, - ConcurrencyTestsUtil.getExecutor(), resultReceivers, 1000) { + ConcurrencyTestsUtil.getSharedExecutor(), resultReceivers, 1000) { @Override protected Double createResult(Iterable element) { return 0.0; @@ -101,7 +101,7 @@ public class TestProcessorQuery { Collection> resultReceivers = new ArrayList<>(); resultReceivers.add(new AbortResultReceiver(resultReceiver)); return new BlockingProcessor, Double>((Class>)(Class) Iterable.class, Double.class, - ConcurrencyTestsUtil.getExecutor(), resultReceivers, 1000) { + ConcurrencyTestsUtil.getSharedExecutor(), resultReceivers, 1000) { @Override protected Double createResult(Iterable element) { return 0.0; @@ -168,7 +168,7 @@ public class TestProcessorQuery { resultReceivers.add(resultReceiver); return new AbstractParallelProcessor, Map>((Class>)(Class) Iterable.class, (Class>)(Class) Map.class, - ConcurrencyTestsUtil.getExecutor(), + ConcurrencyTestsUtil.getSharedExecutor(), resultReceivers) { @Override protected ProcessorInstruction> createInstruction(final Iterable element) { @@ -242,7 +242,7 @@ public class TestProcessorQuery { resultReceivers.add(resultReceiver); return new AbstractParallelProcessor>(Double.class, (Class>)(Class) Map.class, - ConcurrencyTestsUtil.getExecutor(), + ConcurrencyTestsUtil.getSharedExecutor(), resultReceivers) { @Override protected ProcessorInstruction> createInstruction(Double element) { @@ -282,7 +282,7 @@ public class TestProcessorQuery { resultReceivers.add(resultReceiver); return new AbstractParallelProcessor>(Double.class, (Class>)(Class) Map.class, - ConcurrencyTestsUtil.getExecutor(), + ConcurrencyTestsUtil.getSharedExecutor(), resultReceivers) { @Override protected ProcessorInstruction> createInstruction(Double element) { diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorElementProcessing.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorElementProcessing.java new file mode 100644 index 00000000000..c2132833e8c --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorElementProcessing.java @@ -0,0 +1,152 @@ +package com.sap.sse.datamining.impl.components; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sse.common.Util.Pair; +import com.sap.sse.datamining.components.AdditionalResultDataBuilder; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.components.ProcessorInstruction; +import com.sap.sse.datamining.test.util.ConcurrencyTestsUtil; +import com.sap.sse.datamining.test.util.components.StatefulBlockingInstruction; + +public class TestAbstractParallelProcessorElementProcessing { + + private ThreadPoolExecutor executor; + private Processor, Object> processor; + private List> createdInstructions; + + @Before + public void initialize() { + int corePoolSize = Runtime.getRuntime().availableProcessors(); + executor = new ThreadPoolExecutor(corePoolSize, corePoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); + createdInstructions = new ArrayList<>(); + + @SuppressWarnings("unchecked") + Class> inputType = (Class>)(Class) Pair.class; + processor = new AbstractParallelProcessor, Object>(inputType, Object.class, executor, Collections.emptySet()) { + @Override + protected ProcessorInstruction createInstruction(Pair sleepTime) { + StatefulBlockingInstruction instruction = new StatefulBlockingInstruction<>(this, sleepTime.getA(), sleepTime.getB()); + createdInstructions.add(instruction); + return instruction; + } + @Override + protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { } + }; + } + + @Test + public void testSimpleProcessing() throws InterruptedException { + long stepDuration = 10; + int numberOfSteps = 1; + Pair input = new Pair(stepDuration, numberOfSteps); + int elementCount = executor.getMaximumPoolSize() * 2; + for (int i = 0; i < elementCount; i++) { + processor.processElement(input); + } + + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + double executionTime = Math.ceil((double) elementCount / executor.getMaximumPoolSize()) * stepDuration * numberOfSteps; + executor.shutdown(); + assertTrue("Executor couldn't terminate", executor.awaitTermination((long) (executionTime * 1.2), TimeUnit.SECONDS)); + for (StatefulBlockingInstruction instruction : createdInstructions) { + assertTrue("run wasn't called", instruction.runWasCalled()); + assertTrue("computeResult wasn't called", instruction.computeResultWasCalled()); + assertTrue("computeResult didn't finish", instruction.computeResultWasFinished()); + assertFalse("computeResult was aborted", instruction.computeResultWasAborted()); + } + } + + @Test + public void testProcessingAfterFinish() throws InterruptedException { + long stepDuration = 10; + int numberOfSteps = 5; + Pair input = new Pair(stepDuration, numberOfSteps); + int elementCount = executor.getMaximumPoolSize() + 1; // Last instruction will be queued + for (int i = 0; i < elementCount; i++) { + processor.processElement(input); + } + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + Thread.sleep(stepDuration); // Giving some time to ensure execution of unqueued instructions + Thread finishingThread = ConcurrencyTestsUtil.tryToFinishTheProcessorInAnotherThread(processor); + do { + Thread.sleep(1); + } while (!finishingThread.isAlive()); + assertFalse("Processor is already finished", processor.isFinished()); + // Processor not yet finished. New elements will be accepted + processor.processElement(input); + elementCount++; + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + finishingThread.join(); + assertTrue("Processor isn't finished", processor.isFinished()); + processor.processElement(input); + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + for (StatefulBlockingInstruction instruction : createdInstructions) { + assertTrue("run wasn't called", instruction.runWasCalled()); + assertTrue("computeResult wasn't called", instruction.computeResultWasCalled()); + assertTrue("computeResult didn't finish", instruction.computeResultWasFinished()); + assertFalse("computeResult was aborted", instruction.computeResultWasAborted()); + } + } + + @Test + public void testProcessingAfterAbort() throws InterruptedException { + long stepDuration = 10; + int numberOfSteps = 5; + Pair input = new Pair(stepDuration, numberOfSteps); + int elementCount = executor.getMaximumPoolSize() // Set of finished instructions + + executor.getMaximumPoolSize() // Set of started, but not yet finished instructions + + 1; // Scheduled instruction + for (int i = 0; i < elementCount; i++) { + processor.processElement(input); + } + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + long instructionDuration = stepDuration * numberOfSteps; + Thread.sleep(instructionDuration + stepDuration); // Time to finish first set and start second set + processor.abort(); + processor.processElement(new Pair<>(0L, 0)); + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + executor.shutdown(); + assertTrue("Executor couldn't terminate", executor.awaitTermination(2 * stepDuration, TimeUnit.MILLISECONDS)); + for (int i = 0; i < createdInstructions.size(); i++) { + StatefulBlockingInstruction instruction = createdInstructions.get(i); + // run should be called for all instructions + assertTrue("run wasn't called", instruction.runWasCalled()); + if (i < executor.getMaximumPoolSize()) { + // First set of instructions should be processed normally + assertTrue("computeResult wasn't called", instruction.computeResultWasCalled()); + assertTrue("computeResult didn't finish", instruction.computeResultWasFinished()); + assertFalse("computeResult was aborted", instruction.computeResultWasAborted()); + } else if (i < executor.getMaximumPoolSize() * 2) { + // Second set of instructions was running when the processor was aborted. computeResult should be aborted + assertTrue("computeResult wasn't called", instruction.computeResultWasCalled()); + assertTrue("computeResult didn't finish", instruction.computeResultWasFinished()); + assertTrue("computeResult wasn't aborted", instruction.computeResultWasAborted()); + } else { + // Last instructions was still scheduled when the processor was aborted. computeResult should not be called + assertFalse("computeResult of last instruction was called", instruction.computeResultWasCalled()); + assertFalse("computeResult of last instruction finished", instruction.computeResultWasFinished()); + assertFalse("computeResult was aborted", instruction.computeResultWasAborted()); + } + } + } + +} diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorFinishing.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorFinishing.java index 686d2645408..c8ff91fc379 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorFinishing.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorFinishing.java @@ -68,7 +68,7 @@ public class TestAbstractParallelProcessorFinishing { } private AbstractParallelProcessor createProcessor(Collection> receivers) { - return new AbstractParallelProcessor(Integer.class, Integer.class, ConcurrencyTestsUtil.getExecutor(), receivers) { + return new AbstractParallelProcessor(Integer.class, Integer.class, ConcurrencyTestsUtil.getSharedExecutor(), receivers) { @Override protected ProcessorInstruction createInstruction(Integer partialElement) { return new AbstractProcessorInstruction(this) { diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorWithManySimpleInstructions.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorWithManySimpleInstructions.java index 90d99ebac39..79bcfe84061 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorWithManySimpleInstructions.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorWithManySimpleInstructions.java @@ -37,7 +37,7 @@ public class TestAbstractParallelProcessorWithManySimpleInstructions { Collection> receivers = new ArrayList<>(); receivers.add(receiver); - processor = new AbstractParallelProcessor(Integer.class, Integer.class, ConcurrencyTestsUtil.getExecutor(), receivers) { + processor = new AbstractParallelProcessor(Integer.class, Integer.class, ConcurrencyTestsUtil.getSharedExecutor(), receivers) { @Override protected ProcessorInstruction createInstruction(final Integer element) { return new AbstractProcessorInstruction(this) { diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestDataRetrieverChainCreation.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestDataRetrieverChainCreation.java index 17a2721711a..301fe5939c2 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestDataRetrieverChainCreation.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestDataRetrieverChainCreation.java @@ -1,6 +1,6 @@ package com.sap.sse.datamining.impl.components; -import static com.sap.sse.datamining.test.util.ConcurrencyTestsUtil.getExecutor; +import static com.sap.sse.datamining.test.util.ConcurrencyTestsUtil.getSharedExecutor; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; @@ -90,12 +90,12 @@ public class TestDataRetrieverChainCreation { assertThat(chainClone, not(dataRetrieverChainDefinition)); - chainClone.startBuilding(ConcurrencyTestsUtil.getExecutor()); + chainClone.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); } @Test public void testThatTheDataRetrieverChainBuilderHasToBeInitialized() { - DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); try { chainBuilder.getCurrentRetrievedDataType(); @@ -126,7 +126,7 @@ public class TestDataRetrieverChainCreation { @Test public void testStepByStepDataRetrieverChainCreation() throws InterruptedException { - DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); chainBuilder.stepFurther(); //Initialization assertThat(chainBuilder.getCurrentRetrievedDataType().equals(Test_Regatta.class), is(true)); @@ -206,7 +206,7 @@ public class TestDataRetrieverChainCreation { @Test(expected=IllegalArgumentException.class) public void testSettingAFilterWithWrongElementType() { - DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); chainBuilder.stepFurther(); //Initialization chainBuilder.setFilter(raceFilter); @@ -214,7 +214,7 @@ public class TestDataRetrieverChainCreation { @Test(expected=IllegalArgumentException.class) public void testSettingAResultReceiverWithWrongInputType() { - DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); chainBuilder.stepFurther(); //Initialization chainBuilder.addResultReceiver(legReceiver); @@ -253,12 +253,12 @@ public class TestDataRetrieverChainCreation { raceRetrieverClass, Test_HasRaceContext.class, "race"); - dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); } @Test(expected=IllegalStateException.class) public void testSteppingToFar() { - DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); while (chainBuilder.canStepFurther()) { chainBuilder.stepFurther(); } @@ -272,7 +272,7 @@ public class TestDataRetrieverChainCreation { chainWithSettings.endWith(TestLegOfCompetitorWithContextRetrievalProcessor.class, Test_RetrievalProcessorWithSettings.class, Test_HasLegOfCompetitorContext.class, Test_RetrievalProcessorSettings.class, new Test_RetrievalProcessorSettings("Default Settings"), "legOfCompetitor"); - DataRetrieverChainBuilder> chainBuilder = chainWithSettings.startBuilding(getExecutor()); + DataRetrieverChainBuilder> chainBuilder = chainWithSettings.startBuilding(getSharedExecutor()); while (chainBuilder.canStepFurther()) { chainBuilder.stepFurther(); } @@ -311,7 +311,7 @@ public class TestDataRetrieverChainCreation { chainWithSettings.endWith(TestLegOfCompetitorWithContextRetrievalProcessor.class, Test_RetrievalProcessorWithSettings.class, Test_HasLegOfCompetitorContext.class, Test_RetrievalProcessorSettings.class, new Test_RetrievalProcessorSettings("Default Settings"), "legOfCompetitor"); - DataRetrieverChainBuilder> chainBuilder = chainWithSettings.startBuilding(getExecutor()); + DataRetrieverChainBuilder> chainBuilder = chainWithSettings.startBuilding(getSharedExecutor()); while (chainBuilder.canStepFurther()) { chainBuilder.stepFurther(); } @@ -320,7 +320,7 @@ public class TestDataRetrieverChainCreation { @Test(expected=IllegalStateException.class) public void testRetrieverWithNoSettingsButSettedSettingsCreated() { - DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); + DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getSharedExecutor()); chainBuilder.stepFurther(); // Initialization chainBuilder.setSettings(new WrongSettings()); } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestFilteringProcessors.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestFilteringProcessors.java index 7b5b214aa35..cb44c0836a3 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestFilteringProcessors.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestFilteringProcessors.java @@ -34,7 +34,7 @@ public class TestFilteringProcessors { return element % 2 == 0; } }; - Processor filteringProcessor = new ParallelFilteringProcessor(Integer.class, ConcurrencyTestsUtil.getExecutor(), receivers, elementIsEvenCriteria); + Processor filteringProcessor = new ParallelFilteringProcessor(Integer.class, ConcurrencyTestsUtil.getSharedExecutor(), receivers, elementIsEvenCriteria); ConcurrencyTestsUtil.processElements(filteringProcessor, createElementsToProcess()); ConcurrencyTestsUtil.sleepFor(100); // Giving the processor time to process the instructions diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelExtractionProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelExtractionProcessor.java index 20796ebeabd..d39a556d735 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelExtractionProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelExtractionProcessor.java @@ -63,7 +63,7 @@ public class TestParallelExtractionProcessor { @Test public void testValueExtraction() { - Processor, GroupedDataEntry> processor = new ParallelGroupedElementsValueExtractionProcessor(ConcurrencyTestsUtil.getExecutor(), receivers, getCrossSumFunction); + Processor, GroupedDataEntry> processor = new ParallelGroupedElementsValueExtractionProcessor(ConcurrencyTestsUtil.getSharedExecutor(), receivers, getCrossSumFunction); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(processor, elements); ConcurrencyTestsUtil.sleepFor(100); //Giving the processor time to finish the instructions @@ -81,7 +81,7 @@ public class TestParallelExtractionProcessor { @Test public void testValueExtractionWithInvalidFunction() { - Processor, GroupedDataEntry> processor = new ParallelGroupedElementsValueExtractionProcessor(ConcurrencyTestsUtil.getExecutor(), receivers, invalidFunction); + Processor, GroupedDataEntry> processor = new ParallelGroupedElementsValueExtractionProcessor(ConcurrencyTestsUtil.getSharedExecutor(), receivers, invalidFunction); ConcurrencyTestsUtil.processElements(processor, createElements()); ConcurrencyTestsUtil.sleepFor(100); //Giving the processor time to finish the instructions assertThat("Values have been received, but the processor function is invalid.", receivedValues.isEmpty(), is(true)); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java index 1480e10e7d8..adacc161655 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java @@ -57,7 +57,7 @@ public class TestParallelMultiDimensionalGroupingProcessor { @Test(expected=IllegalArgumentException.class) public void testConstructionWithNullDimensions() { - new ParallelMultiDimensionsValueNestingGroupingProcessor<>(Number.class, ConcurrencyTestsUtil.getExecutor(), receivers, null); + new ParallelMultiDimensionsValueNestingGroupingProcessor<>(Number.class, ConcurrencyTestsUtil.getSharedExecutor(), receivers, null); } @Test(expected=IllegalArgumentException.class) diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestRetrieverFilterProcessorChain.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestRetrieverFilterProcessorChain.java index 5675e8851d2..8ef8b80a8c5 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestRetrieverFilterProcessorChain.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestRetrieverFilterProcessorChain.java @@ -72,7 +72,7 @@ public class TestRetrieverFilterProcessorChain { resultReceivers.add(retrievalProcessor); @SuppressWarnings("unchecked") Processor>, Iterable> layeredRetrievalProcessor = new AbstractRetrievalProcessor>, Iterable>((Class>>)(Class) Iterable.class, (Class>)(Class) Iterable.class, - ConcurrencyTestsUtil.getExecutor(), resultReceivers, 0) { + ConcurrencyTestsUtil.getSharedExecutor(), resultReceivers, 0) { @Override protected Iterable> retrieveData(Iterable> element) { return element; @@ -110,11 +110,11 @@ public class TestRetrieverFilterProcessorChain { return element >= 0; } }; - Processor filtrationProcessor = new ParallelFilteringProcessor<>(Integer.class, ConcurrencyTestsUtil.getExecutor(), filtrationResultReceivers, elementGreaterZeroFilterCriteria); + Processor filtrationProcessor = new ParallelFilteringProcessor<>(Integer.class, ConcurrencyTestsUtil.getSharedExecutor(), filtrationResultReceivers, elementGreaterZeroFilterCriteria); Collection> retrievalResultReceivers = new ArrayList<>(); retrievalResultReceivers.add(filtrationProcessor); - retrievalProcessor = new AbstractRetrievalProcessor, Integer>((Class>)(Class) Iterable.class, Integer.class, ConcurrencyTestsUtil.getExecutor(), retrievalResultReceivers, 1) { + retrievalProcessor = new AbstractRetrievalProcessor, Integer>((Class>)(Class) Iterable.class, Integer.class, ConcurrencyTestsUtil.getSharedExecutor(), retrievalResultReceivers, 1) { @Override protected Iterable retrieveData(Iterable element) { return element; diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestAbstractStoringParallelAggregationProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestAbstractStoringParallelAggregationProcessor.java index 8054fd520dc..cb1ef22b8d2 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestAbstractStoringParallelAggregationProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestAbstractStoringParallelAggregationProcessor.java @@ -48,7 +48,7 @@ public class TestAbstractStoringParallelAggregationProcessor { @Test public void testAbstractAggregationHandling() throws InterruptedException { - Processor, Map> processor = new AbstractParallelGroupedDataStoringAggregationProcessor(ConcurrencyTestsUtil.getExecutor(), receivers, "Sum") { + Processor, Map> processor = new AbstractParallelGroupedDataStoringAggregationProcessor(ConcurrencyTestsUtil.getSharedExecutor(), receivers, "Sum") { @Override protected void storeElement(GroupedDataEntry element) { elementStore.add(element); @@ -104,7 +104,7 @@ public class TestAbstractStoringParallelAggregationProcessor { } } }); - Processor, Map> processor = new AbstractParallelGroupedDataStoringAggregationProcessor(ConcurrencyTestsUtil.getExecutor(), receivers, "Sum") { + Processor, Map> processor = new AbstractParallelGroupedDataStoringAggregationProcessor(ConcurrencyTestsUtil.getSharedExecutor(), receivers, "Sum") { @Override protected void storeElement(GroupedDataEntry element) { if (element.getDataEntry() < 0) { diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAggregationProcessors.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAggregationProcessors.java index 2b04658099e..2d90d6f4312 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAggregationProcessors.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAggregationProcessors.java @@ -16,7 +16,7 @@ public class TestParallelAggregationProcessors extends AbstractTestParallelAvera @Test public void testSumAggregationProcessor() throws InterruptedException { - Processor, Map> sumAggregationProcessor = ParallelGroupedNumberDataSumAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getExecutor(), receivers); + Processor, Map> sumAggregationProcessor = ParallelGroupedNumberDataSumAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getSharedExecutor(), receivers); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(sumAggregationProcessor, elements); @@ -27,7 +27,7 @@ public class TestParallelAggregationProcessors extends AbstractTestParallelAvera @Test public void testMedianAggregationProcessor() throws InterruptedException { - Processor, Map> medianAggregationProcessor = ParallelGroupedNumberDataMedianAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getExecutor(), receivers); + Processor, Map> medianAggregationProcessor = ParallelGroupedNumberDataMedianAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getSharedExecutor(), receivers); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(medianAggregationProcessor, elements); @@ -38,7 +38,7 @@ public class TestParallelAggregationProcessors extends AbstractTestParallelAvera @Test public void testMaxAggregationProcessor() throws InterruptedException { - Processor, Map> maxAggregationProcessor = ParallelGroupedNumberDataMaxAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getExecutor(), receivers); + Processor, Map> maxAggregationProcessor = ParallelGroupedNumberDataMaxAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getSharedExecutor(), receivers); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(maxAggregationProcessor, elements); @@ -49,7 +49,7 @@ public class TestParallelAggregationProcessors extends AbstractTestParallelAvera @Test public void testMinAggregationProcessor() throws InterruptedException { - Processor, Map> minAggregationProcessor = ParallelGroupedNumberDataMinAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getExecutor(), receivers); + Processor, Map> minAggregationProcessor = ParallelGroupedNumberDataMinAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getSharedExecutor(), receivers); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(minAggregationProcessor, elements); @@ -60,7 +60,7 @@ public class TestParallelAggregationProcessors extends AbstractTestParallelAvera @Test public void testCountAggregationProcessor() throws InterruptedException { - Processor, Map> countAggregationProcessor = ParallelGroupedDataCountAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getExecutor(), receivers); + Processor, Map> countAggregationProcessor = ParallelGroupedDataCountAggregationProcessor.getDefinition().construct(ConcurrencyTestsUtil.getSharedExecutor(), receivers); @SuppressWarnings("unchecked") Collection> elements = (Collection>)(Collection) createElements(); ConcurrencyTestsUtil.processElements(countAggregationProcessor, elements); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAveragingProcessors.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAveragingProcessors.java index 74ae148c327..1b6fa0900fe 100755 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAveragingProcessors.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelAveragingProcessors.java @@ -19,7 +19,7 @@ public class TestParallelAveragingProcessors extends AbstractTestParallelAveragi @Test public void testAverageAggregationProcessor() throws InterruptedException { Processor, Map>> averageAggregationProcessor = ParallelGroupedNumberDataAverageAggregationProcessor - .getDefinition().construct(ConcurrencyTestsUtil.getExecutor(), receivers); + .getDefinition().construct(ConcurrencyTestsUtil.getSharedExecutor(), receivers); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(averageAggregationProcessor, elements); averageAggregationProcessor.finish(); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelGroupedDataCollectingAsSetProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelGroupedDataCollectingAsSetProcessor.java index 73c369a3592..bdee1ed0eec 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelGroupedDataCollectingAsSetProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/aggregators/TestParallelGroupedDataCollectingAsSetProcessor.java @@ -37,7 +37,7 @@ public class TestParallelGroupedDataCollectingAsSetProcessor { @Test public void testDataCollecting() throws InterruptedException { - Processor, Map>> collectingProcessor = new ParallelGroupedDataCollectingAsSetProcessor(ConcurrencyTestsUtil.getExecutor(), receivers); + Processor, Map>> collectingProcessor = new ParallelGroupedDataCollectingAsSetProcessor(ConcurrencyTestsUtil.getSharedExecutor(), receivers); Collection> elements = createElements(); ConcurrencyTestsUtil.processElements(collectingProcessor, elements); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java index 57b9648370f..8805a8a8252 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java @@ -63,7 +63,7 @@ public class TestFunctionManagerAsFunctionRegistry { DataRetrieverChainDefinitionRegistry dataRetrieverChainDefinitionRegistry = new DataRetrieverChainDefinitionManager(); AggregationProcessorDefinitionRegistry aggregationProcessorDefinitionRegistry = new AggregationProcessorDefinitionManager(); QueryDefinitionDTORegistry queryDefinitionRegistry = new QueryDefinitionDTOManager(); - ModifiableDataMiningServer server = new DataMiningServerImpl(ConcurrencyTestsUtil.getExecutor(), functionManager, + ModifiableDataMiningServer server = new DataMiningServerImpl(ConcurrencyTestsUtil.getSharedExecutor(), functionManager, dataSourceProviderRegistry, dataRetrieverChainDefinitionRegistry, aggregationProcessorDefinitionRegistry, diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java index edceda32792..174b572c8e4 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java @@ -22,7 +22,7 @@ import com.sap.sse.datamining.test.domain.impl.Test_TeamImpl; public final class ComponentTestsUtil { - private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); + private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getSharedExecutor()); public static ProcessorFactory getProcessorFactory() { return processorFactory; diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ConcurrencyTestsUtil.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ConcurrencyTestsUtil.java index 0088cb8a137..eea040f4e2e 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ConcurrencyTestsUtil.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ConcurrencyTestsUtil.java @@ -19,7 +19,7 @@ public class ConcurrencyTestsUtil extends TestsUtil { private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); private static final ExecutorService executor = new DataMiningExecutorService(THREAD_POOL_SIZE); - public static ExecutorService getExecutor() { + public static ExecutorService getSharedExecutor() { return executor; } @@ -47,7 +47,7 @@ public class ConcurrencyTestsUtil extends TestsUtil { } } - public static void tryToFinishTheProcessorInAnotherThread(final Processor processor) { + public static Thread tryToFinishTheProcessorInAnotherThread(final Processor processor) { Thread finishingThread = new Thread(new Runnable() { @Override public void run() { @@ -59,6 +59,7 @@ public class ConcurrencyTestsUtil extends TestsUtil { } }); finishingThread.start(); + return finishingThread; } protected ConcurrencyTestsUtil() { diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/TestsUtil.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/TestsUtil.java index 94f70080799..6a2cd0b8cbc 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/TestsUtil.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/TestsUtil.java @@ -1,6 +1,7 @@ package com.sap.sse.datamining.test.util; import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutorService; import com.sap.sse.datamining.ModifiableDataMiningServer; import com.sap.sse.datamining.components.management.AggregationProcessorDefinitionRegistry; @@ -55,12 +56,16 @@ public class TestsUtil { } public static ModifiableDataMiningServer createNewServer() { + return createNewServer(ConcurrencyTestsUtil.getSharedExecutor()); + } + + public static ModifiableDataMiningServer createNewServer(ExecutorService executor) { FunctionRegistry functionRegistry = new FunctionManager(); DataSourceProviderRegistry dataSourceProviderRegistry = new DataSourceProviderManager(); DataRetrieverChainDefinitionRegistry dataRetrieverChainDefinitionRegistry = new DataRetrieverChainDefinitionManager(); AggregationProcessorDefinitionRegistry aggregationProcessorDefinitionRegistry = new AggregationProcessorDefinitionManager(); QueryDefinitionDTORegistry queryDefinitionRegistry = new QueryDefinitionDTOManager(); - return new DataMiningServerImpl(ConcurrencyTestsUtil.getExecutor(), functionRegistry, + return new DataMiningServerImpl(executor, functionRegistry, dataSourceProviderRegistry, dataRetrieverChainDefinitionRegistry, aggregationProcessorDefinitionRegistry, diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulBlockingInstruction.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulBlockingInstruction.java new file mode 100644 index 00000000000..bdac4dce0e4 --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulBlockingInstruction.java @@ -0,0 +1,58 @@ +package com.sap.sse.datamining.test.util.components; + +import com.sap.sse.datamining.components.ProcessorInstructionHandler; +import com.sap.sse.datamining.impl.components.ProcessorInstructionPriority; + +public class StatefulBlockingInstruction extends StatefulProcessorInstruction { + + protected final long stepDuration; + protected final int numberOfSteps; + protected final ResultType result; + + private boolean computeResultWasAborted; + + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, long stepDuration, int numberOfSteps) { + this(handler, 0, stepDuration, numberOfSteps, null); + } + + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, ProcessorInstructionPriority priority, long stepDuration, int numberOfSteps, ResultType result) { + this(handler, priority.asIntValue(), stepDuration, numberOfSteps, result); + } + + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, int priority, long stepDuration, int numberOfSteps, ResultType result) { + super(handler, priority); + this.stepDuration = stepDuration; + this.numberOfSteps = numberOfSteps; + this.result = result; + } + + @Override + protected ResultType internalComputeResult() throws Exception { + if (getTotalBlockDuration() > 0) { + actionBeforeBlock(); + for (int i = 0; i < numberOfSteps; i++) { + if (isAborted()) { + actionBeforeAbort(); + computeResultWasAborted = true; + break; + } + Thread.sleep(stepDuration); + } + actionAfterBlock(); + } + return result; + } + + protected void actionBeforeBlock() { } + protected void actionBeforeAbort() { } + protected void actionAfterBlock() { } + + public long getTotalBlockDuration() { + return stepDuration * numberOfSteps; + } + + public boolean computeResultWasAborted() { + return computeResultWasAborted; + } + +} \ No newline at end of file diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulProcessorInstruction.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulProcessorInstruction.java new file mode 100644 index 00000000000..b34a376e1eb --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulProcessorInstruction.java @@ -0,0 +1,54 @@ +package com.sap.sse.datamining.test.util.components; + +import com.sap.sse.datamining.components.ProcessorInstructionHandler; +import com.sap.sse.datamining.impl.components.AbstractProcessorInstruction; +import com.sap.sse.datamining.impl.components.ProcessorInstructionPriority; + +public abstract class StatefulProcessorInstruction extends AbstractProcessorInstruction { + + private boolean runWasCalled; + private boolean computeResultWasCalled; + private boolean computeResultWasFinished; + + public StatefulProcessorInstruction(ProcessorInstructionHandler processor, int priority) { + super(processor, priority); + } + + public StatefulProcessorInstruction(ProcessorInstructionHandler handler, + ProcessorInstructionPriority priority) { + super(handler, priority); + } + + public StatefulProcessorInstruction(ProcessorInstructionHandler handler) { + super(handler); + } + + @Override + public void run() { + runWasCalled = true; + super.run(); + } + + @Override + protected ResultType computeResult() throws Exception { + computeResultWasCalled = true; + ResultType result = internalComputeResult(); + computeResultWasFinished = true; + return result; + } + + protected abstract ResultType internalComputeResult() throws Exception; + + public boolean runWasCalled() { + return runWasCalled; + } + + public boolean computeResultWasCalled() { + return computeResultWasCalled; + } + + public boolean computeResultWasFinished() { + return computeResultWasFinished; + } + +} diff --git a/java/com.sap.sse.datamining.ui/.project b/java/com.sap.sse.datamining.ui/.project index 6d2dc52604f..003e74ffac2 100644 --- a/java/com.sap.sse.datamining.ui/.project +++ b/java/com.sap.sse.datamining.ui/.project @@ -21,12 +21,12 @@ - com.google.gdt.eclipse.core.webAppProjectValidator + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator - com.google.gwt.eclipse.core.gwtProjectValidator + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator @@ -34,6 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sse.datamining.ui/META-INF/MANIFEST.MF b/java/com.sap.sse.datamining.ui/META-INF/MANIFEST.MF index 60067bcd7df..b617aa4ee4b 100644 --- a/java/com.sap.sse.datamining.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.datamining.ui/META-INF/MANIFEST.MF @@ -17,7 +17,6 @@ Export-Package: com.sap.sse.datamining.ui.client, com.sap.sse.datamining.ui.client.presentation, com.sap.sse.datamining.ui.client.presentation.dataproviders, com.sap.sse.datamining.ui.client.selection, - com.sap.sse.datamining.ui.client.selection.filter, com.sap.sse.datamining.ui.client.settings Bundle-ActivationPolicy: lazy Automatic-Module-Name: com.sap.sse.datamining.ui diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java index ef961bb9412..3b27225bf42 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java @@ -5,7 +5,6 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.HTML; @@ -18,6 +17,8 @@ import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; import com.sap.sse.datamining.ui.client.QueryDefinitionChangedListener; import com.sap.sse.datamining.ui.client.StringMessages; import com.sap.sse.datamining.ui.client.developer.QueryDefinitionParser.TypeToCodeStrategy; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.ComponentWithoutSettings; import com.sap.sse.gwt.client.shared.settings.ComponentContext; @@ -79,11 +80,11 @@ public class QueryDefinitionViewer extends ComponentWithoutSettings implements Q switch (contentPanel.getSelectedIndex()) { case 0: TextExporter.exportToClipboard(queryDefinitionParser.parseToDetailsAsText(currentDefinition)); - Window.alert(stringMessages.copiedToClipboard()); + Notification.notify(stringMessages.copiedToClipboard(), NotificationType.INFO); break; case 1: TextExporter.exportToClipboard(queryDefinitionParser.parseToCodeAsText(currentDefinition, getTypeStrategy())); - Window.alert(stringMessages.copiedToClipboard()); + Notification.notify(stringMessages.copiedToClipboard(), NotificationType.INFO); break; } } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/DimensionFilterSelectionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/DimensionFilterSelectionProvider.java similarity index 99% rename from java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/DimensionFilterSelectionProvider.java rename to java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/DimensionFilterSelectionProvider.java index d6acbee452a..2a648f0fefd 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/DimensionFilterSelectionProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/DimensionFilterSelectionProvider.java @@ -1,4 +1,4 @@ -package com.sap.sse.datamining.ui.client.selection.filter; +package com.sap.sse.datamining.ui.client.selection; import java.io.Serializable; import java.util.ArrayList; diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/HierarchicalDimensionListFilterSelectionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/HierarchicalDimensionListFilterSelectionProvider.java similarity index 99% rename from java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/HierarchicalDimensionListFilterSelectionProvider.java rename to java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/HierarchicalDimensionListFilterSelectionProvider.java index 0581878a35f..da61d4672b7 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/HierarchicalDimensionListFilterSelectionProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/HierarchicalDimensionListFilterSelectionProvider.java @@ -1,4 +1,4 @@ -package com.sap.sse.datamining.ui.client.selection.filter; +package com.sap.sse.datamining.ui.client.selection; import java.io.Serializable; import java.util.ArrayList; diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java index 57e88513dc1..3b020589f63 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java @@ -43,7 +43,6 @@ import com.sap.sse.datamining.ui.client.StatisticProvider; import com.sap.sse.datamining.ui.client.WithControls; import com.sap.sse.datamining.ui.client.developer.PredefinedQueryRunner; import com.sap.sse.datamining.ui.client.developer.QueryDefinitionViewer; -import com.sap.sse.datamining.ui.client.selection.filter.HierarchicalDimensionListFilterSelectionProvider; import com.sap.sse.datamining.ui.client.settings.AdvancedDataMiningSettings; import com.sap.sse.datamining.ui.client.settings.AdvancedDataMiningSettingsDialogComponent; import com.sap.sse.gwt.client.ErrorReporter; diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java index c06d793afd0..9e489e8bda5 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java @@ -219,24 +219,12 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent displayDuplicates = new HashMap<>(); - for (ExtractionFunctionWithContext statistic : availableExtractionFunctions) { - ExtractionFunctionWithContext duplicate = displayDuplicates - .get(statistic.getExtractionFunction().getDisplayName()); - if (duplicate == null) { - displayDuplicates.put(statistic.getExtractionFunction().getDisplayName(), statistic); - } else { - statistic.setVerbose(true); - duplicate.setVerbose(true); - } - } extractionFunctionSuggestBox.setSelectableValues(availableExtractionFunctions); // TODO Do not pre-select the first element. The other UI components have to be able to handle "empty content" ExtractionFunctionWithContext currentValue = extractionFunctionSuggestBox.getExtractionFunction(); ExtractionFunctionWithContext valueToBeSelected = availableExtractionFunctions.contains(currentValue) ? currentValue : Util.first(availableExtractionFunctions); - extractionFunctionSuggestBox.getValueBox().setValue(valueToBeSelected.getDisplayString(), false); extractionFunctionSuggestBox.setExtractionFunction(valueToBeSelected); } } @@ -377,18 +365,9 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent matchingStrings; - private boolean verbose; public ExtractionFunctionWithContext(DataRetrieverChainDefinitionDTO retrieverChain, FunctionDTO extractionFunction) { @@ -511,30 +489,13 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent) getSuggestOracle(); @@ -622,9 +583,10 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent extends MultiSelectionModel { - - // Ensure one value per key - private final Map selectedSet; - /** - * A map of keys to the item and its pending selection state. - */ - private final Map> selectionChanges; - - private boolean blockNotifications; - - /** - * Constructs a MultiSelectionModel without a key provider. - */ - public ControllableMultiSelectionModel() { - this(null); - } - - /** - * Constructs a MultiSelectionModel with the given key provider. - * - * @param keyProvider - * an instance of ProvidesKey, or null if the item should act as its own key - */ - public ControllableMultiSelectionModel(ProvidesKey keyProvider) { - super(keyProvider); - this.selectedSet = new HashMap(); - this.selectionChanges = new HashMap>(); - blockNotifications = false; - } - - public void setBlockNotifications(boolean blockNotifications) { - this.blockNotifications = blockNotifications; - } - - /** - * Deselect all selected values. - */ - @Override - public void clear() { - // Clear the current list of pending changes. - selectionChanges.clear(); - - /* - * Add a pending change to deselect each key that is currently selected. We cannot just clear the selected set, - * because then we would not know which keys were selected before we cleared, which we need to know to determine - * if we should fire an event. - */ - for (T value : selectedSet.values()) { - selectionChanges.put(getKey(value), new SelectionChange(value, false)); - } - scheduleSelectionChangeEvent(); - } - - /** - * Get the set of selected items as a copy. If multiple selected items share the same key, only the last selected - * item is included in the set. - * - * @return the set of selected items - */ - @Override - public Set getSelectedSet() { - determineChanges(); - return new HashSet(selectedSet.values()); - } - - @Override - public boolean isSelected(T item) { - determineChanges(); - return selectedSet.containsKey(getKey(item)); - } - - @Override - public void setSelected(T item, boolean selected) { - selectionChanges.put(getKey(item), new SelectionChange(item, selected)); - scheduleSelectionChangeEvent(); - } - - @Override - protected void scheduleSelectionChangeEvent() { - if (!blockNotifications) { - super.scheduleSelectionChangeEvent(); - } else { - determineChanges(); - } - } - - @Override - protected void fireSelectionChangeEvent() { - if (isEventScheduled()) { - setEventCancelled(true); - } - determineChanges(); - } - - private void determineChanges() { - if (selectionChanges.isEmpty()) { - return; - } - - boolean changed = false; - for (Map.Entry> entry : selectionChanges.entrySet()) { - Object key = entry.getKey(); - SelectionChange value = entry.getValue(); - boolean selected = value.isSelected(); - - T oldValue = selectedSet.get(key); - if (selected) { - selectedSet.put(key, value.getItem()); - Object oldKey = getKey(oldValue); - if (!changed) { - changed = (oldKey == null) ? (key != null) : !oldKey.equals(key); - } - } else { - if (oldValue != null) { - selectedSet.remove(key); - changed = true; - } - } - } - selectionChanges.clear(); - - // Fire a selection change event. - if (changed && !blockNotifications) { - SelectionChangeEvent.fire(this); - } - } - - /** - * Stores an item and its pending selection state. - * @param the data type of the item - */ - private static class SelectionChange { - private final T item; - private final boolean isSelected; - - SelectionChange(T item, boolean isSelected) { - this.item = item; - this.isSelected = isSelected; - } - - public T getItem() { - return item; - } - - public boolean isSelected() { - return isSelected; - } - } - -} \ No newline at end of file diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/ProcessorInstructionHandler.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/ProcessorInstructionHandler.java index 5cda3920ca7..b5da6b06261 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/ProcessorInstructionHandler.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/ProcessorInstructionHandler.java @@ -2,9 +2,11 @@ package com.sap.sse.datamining.components; public interface ProcessorInstructionHandler { - public void instructionSucceeded(ResultType result); - public void instructionFailed(Exception e); + boolean isAborted(); - public void afterInstructionFinished(); + void instructionSucceeded(ResultType result); + void instructionFailed(Exception e); + + void afterInstructionFinished(ProcessorInstruction instruction); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/management/FunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/management/FunctionProvider.java index 3d39b5ddba7..de2e410c476 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/management/FunctionProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/management/FunctionProvider.java @@ -24,7 +24,9 @@ public interface FunctionProvider { DataRetrieverChainDefinition dataRetrieverChainDefinition); /** - * @param classLoader TODO + * @param functionDTO The {@link FunctionDTO} to resolve + * @param classLoader The class loader used to get the actual {@link Class} objects described + * by the functionDTO * @return The first function, that matches the given DTO or null */ public Function getFunctionForDTO(FunctionDTO functionDTO, ClassLoader classLoader); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java index c566783e517..ebc777c0da0 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java @@ -69,6 +69,9 @@ public abstract class AbstractParallelMultiDimensionalNestingGroupingProcessor keys = new ArrayList<>(); for (ParameterizedFunction parameterizedDimension : parameterizedDimensions) { + if (isAborted()) { + break; + } keys.add(createGroupKeyFor(input, parameterizedDimension.getFunction(), parameterizedDimension.getParameterProvider())); } return new CompoundGroupKey(keys); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelProcessor.java index caa9f427424..77e478588f3 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelProcessor.java @@ -59,18 +59,21 @@ public abstract class AbstractParallelProcessor extends A private boolean isInstructionValid(ProcessorInstruction instruction) { return instruction != null; } - + + @Override public void instructionSucceeded(ResultType result) { forwardResultToReceivers(result); } - + + @Override public void instructionFailed(Exception e) { if (!isAborted() || !(e instanceof InterruptedException)) { onFailure(e); } } - - public void afterInstructionFinished() { + + @Override + public void afterInstructionFinished(ProcessorInstruction instruction) { unfinishedInstructionsCounter.getAndDecrement(); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractProcessorInstruction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractProcessorInstruction.java index 130798524c3..1bc4139a1d7 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractProcessorInstruction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractProcessorInstruction.java @@ -52,17 +52,27 @@ public abstract class AbstractProcessorInstruction implements Proces @Override public void run() { try { - ResultType result = computeResult(); - handler.instructionSucceeded(result); + if (!isAborted()) { + ResultType result = computeResult(); + handler.instructionSucceeded(result); + } } catch (Exception e) { handler.instructionFailed(e); } finally { - handler.afterInstructionFinished(); + handler.afterInstructionFinished(this); } } + + protected boolean isAborted() { + return handler.isAborted(); + } protected abstract ResultType computeResult() throws Exception; + public ProcessorInstructionHandler getHandler() { + return handler; + } + @Override public int getPriority() { return priority; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractRetrievalProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractRetrievalProcessor.java index 16513374ae8..7f8eaccc20a 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractRetrievalProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractRetrievalProcessor.java @@ -38,6 +38,9 @@ public abstract class AbstractRetrievalProcessor extends @Override public ResultType computeResult() { for (ResultType retrievedElement : retrieveData(element)) { + if (isAborted()) { + break; + } retrievedDataAmount.incrementAndGet(); forwardResultToReceivers(retrievedElement); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/AbstractParallelAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/AbstractParallelAggregationProcessor.java index 2506db2ffc1..eac75921afb 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/AbstractParallelAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/AbstractParallelAggregationProcessor.java @@ -5,6 +5,7 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.datamining.components.AdditionalResultDataBuilder; import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.components.ProcessorInstruction; import com.sap.sse.datamining.impl.components.AbstractParallelProcessor; import com.sap.sse.datamining.impl.components.AbstractProcessorInstruction; import com.sap.sse.datamining.impl.components.ProcessorInstructionPriority; @@ -26,7 +27,7 @@ public abstract class AbstractParallelAggregationProcessor createInstruction(final InputType element) { + protected ProcessorInstruction createInstruction(final InputType element) { if (needsSynchronization()) { return new AbstractProcessorInstruction(this, ProcessorInstructionPriority.Aggregation) { @Override diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataAverageAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataAverageAggregationProcessor.java index b5ab005762f..f08ad6ae99f 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataAverageAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataAverageAggregationProcessor.java @@ -82,6 +82,9 @@ public class ParallelGroupedNumberDataAverageAggregationProcessor protected Map> aggregateResult() { Map> result = new HashMap<>(); for (Entry sumAggregationEntry : sumPerKey.entrySet()) { + if (isAborted()) { + break; + } GroupKey key = sumAggregationEntry.getKey(); result.put(key, new AverageWithStatsImpl(sumAggregationEntry.getValue().value / elementAmountPerKey.get(key).get(), minPerKey.get(key), maxPerKey.get(key), diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataMedianAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataMedianAggregationProcessor.java index 26da6a4f8e3..c2c7d8010e2 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataMedianAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberDataMedianAggregationProcessor.java @@ -47,6 +47,9 @@ public class ParallelGroupedNumberDataMedianAggregationProcessor protected Map aggregateResult() { Map result = new HashMap<>(); for (Entry> groupedValuesEntry : groupedValues.entrySet()) { + if (isAborted()) { + break; + } result.put(groupedValuesEntry.getKey(), getMedianOf(groupedValuesEntry.getValue())); } return result; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairAverageAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairAverageAggregationProcessor.java index 55db47ed770..69a35eebbf9 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairAverageAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairAverageAggregationProcessor.java @@ -90,6 +90,9 @@ public class ParallelGroupedNumberPairAverageAggregationProcessor protected Map> aggregateResult() { Map> result = new HashMap<>(); for (Entry> sumAggregationEntry : sumPerKey.entrySet()) { + if (isAborted()) { + break; + } GroupKey key = sumAggregationEntry.getKey(); result.put(key, new PairWithStatsImpl(new Pair<>(sumAggregationEntry.getValue().getA() != null ? sumAggregationEntry.getValue().getA().doubleValue() / elementAmountPerKey.get(key).get() : null, sumAggregationEntry.getValue().getB() != null ? sumAggregationEntry.getValue().getB().doubleValue() / elementAmountPerKey.get(key).get() : null) , minPerKey.get(key), maxPerKey.get(key), diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java index 7ec39013c85..41aedda3bc8 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java @@ -81,6 +81,9 @@ public class ParallelGroupedNumberPairCollectingProcessor protected Map> aggregateResult() { Map> result = new HashMap<>(); for (Entry>> sumAggregationEntry : individualPairs.entrySet()) { + if (isAborted()) { + break; + } GroupKey key = sumAggregationEntry.getKey(); result.put(key, new PairWithStatsImpl(null, /* min */ null, diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java index e27ef4a87a4..4debcfcffdb 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java @@ -228,7 +228,7 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< @Override public String toString() { - return getSimpleName(); + return getDeclaringType().getSimpleName() + "." + getSimpleName() + " : " + getReturnType().getSimpleName(); } @Override diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 0ea32b3abb6..ae7f57a6396 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -12,7 +12,6 @@ import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; import com.google.gwt.json.client.JSONValue; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FileUpload; @@ -27,6 +26,8 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SubmitButton; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * A {@link TextBox} together with an upload button that can use the file storage service to @@ -57,8 +58,8 @@ public class URLFieldWithFileUpload extends Composite implements HasValue - - com.google.gdt.eclipse.core.webAppProjectValidator - - - - - com.google.gwt.eclipse.core.gwtProjectValidator - - - com.gwtplugins.gdt.eclipse.core.webAppProjectValidator @@ -44,7 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sse.gwt/.project b/java/com.sap.sse.gwt/.project index 4c176bdd787..34ac3ebf196 100644 --- a/java/com.sap.sse.gwt/.project +++ b/java/com.sap.sse.gwt/.project @@ -10,16 +10,6 @@ - - com.google.gdt.eclipse.core.webAppProjectValidator - - - - - com.google.gwt.eclipse.core.gwtProjectValidator - - - com.gwtplugins.gdt.eclipse.core.webAppProjectValidator @@ -33,7 +23,7 @@ org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature + com.gwtplugins.gwt.eclipse.core.gwtNature com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java index ac73cd69d40..9006a22a6f3 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -1,96 +1,29 @@ package com.sap.sse.gwt.client; import java.util.LinkedList; +import java.util.List; -import com.google.gwt.animation.client.Animation; import com.google.gwt.core.shared.GWT; -import com.google.gwt.dom.client.Style.Cursor; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootPanel; -import com.sap.sse.common.Util.Pair; /** - * Utility class to show non obstructive warning / info messages using a small notification at the bottom of the page. Clicking the notification will hide it. It will autohide after some time. + * Utility class to show multiple non obstructive warning / info messages using a small notification at the bottom of the page. */ public class Notification { - private static final int NOTIFICATION_TIME = 20000; - private static final double FADE_OUT_PERCENT = 0.98; - private static final double FADE_IN_PERCENT = 0.005; - - interface NotificationResources extends ClientBundle { - @Source("notification.gss") - NotificationCSS css(); - } - - interface NotificationCSS extends CssResource { - String snackbar(); - } - - private final static FlowPanel snackBar = new FlowPanel(); - private final static NotificationResources ress = GWT.create(NotificationResources.class); - private static Animation notificationAnimation; - - static { - ress.css().ensureInjected(); - snackBar.addStyleName(ress.css().snackbar()); - snackBar.getElement().getStyle().setCursor(Cursor.POINTER); - snackBar.getElement().getStyle().setZIndex(99); - - notificationAnimation = new Animation() { - @Override - protected void onStart() { - super.onStart(); - snackBar.getElement().getStyle().setOpacity(0); - snackBar.getElement().getStyle().setVisibility(Visibility.VISIBLE); - } - - @Override - protected void onUpdate(double progress) { - if (progress < FADE_IN_PERCENT) { - double relPr = progress / FADE_IN_PERCENT; - double bottom = 60 * relPr - 30; - snackBar.getElement().getStyle().setOpacity(relPr); - snackBar.getElement().getStyle().setBottom(bottom, Unit.PX); - } else if (progress > FADE_OUT_PERCENT) { - double relPr = (progress - FADE_OUT_PERCENT) / (1 - FADE_OUT_PERCENT); - snackBar.getElement().getStyle().setOpacity(1 - relPr); - double bottom = 60 * (1 - relPr) - 30; - snackBar.getElement().getStyle().setBottom(bottom, Unit.PX); - } else { - snackBar.getElement().getStyle().setOpacity(1); - snackBar.getElement().getStyle().setBottom(30, Unit.PX); - } - } - - @Override - protected void onComplete() { - snackBar.getElement().getStyle().setVisibility(Visibility.HIDDEN); - checkQueue(true); - } - - @Override - protected void onCancel() { - snackBar.getElement().getStyle().setVisibility(Visibility.HIDDEN); - checkQueue(true); - } - }; - } - - private final static LinkedList> QUEUE = new LinkedList<>(); - - private Notification() { - } + private static final int MAX_NOTIFICATIONS = 5; // max. notifications to become displayed at the same time + private static final Panel notifications = new FlowPanel(); + private static final List QUEUE = new LinkedList<>(); + static final NotificationResources ress = GWT.create(NotificationResources.class); public enum NotificationType { ERROR("\u2716", "RED", "WHITE"), WARNING("\u26A0", "ORANGE", "WHITE"), - INFO("\u2139", "#008fcc", "WHITE"); + INFO("\u2139", "#008fcc", "WHITE"), + SUCCESS("\u2713", "#00cc00", "WHITE"); private String decorator; private String bgColor; @@ -101,36 +34,73 @@ public class Notification { this.bgColor = bgColor; this.color = color; } + + public String getDecorator() { + return decorator; + } + + public String getColor() { + return color; + } + + public String getBackgroundColor() { + return bgColor; + } } - public static void notify(String message, NotificationType type) { - QUEUE.add(new Pair<>(message, type)); - checkQueue(false); + interface NotificationResources extends ClientBundle { + @Source("notification.css") + NotificationCSS css(); } + interface NotificationCSS extends CssResource { + String notification_bar(); + + String notification(); + } + + static { + ress.css().ensureInjected(); + RootPanel.get().add(notifications); + notifications.getElement().setId("notificationBar"); + notifications.addStyleName(ress.css().notification_bar()); + } + + private Notification() { + } + /** - * Displays next message if non is displayed currently, else does nothing and will by called by currently displaying - * message TTL timer + * Creates new notification and adds it to the notification queue, if one with the equals message does not already exist + * @param message message formatted as string + * @param type type of notification, see {@link com.sap.sse.gwt.client.Notification.NotificationType} */ - private static void checkQueue(boolean onAnimationFinish) { - if (!QUEUE.isEmpty()) { - if (!notificationAnimation.isRunning() || onAnimationFinish) { - RootPanel.get().add(snackBar); - Event.sinkEvents(snackBar.getElement(), Event.ONCLICK); - Event.setEventListener(snackBar.getElement(), new EventListener() { - @Override - public void onBrowserEvent(Event event) { - GWT.log("test"); - notificationAnimation.cancel(); - } - }); - - Pair notification = QUEUE.pop(); - snackBar.getElement().getStyle().setColor(notification.getB().color); - snackBar.getElement().getStyle().setBackgroundColor(notification.getB().bgColor); - snackBar.getElement().setInnerText(notification.getB().decorator + " " + notification.getA() + " " - + notification.getB().decorator); - notificationAnimation.run(NOTIFICATION_TIME); + public static void notify(String message, NotificationType type) { + NotificationPanel notification = new NotificationPanel(message, type, notifications); + if (!QUEUE.contains(notification)) { + QUEUE.add(notification); + } + checkQueue(notification); + } + + /** + * Checks notification queue for hidden notifications and displays next notification, if the amount of + * currently shown notifications does not exceed notification limit. This method does not have to be called + * in particular by the user to display messages. Use {@link com.sap.sse.gwt.client.Notification#notify()} instead + * to show notifications. + * @param notification should be an instance of NotificationPanel which is newly added or just removed + */ + protected static void checkQueue(NotificationPanel notification) { + //clean old notification from queue + if (notification.alreadyShown()) { + QUEUE.remove(notification); + } + + long currentlyShown = QUEUE.stream().filter(value -> value.alreadyShown()).count(); + for(NotificationPanel notificationInQueue:QUEUE) { + //add currently not shown notifications, untill the MAX_NOTIFICATIONS is reached + if (!notificationInQueue.alreadyShown() && currentlyShown < MAX_NOTIFICATIONS) { + notificationInQueue.show(); + currentlyShown++; } } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/NotificationPanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/NotificationPanel.java new file mode 100644 index 00000000000..c24d96b860b --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/NotificationPanel.java @@ -0,0 +1,153 @@ +package com.sap.sse.gwt.client; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Cursor; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.storage.client.Storage; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Panel; +import com.sap.sse.gwt.client.Notification.NotificationType; + +/** + * Utility class to show non obstructive warning / info messages using a small notification at the bottom of the page. + * Clicking the notification will hide it. It will autohide after some time. To generate notifications use method + * {@link com.sap.sse.gwt.client.Notification#notify()}. + */ +public class NotificationPanel { + private static final int NOTIFICATION_TIME = 10000; + private static final double FADE_OUT_PERCENT = 0.98; + private static final double FADE_IN_PERCENT = 0.005; + + private final String message; + private final Panel panel; + private final Panel parent; + private final Animation animation; + + private boolean alreadyShown = false; + private HandlerRegistration registration; + + + public NotificationPanel(String message, NotificationType type, Panel parent) { + this.message = message; + this.parent = parent; + + panel = new FlowPanel(); + panel.addStyleName(Notification.ress.css().notification()); + panel.getElement().getStyle().setCursor(Cursor.POINTER); + + panel.getElement().getStyle().setColor(type.getColor()); + panel.getElement().getStyle().setBackgroundColor(type.getBackgroundColor()); + panel.getElement().setInnerText(type.getDecorator() + " " + message); + + // ensure notifications work if a dataentry dialog is open! + registration = Event.addNativePreviewHandler(new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + Element target = Element.as(event.getNativeEvent().getEventTarget()); + if (event.getTypeInt() == Event.ONCLICK && target == panel.getElement()) { + animation.cancel(); + } + } + }); + + animation = new Animation() { + @Override + protected void onStart() { + super.onStart(); + alreadyShown = true; + panel.getElement().getStyle().setOpacity(0); + } + + @Override + protected void onUpdate(double progress) { + if (progress < FADE_IN_PERCENT) { + double relPr = progress / FADE_IN_PERCENT; + panel.getElement().getStyle().setOpacity(relPr); + } else if (progress > FADE_OUT_PERCENT) { + double relPr = (progress - FADE_OUT_PERCENT) / (1 - FADE_OUT_PERCENT); + panel.getElement().getStyle().setOpacity(1 - relPr); + } else { + panel.getElement().getStyle().clearOpacity(); + } + } + + @Override + protected void onComplete() { + remove(); + } + + @Override + protected void onCancel() { + remove(); + } + }; + } + + /** + * Displays notification at UI. + */ + public void show() { + final Storage localStorageIfSupported = Storage.getLocalStorageIfSupported(); + int timeout = NOTIFICATION_TIME; + if (localStorageIfSupported != null) { + final String customTimeOut = localStorageIfSupported.getItem("sse.notification.customTimeOutInSeconds"); + if (customTimeOut != null && !customTimeOut.isEmpty()) { + try { + timeout = Integer.parseInt(customTimeOut) * 1000; + } catch (Exception e) { + // If the value can't be parsed, we just use the default + } + } + } + if (!animation.isRunning()) { + parent.add(panel); + animation.run(timeout); + } + } + + /** + * Returns whether notification panel got already displayed. + * @return true if panel got already displayed, otherwise false. + */ + public boolean alreadyShown() { + return alreadyShown; + } + + /** + * Removes panel from parent element at UI and force checking of queue for notifications. + */ + public void remove() { + registration.removeHandler(); + parent.remove(panel); + Notification.checkQueue(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((message == null) ? 0 : message.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NotificationPanel other = (NotificationPanel) obj; + if (message == null) { + if (other.message != null) + return false; + } else if (!message.equals(other.message)) + return false; + return true; + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/datetime/DateInput.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/datetime/DateInput.java index 6ad8bc92f24..487b866dfa2 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/datetime/DateInput.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/datetime/DateInput.java @@ -14,7 +14,7 @@ public class DateInput extends AbstractInput { * Created a new {@link DateInput} instance. */ public DateInput() { - super(DateTimeInputType.TIME.isSupported() ? NativeDateTimeInput.date() : new CustomDateBox()); + super(DateTimeInputType.DATE.isSupported() ? NativeDateTimeInput.date() : new CustomDateBox()); } private static class CustomDateBox extends DateBox { diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java index 36c11588310..7535306d4e4 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/FileStoragePanel.java @@ -64,7 +64,6 @@ public class FileStoragePanel extends FlowPanel { public FileStoragePanel(FileStorageManagementGwtServiceAsync sailingService, ErrorReporter errorReporter) { this.sailingService = sailingService; this.errorReporter = errorReporter; - Button refreshButton = new Button(stringMessages.refresh()); refreshButton.addClickHandler(new ClickHandler() { @Override @@ -73,12 +72,10 @@ public class FileStoragePanel extends FlowPanel { } }); add(refreshButton); - CaptionPanel activeServicePanel = new CaptionPanel(stringMessages.active()); activeServiceLabel = new Label(); activeServicePanel.add(activeServiceLabel); add(activeServicePanel); - CaptionPanel editServicePanel = new CaptionPanel(stringMessages.edit()); VerticalPanel editServicePanelContent = new VerticalPanel(); editServicePanel.add(editServicePanelContent); @@ -90,16 +87,13 @@ public class FileStoragePanel extends FlowPanel { } }); editServicePanelContent.add(servicesListBox); - serviceDescriptionLabel = new Label(); editServicePanelContent.add(serviceDescriptionLabel); - propertiesTable = new BaseCelltable<>(); propertiesTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED); //allow for default tabbing behaviour propertiesListDataProvider = new ListDataProvider<>(new ArrayList()); properties = propertiesListDataProvider.getList(); propertiesListDataProvider.addDataDisplay(propertiesTable); - TextColumn nameColumn = new TextColumn() { @Override public String getValue(FileStorageServicePropertyDTO p) { @@ -107,7 +101,6 @@ public class FileStoragePanel extends FlowPanel { } }; propertiesTable.addColumn(nameColumn, stringMessages.name()); - Column inputColumn = new Column( new TabbingTextInputCell()) { @Override @@ -122,7 +115,6 @@ public class FileStoragePanel extends FlowPanel { } }); propertiesTable.addColumn(inputColumn, stringMessages.value()); - TextColumn descriptionColumn = new TextColumn() { @Override public String getValue(FileStorageServicePropertyDTO p) { @@ -130,7 +122,6 @@ public class FileStoragePanel extends FlowPanel { } }; propertiesTable.addColumn(descriptionColumn, stringMessages.description()); - TextColumn errorColumn = new TextColumn() { @Override public String getValue(FileStorageServicePropertyDTO p) { @@ -140,15 +131,11 @@ public class FileStoragePanel extends FlowPanel { }; errorColumn.setCellStyleNames("errorLabel"); propertiesTable.addColumn(errorColumn, stringMessages.error()); - editServicePanelContent.add(propertiesTable); - propertiesErrorLabel = new Label(); propertiesErrorLabel.setStyleName("errorLabel"); editServicePanelContent.add(propertiesErrorLabel); - HorizontalPanel buttonsPanel = new HorizontalPanel(); - Button saveAndTestPropertiesButton = new Button(stringMessages.save()); saveAndTestPropertiesButton.addClickHandler(new ClickHandler() { @Override @@ -157,7 +144,6 @@ public class FileStoragePanel extends FlowPanel { } }); buttonsPanel.add(saveAndTestPropertiesButton); - Button setAsActiveServiceButton = new Button(stringMessages.setAsActive()); setAsActiveServiceButton.addClickHandler(new ClickHandler() { @Override @@ -166,11 +152,8 @@ public class FileStoragePanel extends FlowPanel { } }); buttonsPanel.add(setAsActiveServiceButton); - editServicePanelContent.add(buttonsPanel); - add(editServicePanel); - refresh(); } @@ -273,12 +256,10 @@ public class FileStoragePanel extends FlowPanel { perPropertyErrors.clear(); serviceDescriptionLabel.setText(""); FileStorageServiceDTO selected = availableServices.get(getSelectedServiceName()); - if (selected == null) { - return; + if (selected != null) { + serviceDescriptionLabel.setText(selected.description); + properties.addAll(Arrays.asList(selected.properties)); } - - serviceDescriptionLabel.setText(selected.description); - properties.addAll(Arrays.asList(selected.properties)); } private String getLocaleInfo() { @@ -287,14 +268,12 @@ public class FileStoragePanel extends FlowPanel { private void refresh() { String oldSelectedService = getSelectedServiceName(); - servicesListBox.clear(); servicesListBox.addItem(""); availableServices.clear(); propertiesErrorLabel.setText(""); perPropertyErrors.clear(); onServiceSelectionChanged(); - sailingService.getActiveFileStorageServiceName(new AsyncCallback() { @Override public void onSuccess(String result) { @@ -306,7 +285,6 @@ public class FileStoragePanel extends FlowPanel { errorReporter.reportError(stringMessages.couldNotLoadActiveService() + ": " + caught.getMessage()); } }); - sailingService.getAvailableFileStorageServices(getLocaleInfo(), new AsyncCallback() { @Override public void onSuccess(FileStorageServiceDTO[] result) { @@ -321,14 +299,12 @@ public class FileStoragePanel extends FlowPanel { errorReporter.reportError(stringMessages.couldNotLoadAvailableServices() + ": " + caught.getMessage()); } }); - for (int i = 0; i < servicesListBox.getItemCount(); i++) { if (servicesListBox.getItemText(i).equals(oldSelectedService)) { servicesListBox.setSelectedIndex(i); } } onServiceSelectionChanged(); - testProperties(null); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/AbstractActivityProxy.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/AbstractActivityProxy.java index ce2f124cf5d..f014df213e6 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/AbstractActivityProxy.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/AbstractActivityProxy.java @@ -5,8 +5,9 @@ import com.google.gwt.activity.shared.Activity; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; import com.google.gwt.event.shared.EventBus; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * Manages GWT code splitting by moving an activity and everything that activity exclusively uses to a separate code @@ -53,7 +54,7 @@ public abstract class AbstractActivityProxy extends AbstractActivity { @Override public void onFailure(Throwable reason) { - Window.alert(reason.getMessage()); + Notification.notify(reason.getMessage(), NotificationType.ERROR); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.css b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.css new file mode 100644 index 00000000000..846404c22d5 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.css @@ -0,0 +1,14 @@ +.notification { + min-width: 400px; + border-radius: 2px; + padding: 16px; + margin-bottom: 10px; +} + +.notification_bar { + position: fixed; + bottom: 0; + left: 50%; + transform: translateX(-50%); + z-index: 100; +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss deleted file mode 100644 index 0e1f3b5a5e0..00000000000 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss +++ /dev/null @@ -1,10 +0,0 @@ -.snackbar { - visibility: hidden; - min-width: 250px; - margin-left: -125px; - text-align: center; - border-radius: 2px; - padding: 16px; - position: fixed; - left: 50%; -} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/SettingsDialog.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/SettingsDialog.java index b4329199df2..823bea3aa7a 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/SettingsDialog.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/SettingsDialog.java @@ -3,9 +3,10 @@ package com.sap.sse.gwt.client.shared.components; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.sap.sse.common.settings.Settings; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.StringMessages; import com.sap.sse.gwt.client.shared.settings.DummyOnSettingsStoredCallback; import com.sap.sse.gwt.client.shared.settings.OnSettingsLoadedCallback; @@ -87,13 +88,13 @@ public class SettingsDialog extends AbstractSetti @Override public void onSuccess() { setButtonSavingState(false, stringMessages); - Window.alert(stringMessages.settingsSavedMessage()); + Notification.notify(stringMessages.settingsSavedMessage(), NotificationType.SUCCESS); } @Override public void onError(Throwable caught) { setButtonSavingState(false, stringMessages); - Window.alert(stringMessages.settingsSaveErrorMessage()); + Notification.notify(stringMessages.settingsSaveErrorMessage(), NotificationType.ERROR); } }); @@ -111,15 +112,14 @@ public class SettingsDialog extends AbstractSetti public void onError(Throwable caught, SettingsType fallbackDefaultSettings) { setButtonSavingState(false, stringMessages); onChange(fallbackDefaultSettings); - Window.alert(stringMessages.settingsRemovedError()); + Notification.notify(stringMessages.settingsRemovedError(), NotificationType.ERROR); } @Override public void onSuccess(SettingsType settings) { setButtonSavingState(false, stringMessages); setDialogComponent(component.getSettingsDialogComponent(settings)); - - Window.alert(stringMessages.settingsRemoved()); + Notification.notify(stringMessages.settingsRemoved(), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java index 23864691929..519f23db652 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java @@ -18,8 +18,9 @@ public abstract class AbstractListSuggestOracle extends AbstractSuggestOracle @Override protected final void getSuggestions(Request request, Callback callback, Iterable queryTokens) { - Iterable filteredList = suggestionMatchingFilter.applyFilter(getKeywordStrings(queryTokens), suggestionObjectList); - this.setSuggestions(request, callback, filteredList, queryTokens); + Iterable keywords = getKeywordStrings(queryTokens); + Iterable filteredList = suggestionMatchingFilter.applyFilter(keywords, suggestionObjectList); + this.setSuggestions(request, callback, filteredList, keywords); } protected Iterable getKeywordStrings(Iterable queryTokens) { diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractSuggestOracle.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractSuggestOracle.java index 1b9b9499a8f..5b737ee34f8 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractSuggestOracle.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractSuggestOracle.java @@ -90,10 +90,12 @@ public abstract class AbstractSuggestOracle extends SuggestOracle { int index = displayString.length(); String matchToken = null; for (String token : queryTokens) { - int matchIndex = normalizedString.indexOf(token, cursor); - if (matchIndex >= 0 && matchIndex < index) { - index = matchIndex; - matchToken = token; + if (token != null && !token.isEmpty()) { + int matchIndex = normalizedString.indexOf(token, cursor); + if (matchIndex >= 0 && matchIndex < index) { + index = matchIndex; + matchToken = token; + } } } if (matchToken != null) { diff --git a/java/com.sap.sse.security.ui/.project b/java/com.sap.sse.security.ui/.project index 39b250b61a3..b2c40005b31 100644 --- a/java/com.sap.sse.security.ui/.project +++ b/java/com.sap.sse.security.ui/.project @@ -20,16 +20,6 @@ - - com.google.gdt.eclipse.core.webAppProjectValidator - - - - - com.google.gwt.eclipse.core.gwtProjectValidator - - - com.gwtplugins.gdt.eclipse.core.webAppProjectValidator @@ -44,7 +34,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature - com.google.gwt.eclipse.core.gwtNature com.gwtplugins.gwt.eclipse.core.gwtNature diff --git a/java/com.sap.sse.security.ui/GWT Security SDM.launch b/java/com.sap.sse.security.ui/GWT Security SDM.launch index e3402b6144b..4f18a99815c 100644 --- a/java/com.sap.sse.security.ui/GWT Security SDM.launch +++ b/java/com.sap.sse.security.ui/GWT Security SDM.launch @@ -7,7 +7,7 @@ - + @@ -38,7 +38,6 @@ - @@ -105,7 +104,7 @@ - + diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch deleted file mode 100644 index db91c43b97e..00000000000 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sse.security.ui/Security UI.launch b/java/com.sap.sse.security.ui/Security UI.launch deleted file mode 100644 index 61ec9497ff6..00000000000 --- a/java/com.sap.sse.security.ui/Security UI.launch +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java index 4c34ac2fbf0..2e10b85dac9 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java @@ -10,6 +10,8 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.web.bindery.event.shared.EventBus; import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.authentication.app.AuthenticationContext; import com.sap.sse.security.ui.authentication.app.AuthenticationContextImpl; @@ -281,7 +283,7 @@ public class AuthenticationManagerImpl implements AuthenticationManager { private class ErrorMessageViewImpl implements ErrorMessageView { @Override public void setErrorMessage(String errorMessage) { - Window.alert(errorMessage); + Notification.notify(errorMessage, NotificationType.ERROR); } } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java index 1588d4d9563..d5e55cb11c8 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java @@ -8,13 +8,14 @@ import java.util.logging.Logger; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Random; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.settings.generic.AbstractGenericSerializableSettings; import com.sap.sse.common.settings.generic.GenericSerializableSettings; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.Storage; import com.sap.sse.gwt.client.StorageEvent; import com.sap.sse.gwt.client.StorageEvent.Handler; @@ -129,7 +130,7 @@ public class UserService { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } })); } @@ -180,7 +181,7 @@ public class UserService { userManagementService.logout(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.couldNotSignOut(caught.getMessage())); + Notification.notify(stringMessages.couldNotSignOut(caught.getMessage()), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java index bcb01c0739f..c775f217323 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java @@ -2,8 +2,9 @@ package com.sap.sse.security.ui.client.component; import java.util.HashMap; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.EntryPointLinkFactory; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.component.UserManagementPanel.UserCreatedEventHandler; @@ -38,7 +39,7 @@ public class CreateUserDialog extends AbstractUserDialog { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.couldNotCreateUser(caught.getMessage())); + Notification.notify(stringMessages.couldNotCreateUser(caught.getMessage()), NotificationType.ERROR); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditEmailDialogWithDefaultCallback.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditEmailDialogWithDefaultCallback.java index d327751a18b..8a47b245fe3 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditEmailDialogWithDefaultCallback.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditEmailDialogWithDefaultCallback.java @@ -2,8 +2,9 @@ package com.sap.sse.security.ui.client.component; import java.util.HashMap; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.security.ui.client.EntryPointLinkFactory; import com.sap.sse.security.ui.client.UserManagementServiceAsync; @@ -11,9 +12,9 @@ import com.sap.sse.security.ui.client.i18n.StringMessages; import com.sap.sse.security.ui.shared.UserDTO; /** - * Forwards the e-mail update request to a {@link UserManagementServiceAsync} and uses default - * Window.alert(...) calls to show success or failure of the server-side update after - * calling back to an optional additional callback that can be provided to the constructor. + * Forwards the e-mail update request to a {@link UserManagementServiceAsync} and uses notifications to show success or + * failure of the server-side update after calling back to an optional additional callback that can be provided to the + * constructor. * * @author Axel Uhl (D043530) * @@ -39,7 +40,7 @@ public class EditEmailDialogWithDefaultCallback extends EditEmailDialog { if (callback != null) { callback.onFailure(caught); } - Window.alert(stringMessages.errorUpdatingEmail(caught.getMessage())); + Notification.notify(stringMessages.errorUpdatingEmail(caught.getMessage()), NotificationType.ERROR); } @Override @@ -47,7 +48,8 @@ public class EditEmailDialogWithDefaultCallback extends EditEmailDialog { if (callback != null) { callback.onSuccess(userData); } - Window.alert(stringMessages.successfullyUpdatedEmail(userData.getUsername(), userData.getEmail())); + Notification.notify(stringMessages.successfullyUpdatedEmail( + userData.getUsername(), userData.getEmail()), NotificationType.SUCCESS); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java index 1050c38e0a2..d796da15cc9 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java @@ -2,11 +2,12 @@ package com.sap.sse.security.ui.client.component; import java.util.HashMap; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.client.EntryPointLinkFactory; @@ -36,12 +37,12 @@ public class ForgotPasswordDialog extends AbstractUserDialog { public void onFailure(Throwable caught) { if (caught instanceof UserManagementException) { if (UserManagementException.CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL.equals(caught.getMessage())) { - Window.alert(StringMessages.INSTANCE.cannotResetPasswordWithoutValidatedEmail(userData.getUsername())); + Notification.notify(StringMessages.INSTANCE.cannotResetPasswordWithoutValidatedEmail(userData.getUsername()), NotificationType.ERROR); } else { - Window.alert(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage())); + Notification.notify(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage()), NotificationType.ERROR); } } else { - Window.alert(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage())); + Notification.notify(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage()), NotificationType.ERROR); } } @@ -57,7 +58,7 @@ public class ForgotPasswordDialog extends AbstractUserDialog { } nameOrEmail.append(userData.getEmail()); } - Window.alert(StringMessages.INSTANCE.passwordResetLinkSent(nameOrEmail.toString())); + Notification.notify(StringMessages.INSTANCE.passwordResetLinkSent(nameOrEmail.toString()), NotificationType.INFO); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java index ca05964b91d..a14c683d578 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java @@ -26,6 +26,8 @@ import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.TabLayoutPanel; import com.google.gwt.user.client.ui.TextBox; import com.sap.sse.gwt.client.AbstractEntryPoint; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.IconResources; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.i18n.StringMessages; @@ -127,23 +129,23 @@ public class SettingsPanel extends LayoutPanel { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } @Override public void onSuccess(Void result) { - Window.alert("Added url!"); + Notification.notify("Added url!", NotificationType.SUCCESS); } }); userManagementService.addSetting("URLS_AUTH_" + key.getText(), String.class.getName(), filter.getText(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } @Override public void onSuccess(Void result) { - Window.alert("Added url filter!"); + Notification.notify("Added url filter!", NotificationType.SUCCESS); } }); } @@ -182,7 +184,7 @@ public class SettingsPanel extends LayoutPanel { boolean deleteS = Window.confirm("Are you sure you want to delete this setting?"); if (deleteS){ - Window.alert("Not implemented yet"); + Notification.notify("Not implemented yet", NotificationType.ERROR); } } }); @@ -292,7 +294,7 @@ public class SettingsPanel extends LayoutPanel { boolean deleteS = Window.confirm("Are you sure you want to delete this setting?"); if (deleteS){ - Window.alert("Not implemented yet"); + Notification.notify("Not implemented yet", NotificationType.ERROR); } } }); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index b273db71b48..b571f6dc640 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -12,7 +12,6 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DecoratorPanel; @@ -24,6 +23,8 @@ import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; @@ -94,13 +95,13 @@ public class UserDetailsView extends FlowPanel { new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorUpdatingRoles(selectedUser.getName(), caught.getMessage())); + Notification.notify(stringMessages.errorUpdatingRoles(selectedUser.getName(), caught.getMessage()), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) { if (!result.isSuccessful()) { - Window.alert(stringMessages.errorUpdatingRoles(selectedUser.getName(), result.getMessage())); + Notification.notify(stringMessages.errorUpdatingRoles(selectedUser.getName(), result.getMessage()), NotificationType.ERROR); } else { userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { @@ -124,13 +125,13 @@ public class UserDetailsView extends FlowPanel { new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorUpdatingPermissions(selectedUser.getName(), caught.getMessage())); + Notification.notify(stringMessages.errorUpdatingPermissions(selectedUser.getName(), caught.getMessage()), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) { if (!result.isSuccessful()) { - Window.alert(stringMessages.errorUpdatingPermissions(selectedUser.getName(), result.getMessage())); + Notification.notify(stringMessages.errorUpdatingPermissions(selectedUser.getName(), result.getMessage()), NotificationType.ERROR); } else { userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { @@ -226,20 +227,20 @@ public class UserDetailsView extends FlowPanel { if (caught instanceof UserManagementException) { String message = ((UserManagementException) caught).getMessage(); if (UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS.equals(message)) { - Window.alert(stringMessages.passwordDoesNotMeetRequirements()); + Notification.notify(stringMessages.passwordDoesNotMeetRequirements(), NotificationType.ERROR); } else if (UserManagementException.INVALID_CREDENTIALS.equals(message)) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } else { - Window.alert(stringMessages.errorChangingPassword(caught.getMessage())); + Notification.notify(stringMessages.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } else { - Window.alert(stringMessages.errorChangingPassword(caught.getMessage())); + Notification.notify(stringMessages.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } @Override public void onSuccess(Void result) { - Window.alert(stringMessages.passwordSuccessfullyChanged()); + Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.SUCCESS); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java index 1b610f25013..818ec82bd4f 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java @@ -8,13 +8,14 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.view.client.AbstractDataProvider; import com.google.gwt.view.client.HasData; import com.google.gwt.view.client.Range; import com.google.gwt.view.client.SelectionModel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.shared.UserDTO; @@ -34,7 +35,7 @@ public class UserListDataProvider extends AbstractDataProvider { userManagementService.getFilteredSortedUserList(filterBox.getText(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java index 8641b84e823..7976a54d364 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java @@ -24,6 +24,8 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.shared.Permission; import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.Role; @@ -80,12 +82,12 @@ public class UserManagementPanel extends DockPanel { for (UserDeletedEventHandler userDeletedHandler : userDeletedHandlers) { userDeletedHandler.onUserDeleted(userToDelete); } - Window.alert(result.getMessage()); + Notification.notify(result.getMessage(), NotificationType.SUCCESS); } @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorDeletingUser()); + Notification.notify(stringMessages.errorDeletingUser(), NotificationType.ERROR); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/editprofile/EditProfileEntryPoint.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/editprofile/EditProfileEntryPoint.java index e0fe774b1d1..25a19fb69e3 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/editprofile/EditProfileEntryPoint.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/editprofile/EditProfileEntryPoint.java @@ -23,6 +23,8 @@ import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.SubmitButton; import com.google.gwt.user.client.ui.TextBox; import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.client.RemoteServiceMappingConstants; import com.sap.sse.security.ui.client.UserManagementService; @@ -163,20 +165,20 @@ public class EditProfileEntryPoint implements EntryPoint { if (caught instanceof UserManagementException) { String message = ((UserManagementException) caught).getMessage(); if (UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS.equals(message)) { - Window.alert(stringMessages.passwordDoesNotMeetRequirements()); + Notification.notify(stringMessages.passwordDoesNotMeetRequirements(), NotificationType.ERROR); } else if (UserManagementException.INVALID_CREDENTIALS.equals(message)) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } else { - Window.alert(stringMessages.errorChangingPassword(caught.getMessage())); + Notification.notify(stringMessages.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } else { - Window.alert(stringMessages.errorChangingPassword(caught.getMessage())); + Notification.notify(stringMessages.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } @Override public void onSuccess(Void result) { - Window.alert(stringMessages.passwordSuccessfullyChanged()); + Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java index 6759b402553..67321e5f36d 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java @@ -17,6 +17,8 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.PasswordTextBoxWithWatermark; import com.sap.sse.gwt.client.controls.TextBoxWithWatermark; import com.sap.sse.gwt.client.dialog.DialogUtils; @@ -83,13 +85,13 @@ public class LoginView extends Composite { if (!result.getRedirectURL().equals("")) { Window.Location.replace(result.getRedirectURL()); } else { - Window.alert(stringMessages.loggedIn(result.getUserDTO().getName())); + Notification.notify(stringMessages.loggedIn(result.getUserDTO().getName()), NotificationType.INFO); } } else { if (SuccessInfo.FAILED_TO_LOGIN.equals(result.getMessage())) { - Window.alert(stringMessages.failedToSignIn()); + Notification.notify(stringMessages.failedToSignIn(), NotificationType.ERROR); } else { - Window.alert(result.getMessage()); + Notification.notify(result.getMessage(), NotificationType.ERROR); } } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java index d440fd43f36..78bcfc1e3af 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java @@ -7,12 +7,13 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.security.ui.client.EntryPointLinkFactory; @@ -56,11 +57,11 @@ public class LoginPanel extends HorizontalPanel implements UserStatusEventHandle userService.login(userData.getUsername(), userData.getPassword(), new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) { if (!result.isSuccessful()) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } } })); @@ -83,14 +84,14 @@ public class LoginPanel extends HorizontalPanel implements UserStatusEventHandle new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorCreatingUser(userData.getUsername(), caught.getMessage())); + Notification.notify(stringMessages.errorCreatingUser(userData.getUsername(), caught.getMessage()), NotificationType.ERROR); } @Override public void onSuccess(UserDTO result) { userService.login(userData.getUsername(), userData.getPassword(), new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { // pretty strange; we just successfully created the user with these credentials... - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) {} })); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java index df3bc7d0d70..2783bc2385c 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java @@ -4,7 +4,6 @@ import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.Button; @@ -13,6 +12,8 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.RemoteServiceMappingConstants; import com.sap.sse.security.ui.client.UserChangeEventHandler; import com.sap.sse.security.ui.client.UserManagementService; @@ -50,7 +51,7 @@ public class OAuthLoginEntryPoint implements EntryPoint, UserChangeEventHandler userService.verifySocialUser(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java index e3f658c6911..d37212ed934 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java @@ -5,6 +5,8 @@ import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.oauth.client.CredentialDTO; import com.sap.sse.security.ui.shared.SuccessInfo; @@ -296,7 +298,7 @@ public class ClientUtils { String error = Location.getParameter("error"); if (error != null) { String errorMessage = Location.getParameter("error_description"); - Window.alert("Error: " + error + ":" + errorMessage); + Notification.notify(error + ":" + errorMessage, NotificationType.ERROR); reload(); return false; } @@ -383,7 +385,7 @@ public class ClientUtils { } public static void showSessionExpires() { - Window.alert("Your session seems to have expired!\n" + "You will be logged out"); + Notification.notify("Your session seems to have expired! You will be logged out.", NotificationType.ERROR); ClientUtils.reload(); } @@ -394,7 +396,7 @@ public class ClientUtils { message += "Please Logout/reload the application"; // String st = LogUtil.stackTraceToString(caught); // message += "Stack Trace:\n" + st; - Window.alert(message); + Notification.notify(message, NotificationType.ERROR); } public static void logout(UserManagementServiceAsync userManagementService) { @@ -403,7 +405,7 @@ public class ClientUtils { @Override public void onFailure(Throwable caught) { - Window.alert("Could not log out! " + caught); + Notification.notify("Could not log out! " + caught, NotificationType.ERROR); ClientUtils.reload(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java index fa49d95226a..c3ff8201619 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java @@ -10,12 +10,13 @@ import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.PasswordTextBoxWithWatermark; import com.sap.sse.gwt.client.controls.TextBoxWithWatermark; import com.sap.sse.gwt.client.dialog.DialogUtils; @@ -85,20 +86,20 @@ public class RegisterView extends Composite { if (caught instanceof UserManagementException) { String message = ((UserManagementException) caught).getMessage(); if (UserManagementException.USER_ALREADY_EXISTS.equals(message)) { - Window.alert(stringMessages.userAlreadyExists(usernameTextBox.getText())); + Notification.notify(stringMessages.userAlreadyExists(usernameTextBox.getText()), NotificationType.ERROR); } } else { - Window.alert(stringMessages.errorCreatingUser(usernameTextBox.getText(), caught.getMessage())); + Notification.notify(stringMessages.errorCreatingUser(usernameTextBox.getText(), caught.getMessage()), NotificationType.ERROR); } } @Override public void onSuccess(UserDTO result) { if (result != null) { - Window.alert(stringMessages.signedUpSuccessfully(result.getName())); + Notification.notify(stringMessages.signedUpSuccessfully(result.getName()), NotificationType.SUCCESS); closeWindow(); } else { - Window.alert(stringMessages.unknownErrorCreatingUser(usernameTextBox.getText())); + Notification.notify(stringMessages.unknownErrorCreatingUser(usernameTextBox.getText()), NotificationType.ERROR); } } }); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java index 0ae2c0a4356..fc43e4b23a3 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java @@ -8,6 +8,8 @@ import com.sap.sse.security.ui.authentication.app.AuthenticationContext; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.component.NewAccountValidator; import com.sap.sse.security.ui.client.i18n.StringMessages; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * Default Presenter implementation for {@link UserDetailsView}. @@ -47,13 +49,14 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { new AsyncCallback() { @Override public void onSuccess(Void result) { - Window.alert(i18n_sec.successfullyUpdatedUserProperties( - authenticationManager.getAuthenticationContext().getCurrentUser().getName())); + Notification.notify(i18n_sec.successfullyUpdatedUserProperties( + authenticationManager.getAuthenticationContext().getCurrentUser().getName()), + NotificationType.INFO); } @Override public void onFailure(Throwable caught) { - Window.alert(i18n_sec.errorUpdatingUserProperties(caught.getMessage())); + Notification.notify(i18n_sec.errorUpdatingUserProperties(caught.getMessage()), NotificationType.ERROR); } }); } @@ -67,12 +70,12 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { new AsyncCallback() { @Override public void onSuccess(Void result) { - Window.alert(i18n_sec.successfullyUpdatedEmail(username, email)); + Notification.notify(i18n_sec.successfullyUpdatedEmail(username, email), NotificationType.INFO); } @Override public void onFailure(Throwable caught) { - Window.alert(i18n_sec.errorUpdatingEmail(caught.getMessage())); + Notification.notify(i18n_sec.errorUpdatingEmail(caught.getMessage()), NotificationType.ERROR); } }); } @@ -82,7 +85,7 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { final String username = authenticationManager.getAuthenticationContext().getCurrentUser().getName(); String errorMessage = validator.validateUsernameAndPassword(username, newPassword, newPasswordConfirmation); if (errorMessage != null && !errorMessage.isEmpty()) { - Window.alert(errorMessage); + Notification.notify(errorMessage, NotificationType.ERROR); return; } userManagementService.updateSimpleUserPassword(username, oldPassword, null, newPassword, @@ -92,20 +95,20 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { if (caught instanceof UserManagementException) { String message = ((UserManagementException) caught).getMessage(); if (UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS.equals(message)) { - Window.alert(i18n_sec.passwordDoesNotMeetRequirements()); + Notification.notify(i18n_sec.passwordDoesNotMeetRequirements(), NotificationType.ERROR); } else if (UserManagementException.INVALID_CREDENTIALS.equals(message)) { - Window.alert(i18n_sec.invalidCredentials()); + Notification.notify(i18n_sec.invalidCredentials(), NotificationType.ERROR); } else { - Window.alert(i18n_sec.errorChangingPassword(caught.getMessage())); + Notification.notify(i18n_sec.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } else { - Window.alert(i18n_sec.errorChangingPassword(caught.getMessage())); + Notification.notify(i18n_sec.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } @Override public void onSuccess(Void result) { - Window.alert(i18n_sec.passwordSuccessfullyChanged()); + Notification.notify(i18n_sec.passwordSuccessfullyChanged(), NotificationType.SUCCESS); view.clearPasswordFields(); } }); diff --git a/java/com.sap.sse.security/META-INF/MANIFEST.MF b/java/com.sap.sse.security/META-INF/MANIFEST.MF index ef37424199c..994486340bc 100644 --- a/java/com.sap.sse.security/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security/META-INF/MANIFEST.MF @@ -6,8 +6,7 @@ Bundle-Version: 1.0.0.qualifier Bundle-Activator: com.sap.sse.security.impl.Activator Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: com.sap.sse.util, - javax.mail;version="1.4.0", +Import-Package: javax.mail;version="1.4.0", javax.mail.internet;version="1.4.0", javax.ws.rs;version="1.1.1", javax.ws.rs.core;version="1.1.1", @@ -26,6 +25,7 @@ Require-Bundle: javax.servlet;bundle-version="3.1.0", org.apache.shiro.web;bundle-version="1.2.2", org.apache.servicemix.bundles.ehcache;bundle-version="2.6.9", org.apache.servicemix.bundles.scribe;bundle-version="1.3.3", + com.sap.sse, com.sap.sse.common, com.sap.sse.replication, com.sap.sse.operationaltransformation, diff --git a/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF b/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF index 7a9750199c9..35321b45a2f 100644 --- a/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.shared.android/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Export-Package: com.sap.sse.concurrent, com.sap.sse.shared.media, com.sap.sse.shared.media.impl, com.sap.sse.shared.settings, + com.sap.sse.util, com.sap.sse.util.impl Import-Package: com.sap.sse.common.settings, com.sap.sse.common.settings.generic, diff --git a/java/com.sap.sse/src/com/sap/sse/util/WeakIdentityHashMap.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakIdentityHashMap.java similarity index 100% rename from java/com.sap.sse/src/com/sap/sse/util/WeakIdentityHashMap.java rename to java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakIdentityHashMap.java diff --git a/java/com.sap.sse/src/com/sap/sse/util/WeakReferenceWithKey.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakReferenceWithKey.java similarity index 100% rename from java/com.sap.sse/src/com/sap/sse/util/WeakReferenceWithKey.java rename to java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakReferenceWithKey.java diff --git a/java/com.sap.sse/src/com/sap/sse/util/WeakValueCache.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakValueCache.java similarity index 100% rename from java/com.sap.sse/src/com/sap/sse/util/WeakValueCache.java rename to java/com.sap.sse.shared.android/src/com/sap/sse/util/WeakValueCache.java diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index b1359749d89..24d2b248a48 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.10.1 +Bundle-Version: 3.11.0 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar @@ -88,7 +88,6 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.subscription.lib.impl.io.controllers, com.tractrac.subscription.lib.impl.io.receivers, com.tractrac.subscription.lib.impl.race, - com.tractrac.subscription.lib.impl.route, com.tractrac.subscription.lib.impl.ws, com.tractrac.util.lib.api, com.tractrac.util.lib.api.autolog, diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 90b283e5ebe..1bf14157721 100755 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,11 +16,39 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.11.0 *************** +******************************************** +This is a final version. It changes the implementation of the IRaceSubscriber interface, +breaking the backward compatibility: + + - The methods IRaceSubscriber.subscribeRoutes() and IRaceSubscriber.unsubscribeRoutes() + have been removed. + - The IRoutesListener has been removed. + - The IControlRouteChangeListener has been extended adding a new method to get updates + of a IPathRoute. If you are managing "sailing events" this method will never be invoked. + - When any of the attributes of the route is updated, will be notified using the + IRaceSubscriber.subscribeRouteChanges(IControlRouteChangeListener) method + +Release date: 30/07/2018 +Build number: 416b827959b696583cce509ac39b7fd64aaefe9b + + 1) Features + + - Merging the subscriptions IRaceSubscriber.subscribeRouteChanges and IRaceSubscriber.subscribeRoutes + in the same method (Requested by Axel Uhl, 23/07/2018) + + 2) Bugs + + - When the client is offline, if it tries to load a race it gets a RaceLoadingException. But then, + when the client is online again, it can't reuse the same race because it is in an internal wrong + status (Reported by Thomas Scott, 19/06/2018) + ******************************************** ************* TracAPI 3.10.1 *************** ******************************************** - This is a final version.It fixes bugs in the implementation and it adds a some features. - It keeps the backward compatibility. +This is a final version.It fixes bugs in the implementation and it adds a some features. +It keeps the backward compatibility: Release date: 28/06/2018 Build number: 35a26e503339fff26fe984617e5190a18438e10b @@ -35,7 +63,7 @@ It contains also some files: when the client is online again, it can't reuse the same race because it is in an internal wrong status (Reported by Thomas Scott, 19/06/2018) - The live delay is not propagated (sometimes). This feature has been reimplemented and now the - value is attached and send with the race object using the same approach used to transmit the race + value is attached and sent with the race object using the same approach used to transmit the race start time or the tracking start time (Reported by Axel Uhl, 19/06/2018) ******************************************** diff --git a/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html b/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html index 7034a5bfbbb..615b0dd464a 100755 --- a/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html @@ -2,9 +2,9 @@ - + All Classes - + diff --git a/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html b/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html index b101d156627..173dd4010f9 100755 --- a/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html +++ b/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html @@ -2,9 +2,9 @@ - + All Classes - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html index 718ed0cbd88..aa42f82f2e0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html @@ -2,9 +2,9 @@ - + IIdentifiable - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html index 9250e6aefa3..6ecfcaaead0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html @@ -2,9 +2,9 @@ - + INamed - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html index f64f700e96d..72214a24095 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html @@ -2,9 +2,9 @@ - + ModelLocator - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html index 8c4c9c2ca93..5e5ef5e9f87 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html @@ -2,9 +2,9 @@ - + AbstractAttachable - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html index 649fd39b3c6..12f74b67f9a 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html @@ -2,9 +2,9 @@ - + IAttachable - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html index a3bd5834d59..4fa41c4d75f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html @@ -2,9 +2,9 @@ - + IAttachmentKey - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html index 29dd171b1c1..f37e4809e01 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html @@ -2,9 +2,9 @@ - + IAttachmentManager - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html index dde7e1c6e6f..3134cb8ea02 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.attachment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html index f20a4b5f56f..0e3a145fdf3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.attachment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html index 93dad8cd7ad..2dd0a007cf1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.attachment Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html index 520dfd014c8..470b5fb25a6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html @@ -2,9 +2,9 @@ - + IControlPassing - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html index fedc0c5e273..9f4600b5465 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html @@ -2,9 +2,9 @@ - + IControlPassings - + @@ -128,7 +128,7 @@ var activeTableTab = "activeTableTab"; java.util.List<IControlPassing> getPassings()
- Returns a list of control passings.
+ Returns a list of control passings sorted by time. @@ -154,7 +154,7 @@ var activeTableTab = "activeTableTab";

getPassings

java.util.List<IControlPassing> getPassings()

- Returns a list of control passings. + Returns a list of control passings sorted by time.

This method is thread-safety: it returns a copy of the list. diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html index 26d8f750b06..74476c1e108 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html @@ -2,9 +2,9 @@ - + IMessageData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html index 9cc4644e6fe..f3522f667ec 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html @@ -2,9 +2,9 @@ - + IPosition - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html index 6fe0cefe7e5..bcd023c844f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html @@ -2,9 +2,9 @@ - + IPositionFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html index 12af620c739..6abc9d85074 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html @@ -2,9 +2,9 @@ - + IPositionOffset - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html index 4b714756efc..7ca4751a894 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html @@ -2,9 +2,9 @@ - + IPositionSnapped - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html index 85082893cfd..1e84530ebe6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html @@ -2,9 +2,9 @@ - + IStartStopData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html index 4702febb7c3..3b32edf0275 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html @@ -2,9 +2,9 @@ - + ITimeData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html index 5ded1043747..16d25e8bb20 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.data - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html index 8f4bc2f0ba0..ef947a352d0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.data - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html index 15ca104c64e..7170bda2d78 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.data Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html index be860aa41f2..50f8676843f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html @@ -2,9 +2,9 @@ - + CreateModelException - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html index e14e3541ddd..902f58d3df9 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html @@ -2,9 +2,9 @@ - + DataSource - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html index 273bcab5fbe..97915ff002a 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html @@ -2,9 +2,9 @@ - + EventType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html index e8119408c38..40666b3cf2d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html @@ -2,9 +2,9 @@ - + ICompetitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html index be94e76482d..cc40027d194 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html @@ -2,9 +2,9 @@ - + ICompetitorClass - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html index de9db275185..f499b47188d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html @@ -2,9 +2,9 @@ - + IEvent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html index c3183bef8c6..705b465c790 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html @@ -2,9 +2,9 @@ - + IEventFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html index f60f21b6eee..a97c5b10ffc 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html @@ -2,9 +2,9 @@ - + IRace - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html index c09b045c4ec..6e96d027829 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html @@ -2,9 +2,9 @@ - + IRaceCompetitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html index bb62df44ab5..c16a0ec6f80 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html @@ -2,9 +2,9 @@ - + IRaceSerie - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html index 685bc1d36d3..a4334424da5 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html @@ -2,9 +2,9 @@ - + ITeam - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html index bd0632bab21..301f8b3f7e1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html @@ -2,9 +2,9 @@ - + RaceCompetitorStatusType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html index 7c1203eedae..fee3d06d078 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html @@ -2,9 +2,9 @@ - + RaceLoadingException - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html index 41dae392e2c..0d4bf1d2c86 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html @@ -2,9 +2,9 @@ - + RaceStatusType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html index fc31e8e46f6..9e3547458ef 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html @@ -2,9 +2,9 @@ - + RaceVisibilityType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html index 32484cd9fb2..c94801baca1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html @@ -2,9 +2,9 @@ - + StartTimeType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html index 74e0373a891..cb75afc1af6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html index 4bfb9cbb647..15cdd2b01ad 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html index dd930f8659c..7bfd5afa38a 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.event Class Hierarchy - + @@ -150,12 +150,12 @@

diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html index 18bf57d6fa5..1700c157fb6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html @@ -2,9 +2,9 @@ - + IMetadata - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html index fdddc3f8079..57068058fee 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html @@ -2,9 +2,9 @@ - + IMetadataContainer - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html index 6b04553976e..24f408aa728 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html @@ -2,9 +2,9 @@ - + IMetadataFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html index de0c6a24d88..8d76a8b2bf0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html @@ -2,9 +2,9 @@ - + IPropertiesContainer - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html index 4e5326d2d22..9da080b27c8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.metadata - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html index 9cd30ef4b4c..75d91990102 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.metadata - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html index 94685541f68..c9a6d975975 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.metadata Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html index 3e302456cae..deead2787f8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html index 905f510b36f..ec6d94b0e6d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html index 08833871556..e5857ed0990 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html index 32a2e6901e3..8d1fc09f266 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html @@ -2,9 +2,9 @@ - + IControl - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html index 2027a203127..fa9c685861f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html @@ -2,9 +2,9 @@ - + IControlPoint - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html index d2aefd44d42..f6c1fd8b7aa 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html @@ -2,9 +2,9 @@ - + IControlRoute - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html index 0826726e79e..1b5ed549409 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html @@ -2,9 +2,9 @@ - + IPathRoute - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html index 4bb0c307805..117295b6355 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html @@ -2,9 +2,9 @@ - + IPathRouteFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html index 80991928799..33df726ced1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html @@ -2,9 +2,9 @@ - + IPathSegment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html index 6839319609b..ac414178175 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html @@ -2,9 +2,9 @@ - + IRoute - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html index 08153f15ce7..764986d52a0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html @@ -2,9 +2,9 @@ - + ISegment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html index 2b0aebf19b8..deb356597db 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html index 30ed8c76083..72603bdd661 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html index 50ff345f5ec..57f74fd9695 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.route Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html index cf1eeba60b8..c04c3e4435a 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html @@ -2,9 +2,9 @@ - + ISensorData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html index 1d775dc53b5..a4cee7a925f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.sensor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html index 9a62c28a0aa..99775e4aff0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.sensor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html index 570a18409d4..3ca30324935 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.sensor Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html index 27748e19679..4ea08c37991 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html @@ -2,9 +2,9 @@ - + ICoordinate - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html index 65ab26af492..210eac263fe 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html @@ -2,9 +2,9 @@ - + ICoordinateSequence - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html index e50673934eb..d0755ed01d7 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html @@ -2,9 +2,9 @@ - + IExtent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html index 8370f16f519..1e27dcd875d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html @@ -2,9 +2,9 @@ - + IGeoCoordinate - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html index 293a321eaa7..b8215816271 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.spatial - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html index a0e41459e7c..469e9650400 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.spatial - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html index efbe40c727e..fc76415dd19 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.spatial Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html index c9e058bb0c3..6c8c1b73aca 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html @@ -2,9 +2,9 @@ - + IEventSubscriber - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html index 77ef6adf411..804d85a0809 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html @@ -2,9 +2,9 @@ - + IRaceSubscriber - + @@ -18,7 +18,7 @@ catch(err) { } //--> -var methods = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6,"i18":6,"i19":6,"i20":6,"i21":6,"i22":6,"i23":6,"i24":6,"i25":6,"i26":6,"i27":6,"i28":6,"i29":6}; +var methods = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6,"i18":6,"i19":6,"i20":6,"i21":6,"i22":6,"i23":6,"i24":6,"i25":6,"i26":6,"i27":6,"i28":6,"i29":6,"i30":6,"i31":6,"i32":6}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -149,11 +149,18 @@ extends void +subscribeControlPassings(IControlPassingsListener listener, + java.util.UUID... classIds) +
Subscribes to control passing data for the specified race and a class(es)
+ + + +void subscribeControlPositions(IControlPointPositionListener listener)
Subscribes to control positions for the race.
- + void subscribeControlPositions(IControlPointPositionListener listener, long fromTime, @@ -162,13 +169,13 @@ extends + void subscribeControlSensorData(IControlPointSensorDataListener listener)
Subscribes to control sensor data for the race.
- + void subscribeControlSensorData(IControlPointSensorDataListener listener, long fromTime, @@ -177,13 +184,13 @@ extends + void subscribePositions(IPositionListener listener)
Subscribes to competitor positions for a given Race.
- + void subscribePositions(IPositionListener listener, long fromTime, @@ -191,13 +198,29 @@ extends Subscribes to competitor positions for a given Race.
- + +void +subscribePositions(IPositionListener listener, + long fromTime, + long toTime, + java.util.UUID... classIds) +
Subscribes to competitor positions for a given Race and class(es).
+ + + +void +subscribePositions(IPositionListener listener, + java.util.UUID... classIds) +
Subscribes to competitor positions for a given Race and class(es).
+ + + void subscribePositionsOffset(IPositionOffsetListener listener)
Subscribes to offset competitor positions for a given Race.
- + void subscribePositionsOffset(IPositionOffsetListener listener, long fromTime, @@ -206,13 +229,13 @@ extends + void subscribePositionsSnapped(IPositionSnappedListener listener)
Subscribes to snapped competitor positions for a given Race.
- + void subscribePositionsSnapped(IPositionSnappedListener listener, long fromTime, @@ -221,110 +244,113 @@ extends + +void +subscribePositionsSnapped(IPositionSnappedListener listener, + long fromTime, + long toTime, + java.util.UUID... classIds) +
Subscribes to snapped competitor positions for a given Race and class(es), including + historic data.
+ + + +void +subscribePositionsSnapped(IPositionSnappedListener listener, + java.util.UUID... classIds) +
Subscribes to snapped competitor positions for a given Race and class(es).
+ + + void subscribeRaceCompetitor(IRaceCompetitorListener listener)
Subscribes to race competitor for a given Race.
- + void subscribeRaceMessages(IRaceMessageListener listener)
Subscribe to messages for a race.
- + void subscribeRaceTimesChanges(IRaceStartStopTimesChangeListener listener)
Subscribes to changes in race start and stop times.
- + void subscribeRouteChanges(IControlRouteChangeListener listener)
Subscribes to route changes for a given Race.
- -void -subscribeRoutes(IRoutesListener listener) -
- Subscriber to route changes.
- - - + void unsubscribeCompetitorSensorData(ICompetitorSensorDataListener listener)
Unsubscribes to competitor sensor data for a given Race.
- + void unsubscribeControlPassings(IControlPassingsListener listener)
Unsubscribes to control passing data for the specified race.
- + void unsubscribeControlPositions(IControlPointPositionListener listener)
Unsubscribes to control positions.
- + void unsubscribeControlSensorData(IControlPointSensorDataListener listener)
Unsubscribes to control sensor data for a given Race.
- + void unsubscribePositions(IPositionListener listener)
Unsubscribes to competitor positions for a given Race.
- + void unsubscribePositionsOffset(IPositionOffsetListener listener)
Unsubscribes to competitor positions for a given Race.
- + void unsubscribePositionsSnapped(IPositionSnappedListener listener)
Unsubscribes to competitor positions for a given Race.
- + void unsubscribeRaceCompetitor(IRaceCompetitorListener listener)
Unsubscribes to race competitor
- + void unsubscribeRaceMessages(IRaceMessageListener listener)
Unsubscribes to race messages
- + void unsubscribeRaceTimesChanges(IRaceStartStopTimesChangeListener listener)
Unsubscribes to changes in race start and stop times.
- + void unsubscribeRouteChanges(IControlRouteChangeListener listener)
Unsubscribes to route changes for a given Race.
- -void -unsubscribeRoutes(IRoutesListener listener) -
- Unsubscribes to route changes for a given Race.
- - @@ -197,6 +200,15 @@ extends java.lang.Exception

Constructor Detail

+ + + +
    +
  • +

    SubscriberInitializationException

    +
    public SubscriberInitializationException(java.lang.String message)
    +
  • +
diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html index 9445a1db9c3..41ae621aa61 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html @@ -2,9 +2,9 @@ - + SubscriptionLocator - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html index 65e7a7b2dfd..efa78314623 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html @@ -2,9 +2,9 @@ - + ICompetitorSensorDataListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html index 6615e293771..76b8f16f571 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html @@ -2,9 +2,9 @@ - + ICompetitorsListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html index 07701cd6bc7..e5bda9e2fda 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html @@ -2,9 +2,9 @@ - + IPositionListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html index 2f2df1429b4..b0747296cf5 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html @@ -2,9 +2,9 @@ - + IPositionOffsetListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html index b022fc139df..560ce0bf40e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html @@ -2,9 +2,9 @@ - + IPositionSnappedListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html index 2480d0287a1..891b0f61300 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.competitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html index 78dfa7dc551..b176cd07d6d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.competitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html index a4786842090..35d78bb118a 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.competitor Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html index 1e1537d3bda..416b3be519d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html @@ -2,9 +2,9 @@ - + IControlPassingsListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html index 717dc036a11..812697479b6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html @@ -2,9 +2,9 @@ - + IControlPointPositionListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html index 62029137746..a01d3abcf70 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html @@ -2,9 +2,9 @@ - + IControlPointSensorDataListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html index 741e6336cab..48988b99e06 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html @@ -2,9 +2,9 @@ - + IControlRouteChangeListener - + @@ -18,7 +18,7 @@ catch(err) { } //--> -var methods = {"i0":6}; +var methods = {"i0":6,"i1":6}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -133,7 +133,14 @@ extends void gotRouteChange(IControlRoute controlRoute, long timeStamp) -
Invoked when updates to a route arrives
+
Invoked when updates to a control route arrives
+ + + +void +gotRouteChange(IPathRoute pathRoute, + long timeStamp) +
Invoked when updates to a path route arrives
@@ -154,12 +161,12 @@ extends -
    +
    • gotRouteChange

      void gotRouteChange(IControlRoute controlRoute,
                           long timeStamp)
      -
      Invoked when updates to a route arrives
      +
      Invoked when updates to a control route arrives
      Parameters:
      controlRoute - the new configuration of the route.
      @@ -167,6 +174,22 @@ extends + + +
        +
      • +

        gotRouteChange

        +
        void gotRouteChange(IPathRoute pathRoute,
        +                    long timeStamp)
        +
        Invoked when updates to a path route arrives
        +
        +
        Parameters:
        +
        pathRoute - the new configuration of the route.
        +
        timeStamp - a time stamp when the new route is valid.
        +
        +
      • +
    diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html index 5d1a9f59df6..a6ff90834ff 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html @@ -2,9 +2,9 @@ - + IControlsListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html index 4cf8232f65e..85a03a8d713 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.control - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html index 47edd25e949..3eb66888e6e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.control - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html index b9406ae962f..738b5dd6333 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.control Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html index 2c649839f31..ac196c56519 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html @@ -2,9 +2,9 @@ - + IConnectionStatusListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html index 26f2b273201..8fb83121542 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html @@ -2,9 +2,9 @@ - + IEventMessageListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html index 96a59f5cfce..3be85758049 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html @@ -2,9 +2,9 @@ - + ILiveDataEvent.StatusType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html index ce65b729aa7..1b1978f6d8d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html @@ -2,9 +2,9 @@ - + ILiveDataEvent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html index 584075ed6ee..fafe7d5be1d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html @@ -2,9 +2,9 @@ - + IMessage - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html index b5fbdd0036f..aceb2d3fd03 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html @@ -2,9 +2,9 @@ - + IServerTimeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html index 49d0e3c26e6..c763235b5f8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html @@ -2,9 +2,9 @@ - + IStoredDataEvent.Type - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html index 1f023ef8ab3..02e6a5f6c6c 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html @@ -2,9 +2,9 @@ - + IStoredDataEvent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html index 97f80c93446..f22d0421ad9 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html index c51653ee5c6..0c693d1d050 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html index 5bbe73d0d5d..645c92a3ba1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.event Class Hierarchy - + @@ -95,8 +95,8 @@ diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html index f9a3aa54977..19d9958e85c 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html index 28b84f49458..ed3dc34bc86 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html index 05e92009ba9..3703568fc44 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html index b503870a34d..2c87423618f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html @@ -2,9 +2,9 @@ - + IRaceCompetitorListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html index 2c7b6f0b011..14da69cda24 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html @@ -2,9 +2,9 @@ - + IRaceMessageListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html index 49405d2ec8e..03af92762b2 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html @@ -2,9 +2,9 @@ - + IRaceStartStopTimesChangeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html index 66741c9ad87..b0472d99832 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html @@ -2,9 +2,9 @@ - + IRacesListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html index 4a363357a9d..eb6a49aa636 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html @@ -2,9 +2,9 @@ - + IStartStopTimesChangeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html index 2b7b1295cf7..03dd75fb078 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.race - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html index 6c93f7e96bf..fd128ec0fe2 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.race - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html index 56d1e34897e..ea1dd1f73d9 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.race Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html index 3129db7443d..adbd6911405 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html @@ -2,9 +2,9 @@ - + IRoutesListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html index 2bbe32618d8..b01d00326dd 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html index e77847abc31..0ff81696499 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html index 52aac07a444..a4d8dcb920a 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.route Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/constant-values.html b/java/com.tractrac.clientmodule/javadoc/constant-values.html index 1a93b320e07..9f6e35b4f46 100755 --- a/java/com.tractrac.clientmodule/javadoc/constant-values.html +++ b/java/com.tractrac.clientmodule/javadoc/constant-values.html @@ -2,9 +2,9 @@ - + Constant Field Values - + diff --git a/java/com.tractrac.clientmodule/javadoc/deprecated-list.html b/java/com.tractrac.clientmodule/javadoc/deprecated-list.html index 0cbbc667491..220ab813168 100755 --- a/java/com.tractrac.clientmodule/javadoc/deprecated-list.html +++ b/java/com.tractrac.clientmodule/javadoc/deprecated-list.html @@ -2,9 +2,9 @@ - + Deprecated List - + diff --git a/java/com.tractrac.clientmodule/javadoc/help-doc.html b/java/com.tractrac.clientmodule/javadoc/help-doc.html index ae0daa65b65..4bdaed38129 100755 --- a/java/com.tractrac.clientmodule/javadoc/help-doc.html +++ b/java/com.tractrac.clientmodule/javadoc/help-doc.html @@ -2,9 +2,9 @@ - + API Help - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html index 7c8049d8518..cc76fb1475b 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html @@ -2,9 +2,9 @@ - + A-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html index 3abe9e0ad14..66bce86a90f 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html @@ -2,9 +2,9 @@ - + L-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html index 3b95bf121a0..7dd481727c3 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html @@ -2,9 +2,9 @@ - + M-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html index ff9219c1063..6e8aad1f123 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html @@ -2,9 +2,9 @@ - + N-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html index 434e8ebacf5..ef7c69cd883 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html @@ -2,9 +2,9 @@ - + O-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html index 222418dc1c9..44aa0af4b93 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html @@ -2,9 +2,9 @@ - + R-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html index 0ff70d2903b..c189d116290 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html @@ -2,9 +2,9 @@ - + S-Index - + @@ -161,6 +161,10 @@
    Subscribes to control passing data for the specified race.
    +
    subscribeControlPassings(IControlPassingsListener, UUID...) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    +
    +
    Subscribes to control passing data for the specified race and a class(es)
    +
    subscribeControlPositions(IControlPointPositionListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    Subscribes to control positions for the race.
    @@ -196,10 +200,18 @@
    Subscribes to competitor positions for a given Race.
    +
    subscribePositions(IPositionListener, UUID...) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    +
    +
    Subscribes to competitor positions for a given Race and class(es).
    +
    subscribePositions(IPositionListener, long, long) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    Subscribes to competitor positions for a given Race.
    +
    subscribePositions(IPositionListener, long, long, UUID...) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    +
    +
    Subscribes to competitor positions for a given Race and class(es).
    +
    subscribePositionsOffset(IPositionOffsetListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    Subscribes to offset competitor positions for a given Race.
    @@ -213,11 +225,20 @@
    Subscribes to snapped competitor positions for a given Race.
    +
    subscribePositionsSnapped(IPositionSnappedListener, UUID...) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    +
    +
    Subscribes to snapped competitor positions for a given Race and class(es).
    +
    subscribePositionsSnapped(IPositionSnappedListener, long, long) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    Subscribes to snapped competitor positions for a given Race, including historic data.
    +
    subscribePositionsSnapped(IPositionSnappedListener, long, long, UUID...) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    +
    +
    Subscribes to snapped competitor positions for a given Race and class(es), including + historic data.
    +
    subscribeRaceCompetitor(IRaceCompetitorListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    Subscribes to race competitor for a given Race.
    @@ -239,17 +260,14 @@
    This exception is thrown when it is not possible to create a susbcriber.
    +
    SubscriberInitializationException(String) - Constructor for exception com.tractrac.subscription.lib.api.SubscriberInitializationException
    +
     
    SubscriberInitializationException(Exception) - Constructor for exception com.tractrac.subscription.lib.api.SubscriberInitializationException
     
    subscribeRouteChanges(IControlRouteChangeListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    Subscribes to route changes for a given Race.
    -
    subscribeRoutes(IRoutesListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    -
    -
    - Subscriber to route changes.
    -
    subscribeServerTime(IServerTimeListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
    Subscribe to messages for the server time.
    diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html index dbddc592ab6..34787d7b483 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html @@ -2,9 +2,9 @@ - + T-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html index d1a5f2ac061..fea38968e9e 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html @@ -2,9 +2,9 @@ - + U-Index - + @@ -144,11 +144,6 @@
    Unsubscribes to route changes for a given Race.
    -
    unsubscribeRoutes(IRoutesListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
    -
    -
    - Unsubscribes to route changes for a given Race.
    -
    unsubscribeServerTime(IServerTimeListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
    Unsubscribes to messages for the server time.
    diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html index 2aa9d25b145..2fef48b752e 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html @@ -2,9 +2,9 @@ - + V-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html index 7b79ce0b3d8..11f3de9508d 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html @@ -2,9 +2,9 @@ - + B-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html index acd8fe8e536..6a901a6cda6 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html @@ -2,9 +2,9 @@ - + C-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html index 0f2aab2b2bf..5b4a1d7fc0e 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html @@ -2,9 +2,9 @@ - + D-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html index f58324406df..8fdbe9cadb1 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html @@ -2,9 +2,9 @@ - + E-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html index 8d0d5621170..5dd787133e5 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html @@ -2,9 +2,9 @@ - + F-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html index 08fe7bfe401..46db2da254c 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html @@ -2,9 +2,9 @@ - + G-Index - + @@ -406,7 +406,7 @@
    getPassings() - Method in interface com.tractrac.model.lib.api.data.IControlPassings
    - Returns a list of control passings.
    + Returns a list of control passings sorted by time.
    getPathRouteFactory() - Static method in class com.tractrac.model.lib.api.ModelLocator
    @@ -753,7 +753,11 @@
    gotRouteChange(IControlRoute, long) - Method in interface com.tractrac.subscription.lib.api.control.IControlRouteChangeListener
    -
    Invoked when updates to a route arrives
    +
    Invoked when updates to a control route arrives
    +
    +
    gotRouteChange(IPathRoute, long) - Method in interface com.tractrac.subscription.lib.api.control.IControlRouteChangeListener
    +
    +
    Invoked when updates to a path route arrives
    gotSensorData(IRaceCompetitor, ISensorData) - Method in interface com.tractrac.subscription.lib.api.competitor.ICompetitorSensorDataListener
    diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html index 8e505408b3e..a57e3a58e81 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html @@ -2,9 +2,9 @@ - + H-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html index 630338b3df8..d4e0589e0ba 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html @@ -2,9 +2,9 @@ - + I-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index.html b/java/com.tractrac.clientmodule/javadoc/index.html index 6e3820b553e..a92317add61 100755 --- a/java/com.tractrac.clientmodule/javadoc/index.html +++ b/java/com.tractrac.clientmodule/javadoc/index.html @@ -2,7 +2,7 @@ - + Generated Documentation (Untitled) diff --git a/java/com.tractrac.clientmodule/javadoc/overview-summary.html b/java/com.tractrac.clientmodule/javadoc/overview-summary.html index 3bc03c7a133..8084eae776a 100755 --- a/java/com.tractrac.clientmodule/javadoc/overview-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/overview-summary.html @@ -2,9 +2,9 @@ - + Overview - + diff --git a/java/com.tractrac.clientmodule/javadoc/overview-tree.html b/java/com.tractrac.clientmodule/javadoc/overview-tree.html index 15b46c0d56d..a847dcac8e5 100755 --- a/java/com.tractrac.clientmodule/javadoc/overview-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/overview-tree.html @@ -2,9 +2,9 @@ - + Class Hierarchy - + @@ -337,12 +337,12 @@
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) diff --git a/java/com.tractrac.clientmodule/javadoc/serialized-form.html b/java/com.tractrac.clientmodule/javadoc/serialized-form.html index 41b76082098..4c6f1fb57da 100755 --- a/java/com.tractrac.clientmodule/javadoc/serialized-form.html +++ b/java/com.tractrac.clientmodule/javadoc/serialized-form.html @@ -2,9 +2,9 @@ - + Serialized Form - + diff --git a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar index de4c29d27cd..3366c6dbc7d 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar and b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar differ diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 3bc94a18dc1..cc95dcfc5bd 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index e1d11c5efa5..2c21870c7f2 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.10.1 + 3.11.0 eclipse-plugin diff --git a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/AbstractListener.java b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/AbstractListener.java new file mode 100755 index 00000000000..ad29c81475f --- /dev/null +++ b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/AbstractListener.java @@ -0,0 +1,190 @@ +package com.tractrac.subscription.app.tracapi; + +import java.net.URI; +import java.util.UUID; + +import com.tractrac.model.lib.api.data.IControlPassings; +import com.tractrac.model.lib.api.data.IMessageData; +import com.tractrac.model.lib.api.data.IPosition; +import com.tractrac.model.lib.api.data.IPositionOffset; +import com.tractrac.model.lib.api.data.IPositionSnapped; +import com.tractrac.model.lib.api.data.IStartStopData; +import com.tractrac.model.lib.api.event.DataSource; +import com.tractrac.model.lib.api.event.ICompetitor; +import com.tractrac.model.lib.api.event.IEvent; +import com.tractrac.model.lib.api.event.IRace; +import com.tractrac.model.lib.api.event.IRaceCompetitor; +import com.tractrac.model.lib.api.route.IControl; +import com.tractrac.model.lib.api.route.IControlRoute; +import com.tractrac.model.lib.api.route.IPathRoute; +import com.tractrac.model.lib.api.sensor.ISensorData; +import com.tractrac.subscription.lib.api.competitor.ICompetitorSensorDataListener; +import com.tractrac.subscription.lib.api.competitor.ICompetitorsListener; +import com.tractrac.subscription.lib.api.competitor.IPositionListener; +import com.tractrac.subscription.lib.api.competitor.IPositionOffsetListener; +import com.tractrac.subscription.lib.api.competitor.IPositionSnappedListener; +import com.tractrac.subscription.lib.api.control.IControlPassingsListener; +import com.tractrac.subscription.lib.api.control.IControlPointPositionListener; +import com.tractrac.subscription.lib.api.control.IControlRouteChangeListener; +import com.tractrac.subscription.lib.api.control.IControlsListener; +import com.tractrac.subscription.lib.api.event.IConnectionStatusListener; +import com.tractrac.subscription.lib.api.event.IEventMessageListener; +import com.tractrac.subscription.lib.api.event.ILiveDataEvent; +import com.tractrac.subscription.lib.api.event.IStoredDataEvent; +import com.tractrac.subscription.lib.api.race.IRaceMessageListener; +import com.tractrac.subscription.lib.api.race.IRaceStartStopTimesChangeListener; +import com.tractrac.subscription.lib.api.race.IRacesListener; + +/** + * @author Jorge Piera Llodrá + */ +public abstract class AbstractListener implements IEventMessageListener, + IRaceMessageListener, IPositionListener, IPositionOffsetListener, + IPositionSnappedListener, IConnectionStatusListener, IControlPointPositionListener, + IControlPassingsListener, IRaceStartStopTimesChangeListener, + IControlRouteChangeListener, ICompetitorSensorDataListener, + IRacesListener, ICompetitorsListener, IControlsListener { + + @Override + public void gotStoredDataEvent(IStoredDataEvent storedDataEvent) { + + } + + @Override + public void gotLiveDataEvent(ILiveDataEvent liveDataEvent) { + + } + + @Override + public void gotRouteChange(IControlRoute controlRoute, long timeStamp) { + + } + + @Override + public void gotRouteChange(IPathRoute controlRoute, long timeStamp) { + + } + + @Override + public void gotControlPassings(IRaceCompetitor raceCompetitor, + IControlPassings markPassings) { + + } + + @Override + public void gotControlPointPosition(IControl control, IPosition position, int markNumber) { + + } + + @Override + public void gotPositionSnapped(IRaceCompetitor raceCompetitor, + IPositionSnapped positionSnapped) { + + } + + @Override + public void gotPositionOffset(IRaceCompetitor raceCompetitor, + IPositionOffset position) { + + } + + @Override + public void gotPosition(IRaceCompetitor raceCompetitor, IPosition position) { + + } + + @Override + public void gotRaceStartStopTime(IRace race, IStartStopData startStopData) { + + } + + @Override + public void gotTrackingStartStopTime(IRace race, IStartStopData startStopData) { + + } + + @Override + public void gotRaceMessage(IRace race, IMessageData messageData) { + + } + + @Override + public void stopped(Object object) { + + } + + @Override + public void gotEventMessage(IEvent event, IMessageData messageData) { + + } + + @Override + public void gotSensorData(IRaceCompetitor raceCompetitor, ISensorData sensorData) { + + } + + @Override + public void updateCompetitor(ICompetitor competitor) { + + } + + @Override + public void addCompetitor(ICompetitor competitor) { + + } + + @Override + public void deleteCompetitor(UUID competitorId) { + + } + + @Override + public void updateControl(IControl control) { + + } + + @Override + public void addControl(IControl control) { + + } + + @Override + public void deleteControl(UUID controlId) { + + } + + @Override + public void updateRace(IRace race) { + + } + + @Override + public void addRace(IRace race) { + + } + + @Override + public void deleteRace(UUID raceId) { + + } + + @Override + public void reloadRace(UUID raceId) { + + } + + @Override + public void abandonRace(UUID raceId) { + + } + + @Override + public void startTracking(UUID raceId) { + + } + + @Override + public void dataSourceChanged(IRace race, DataSource oldDataSource, URI oldLiveURI, URI oldStoredURI) { + + } +} diff --git a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/DelayListener.java b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/DelayListener.java new file mode 100755 index 00000000000..f29763405ad --- /dev/null +++ b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/DelayListener.java @@ -0,0 +1,55 @@ +package com.tractrac.subscription.app.tracapi; + +import com.tractrac.model.lib.api.data.*; +import com.tractrac.model.lib.api.event.*; +import com.tractrac.model.lib.api.route.IControl; +import com.tractrac.util.lib.api.TimeUtils; + +import java.util.Date; + +/** + * @author Jorge Piera Llodrá + */ +public class DelayListener extends AbstractListener { + + @Override + public void gotControlPointPosition(IControl control, IPosition position, int markNumber) { + //printDelay(position.getTimestamp(), control.getName()); + } + + @Override + public void gotPosition(IRaceCompetitor raceCompetitor, IPosition position) { + if (!raceCompetitor.getCompetitor().isNonCompeting()) { + printDelay(position.getTimestamp(), raceCompetitor.getCompetitor().getName()); + } + } + + private void printDelay(long sampleTime, String name) { + long now = new Date().getTime(); + long diff = now - sampleTime; + int steps = Math.round(diff/100); + StringBuilder delay = new StringBuilder(); + for (int i = 0 ; i " + + delay.toString() + ); + } + + public String fillString(String string, int count){ + if (string.length() > count) { + return string.substring(0, count); + } + String out = string; + for (int i=string.length() ;iJorge Piera Llodrá + * @author Jorge Piera Llodrá */ -public class EventListener implements IEventMessageListener, - IRaceMessageListener, IPositionListener, IPositionOffsetListener, - IPositionSnappedListener, IConnectionStatusListener, IControlPointPositionListener, - IControlPassingsListener, IRaceStartStopTimesChangeListener, - IControlRouteChangeListener { +public class EventListener extends AbstractListener { private static void show(Object obj) { - System.out.println(Thread.currentThread().getName() + ": " + obj); + System.out.println(String.valueOf(TimeUtils.formatDateInMillis(new Date().getTime())) + ": " + obj); } @Override @@ -57,7 +51,13 @@ public class EventListener implements IEventMessageListener, @Override public void gotRouteChange(IControlRoute controlRoute, long timeStamp) { - show("New route at " + timeStamp + ": " + controlRoute.toString()); + StringBuilder message = new StringBuilder(); + message.append("New route at " + timeStamp + ": " + controlRoute.toString()); + for (int i=0 ; iJorge Piera Llodrá + * + * @author Jorge Piera Llodrá */ public class Main { /** * @param args * the command line arguments - * @throws CreateModelException + * @throws CreateModelException */ public static void main(String[] args) throws URISyntaxException, MalformedURLException, FileNotFoundException, IOException, SubscriberInitializationException, CreateModelException, TimeOutException { - URI paramURI = parseArguments(args); + Object[] myArgs = parseArguments(args); + URI paramURI = (URI)myArgs[0]; + boolean measureDelay = (boolean)myArgs[1];; // Create the event object IEventFactory eventFactory = ModelLocator.getEventFactory(); @@ -46,44 +48,55 @@ public class Main { ISubscriberFactory subscriberFactory = SubscriptionLocator.getSusbcriberFactory(); IEventSubscriber eventSubscriber = subscriberFactory.createEventSubscriber(race.getEvent()); - EventListener eventListener = new EventListener(); - eventSubscriber.subscribeConnectionStatus(eventListener); - eventSubscriber.subscribeEventMessages(eventListener); - + AbstractListener listener; + if (measureDelay) { + listener = new DelayListener(); + } else { + listener = new EventListener(); + } + + eventSubscriber.subscribeConnectionStatus(listener); + eventSubscriber.subscribeEventMessages(listener); + eventSubscriber.subscribeRaces(listener); + eventSubscriber.subscribeControls(listener); + eventSubscriber.subscribeCompetitors(listener); + IRaceSubscriber raceSubscriber = subscriberFactory.createRaceSubscriber(race); - raceSubscriber.subscribeConnectionStatus(eventListener); - raceSubscriber.subscribeControlPositions(eventListener); - raceSubscriber.subscribePositions(eventListener); - raceSubscriber.subscribePositionsSnapped(eventListener); - raceSubscriber.subscribeControlPassings(eventListener); - raceSubscriber.subscribeRaceMessages(eventListener); - raceSubscriber.subscribeRaceMessages(eventListener); - raceSubscriber.subscribeRaceTimesChanges(eventListener); - raceSubscriber.subscribeRouteChanges(eventListener); - raceSubscriber.start(); - eventSubscriber.start(); - + raceSubscriber.subscribeConnectionStatus(listener); + raceSubscriber.subscribeControlPositions(listener); + raceSubscriber.subscribePositions(listener); +// raceSubscriber.subscribePositionsSnapped(listener); + raceSubscriber.subscribeControlPassings(listener); + raceSubscriber.subscribeCompetitorSensorData(listener); + raceSubscriber.subscribeRaceMessages(listener); + raceSubscriber.subscribeRaceTimesChanges(listener); + raceSubscriber.subscribeRouteChanges(listener); + raceSubscriber.start(); + eventSubscriber.start(); + // Go ahead with GUI or other stuff in main thread System.out.println("Press key to cancel live data stream"); System.in.read(); System.out.println("Cancelling data stream"); - + // Stop data streams - eventSubscriber.stop(); - raceSubscriber.stop(); + eventSubscriber.stop(); + raceSubscriber.stop(); } - private static URI parseArguments(String[] args) { - if (args.length != 1) { - System.out.println("Usage: java -jar TracAPI.jar parametersfile"); + private static Object[] parseArguments(String[] args) { + if (args.length < 1) { + System.out.println("Usage: java -jar TracAPI.jar parametersfile measureDelay"); System.exit(0); } + Object[] myArgs = new Object[2]; try { - return new URI(args[0]); + myArgs[0] = new URI(args[0]); + myArgs[1] = args.length >= 2 && args[1].equals("1"); } catch (URISyntaxException ex) { System.out.println("Malformed URL " + ex.getMessage()); System.exit(0); } - return null; + return myArgs; } } diff --git a/java/org.openqa.selenium.osgi/.classpath b/java/org.openqa.selenium.osgi/.classpath index 65f4aa99545..295889fd5f9 100644 --- a/java/org.openqa.selenium.osgi/.classpath +++ b/java/org.openqa.selenium.osgi/.classpath @@ -1,9 +1,18 @@ - - + + + + + + + + + + + + - diff --git a/java/org.openqa.selenium.osgi/META-INF/MANIFEST.MF b/java/org.openqa.selenium.osgi/META-INF/MANIFEST.MF index f93f8ec012a..a549abe2690 100644 --- a/java/org.openqa.selenium.osgi/META-INF/MANIFEST.MF +++ b/java/org.openqa.selenium.osgi/META-INF/MANIFEST.MF @@ -2,92 +2,49 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Selenium Bundle-SymbolicName: org.openqa.selenium.osgi -Bundle-Version: 2.40.0 -Bundle-ClassPath: lib/apache-mime4j-0.6.jar, - lib/bsh-1.3.0.jar, - lib/cglib-nodep-2.1_3.jar, - lib/commons-codec-1.8.jar, - lib/commons-collections-3.2.1.jar, - lib/commons-exec-1.1.jar, - lib/commons-io-2.2.jar, - lib/commons-jxpath-1.3.jar, - lib/commons-lang3-3.1.jar, - lib/commons-logging-1.1.1.jar, - lib/cssparser-0.9.11.jar, - lib/hamcrest-core-1.3.jar, - lib/hamcrest-library-1.3.jar, - lib/htmlunit-2.13.jar, - lib/htmlunit-core-js-2.13.jar, - lib/httpclient-4.3.1.jar, - lib/httpcore-4.3.jar, - lib/httpmime-4.3.1.jar, - lib/ini4j-0.5.2.jar, - lib/jcommander-1.29.jar, - lib/jetty-websocket-8.1.8.jar, - lib/jna-3.4.0.jar, - lib/jna-platform-3.4.0.jar, - lib/json-20080701.jar, - lib/junit-dep-4.11.jar, - lib/nekohtml-1.9.19.jar, - lib/netty-3.5.7.Final.jar, - lib/operadriver-1.5.jar, - lib/phantomjsdriver-1.1.0.jar, - lib/protobuf-java-2.4.1.jar, - lib/sac-1.3.jar, - lib/serializer-2.7.1.jar, - lib/testng-6.8.5.jar, - lib/xalan-2.7.1.jar, - lib/xercesImpl-2.10.0.jar, - selenium-java-2.40.0.jar, - lib/guava-15.0.jar +Bundle-Version: 3.13.0 +Bundle-ClassPath: lib/json-20080701.jar, + client-combined-3.13.0.jar, + lib/commons-codec-1.10.jar, + lib/byte-buddy-1.8.3.jar, + lib/commons-exec-1.3.jar, + lib/commons-logging-1.2.jar, + lib/gson-2.8.4.jar, + lib/guava-25.0-jre.jar, + lib/httpclient-4.5.5.jar, + lib/httpcore-4.4.9.jar, + lib/okhttp-3.10.0.jar, + lib/okio-1.14.1.jar Bundle-Vendor: openqa Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Export-Package: com.google.common.annotations, - com.google.common.base, - com.google.common.base.internal, - com.google.common.cache, +Export-Package: com.google.common.base, com.google.common.collect, - com.google.common.escape, - com.google.common.eventbus, - com.google.common.hash, - com.google.common.html, - com.google.common.io, - com.google.common.math, - com.google.common.net, - com.google.common.primitives, - com.google.common.reflect, - com.google.common.util.concurrent, - com.google.common.xml, - com.thoughtworks.selenium, - com.thoughtworks.selenium.condition, org.json, org.openqa.selenium, - org.openqa.selenium.browserlaunchers, - org.openqa.selenium.browserlaunchers.locators, org.openqa.selenium.chrome, + org.openqa.selenium.edge, org.openqa.selenium.firefox, org.openqa.selenium.firefox.internal, org.openqa.selenium.html5, - org.openqa.selenium.htmlunit, org.openqa.selenium.ie, org.openqa.selenium.interactions, org.openqa.selenium.interactions.internal, org.openqa.selenium.interactions.touch, org.openqa.selenium.internal, org.openqa.selenium.io, - org.openqa.selenium.lift, - org.openqa.selenium.lift.find, - org.openqa.selenium.lift.match, org.openqa.selenium.logging, org.openqa.selenium.logging.profiler, + org.openqa.selenium.mobile, org.openqa.selenium.net, + org.openqa.selenium.opera, org.openqa.selenium.os, org.openqa.selenium.remote, org.openqa.selenium.remote.html5, + org.openqa.selenium.remote.http, org.openqa.selenium.remote.internal, + org.openqa.selenium.remote.mobile, org.openqa.selenium.remote.service, org.openqa.selenium.safari, - org.openqa.selenium.security, org.openqa.selenium.support, org.openqa.selenium.support.events, org.openqa.selenium.support.events.internal, diff --git a/java/org.openqa.selenium.osgi/build.properties b/java/org.openqa.selenium.osgi/build.properties index feea6148156..603c6b2609c 100644 --- a/java/org.openqa.selenium.osgi/build.properties +++ b/java/org.openqa.selenium.osgi/build.properties @@ -1,38 +1,13 @@ bin.includes = META-INF/,\ - lib/apache-mime4j-0.6.jar,\ - lib/bsh-1.3.0.jar,\ - lib/cglib-nodep-2.1_3.jar,\ - lib/commons-codec-1.8.jar,\ - lib/commons-collections-3.2.1.jar,\ - lib/commons-exec-1.1.jar,\ - lib/commons-io-2.2.jar,\ - lib/commons-jxpath-1.3.jar,\ - lib/commons-lang3-3.1.jar,\ - lib/commons-logging-1.1.1.jar,\ - lib/cssparser-0.9.11.jar,\ - lib/hamcrest-core-1.3.jar,\ - lib/hamcrest-library-1.3.jar,\ - lib/htmlunit-2.13.jar,\ - lib/htmlunit-core-js-2.13.jar,\ - lib/httpclient-4.3.1.jar,\ - lib/httpcore-4.3.jar,\ - lib/httpmime-4.3.1.jar,\ - lib/ini4j-0.5.2.jar,\ - lib/jcommander-1.29.jar,\ - lib/jetty-websocket-8.1.8.jar,\ - lib/jna-3.4.0.jar,\ - lib/jna-platform-3.4.0.jar,\ lib/json-20080701.jar,\ - lib/junit-dep-4.11.jar,\ - lib/nekohtml-1.9.19.jar,\ - lib/netty-3.5.7.Final.jar,\ - lib/operadriver-1.5.jar,\ - lib/protobuf-java-2.4.1.jar,\ - lib/sac-1.3.jar,\ - lib/serializer-2.7.1.jar,\ - lib/testng-6.8.5.jar,\ - lib/xalan-2.7.1.jar,\ - lib/xercesImpl-2.10.0.jar,\ - lib/phantomjsdriver-1.1.0.jar,\ - selenium-java-2.40.0.jar,\ - lib/guava-15.0.jar + client-combined-3.13.0.jar,\ + lib/byte-buddy-1.8.3.jar,\ + lib/commons-codec-1.10.jar,\ + lib/commons-exec-1.3.jar,\ + lib/commons-logging-1.2.jar,\ + lib/gson-2.8.4.jar,\ + lib/guava-25.0-jre.jar,\ + lib/httpclient-4.5.5.jar,\ + lib/httpcore-4.4.9.jar,\ + lib/okhttp-3.10.0.jar,\ + lib/okio-1.14.1.jar diff --git a/java/org.openqa.selenium.osgi/client-combined-3.13.0-sources.jar b/java/org.openqa.selenium.osgi/client-combined-3.13.0-sources.jar new file mode 100644 index 00000000000..0cfbaf70c20 Binary files /dev/null and b/java/org.openqa.selenium.osgi/client-combined-3.13.0-sources.jar differ diff --git a/java/org.openqa.selenium.osgi/client-combined-3.13.0.jar b/java/org.openqa.selenium.osgi/client-combined-3.13.0.jar new file mode 100644 index 00000000000..edf3d0b920c Binary files /dev/null and b/java/org.openqa.selenium.osgi/client-combined-3.13.0.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/apache-mime4j-0.6.jar b/java/org.openqa.selenium.osgi/lib/apache-mime4j-0.6.jar deleted file mode 100644 index 1d2282ce263..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/apache-mime4j-0.6.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/bsh-1.3.0.jar b/java/org.openqa.selenium.osgi/lib/bsh-1.3.0.jar deleted file mode 100644 index ddd7cf6e7bf..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/bsh-1.3.0.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/byte-buddy-1.8.3.jar b/java/org.openqa.selenium.osgi/lib/byte-buddy-1.8.3.jar new file mode 100644 index 00000000000..97285567edf Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/byte-buddy-1.8.3.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/cglib-nodep-2.1_3.jar b/java/org.openqa.selenium.osgi/lib/cglib-nodep-2.1_3.jar deleted file mode 100644 index e9b17ebf129..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/cglib-nodep-2.1_3.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-codec-1.10.jar b/java/org.openqa.selenium.osgi/lib/commons-codec-1.10.jar new file mode 100644 index 00000000000..1d7417c4031 Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/commons-codec-1.10.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-codec-1.8.jar b/java/org.openqa.selenium.osgi/lib/commons-codec-1.8.jar deleted file mode 100644 index 32f84c92b7b..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-codec-1.8.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-collections-3.2.1.jar b/java/org.openqa.selenium.osgi/lib/commons-collections-3.2.1.jar deleted file mode 100644 index c35fa1fee14..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-collections-3.2.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-exec-1.1.jar b/java/org.openqa.selenium.osgi/lib/commons-exec-1.1.jar deleted file mode 100644 index baee06ff33f..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-exec-1.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-exec-1.3.jar b/java/org.openqa.selenium.osgi/lib/commons-exec-1.3.jar new file mode 100644 index 00000000000..9a64351981a Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/commons-exec-1.3.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-io-2.2.jar b/java/org.openqa.selenium.osgi/lib/commons-io-2.2.jar deleted file mode 100644 index 84ca565859d..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-io-2.2.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-jxpath-1.3.jar b/java/org.openqa.selenium.osgi/lib/commons-jxpath-1.3.jar deleted file mode 100644 index e344a1e66d8..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-jxpath-1.3.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-lang3-3.1.jar b/java/org.openqa.selenium.osgi/lib/commons-lang3-3.1.jar deleted file mode 100644 index a85e539b175..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-lang3-3.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-logging-1.1.1.jar b/java/org.openqa.selenium.osgi/lib/commons-logging-1.1.1.jar deleted file mode 100644 index 1deef144cb1..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/commons-logging-1.1.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/commons-logging-1.2.jar b/java/org.openqa.selenium.osgi/lib/commons-logging-1.2.jar new file mode 100644 index 00000000000..93a3b9f6db4 Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/commons-logging-1.2.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/cssparser-0.9.11.jar b/java/org.openqa.selenium.osgi/lib/cssparser-0.9.11.jar deleted file mode 100644 index 49eea5c51aa..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/cssparser-0.9.11.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/gson-2.8.4.jar b/java/org.openqa.selenium.osgi/lib/gson-2.8.4.jar new file mode 100644 index 00000000000..41bae96d7e7 Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/gson-2.8.4.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/guava-15.0.jar b/java/org.openqa.selenium.osgi/lib/guava-15.0.jar deleted file mode 100644 index eb9ef8ad5ff..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/guava-15.0.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/guava-25.0-jre.jar b/java/org.openqa.selenium.osgi/lib/guava-25.0-jre.jar new file mode 100644 index 00000000000..79a744e174f Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/guava-25.0-jre.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/hamcrest-core-1.3.jar b/java/org.openqa.selenium.osgi/lib/hamcrest-core-1.3.jar deleted file mode 100644 index 9d5fe16e3dd..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/hamcrest-core-1.3.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/hamcrest-library-1.3.jar b/java/org.openqa.selenium.osgi/lib/hamcrest-library-1.3.jar deleted file mode 100644 index 9eac80d7bae..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/hamcrest-library-1.3.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/htmlunit-2.13.jar b/java/org.openqa.selenium.osgi/lib/htmlunit-2.13.jar deleted file mode 100644 index b3178807b0f..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/htmlunit-2.13.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/htmlunit-core-js-2.13.jar b/java/org.openqa.selenium.osgi/lib/htmlunit-core-js-2.13.jar deleted file mode 100644 index ad61e8b4af5..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/htmlunit-core-js-2.13.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/httpclient-4.3.1.jar b/java/org.openqa.selenium.osgi/lib/httpclient-4.3.1.jar deleted file mode 100644 index cdd1e3874c4..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/httpclient-4.3.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/httpclient-4.5.5.jar b/java/org.openqa.selenium.osgi/lib/httpclient-4.5.5.jar new file mode 100644 index 00000000000..7796b0e742c Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/httpclient-4.5.5.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/httpcore-4.3.jar b/java/org.openqa.selenium.osgi/lib/httpcore-4.3.jar deleted file mode 100644 index ddfe6dacbc4..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/httpcore-4.3.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/httpcore-4.4.9.jar b/java/org.openqa.selenium.osgi/lib/httpcore-4.4.9.jar new file mode 100644 index 00000000000..cddba28f99b Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/httpcore-4.4.9.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/httpmime-4.3.1.jar b/java/org.openqa.selenium.osgi/lib/httpmime-4.3.1.jar deleted file mode 100644 index 2b26d7a79cc..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/httpmime-4.3.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/ini4j-0.5.2.jar b/java/org.openqa.selenium.osgi/lib/ini4j-0.5.2.jar deleted file mode 100644 index 27e5ddc7635..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/ini4j-0.5.2.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/jcommander-1.29.jar b/java/org.openqa.selenium.osgi/lib/jcommander-1.29.jar deleted file mode 100644 index 48cf0115bf4..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/jcommander-1.29.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/jetty-websocket-8.1.8.jar b/java/org.openqa.selenium.osgi/lib/jetty-websocket-8.1.8.jar deleted file mode 100644 index ca9e40b3b87..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/jetty-websocket-8.1.8.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/jna-3.4.0.jar b/java/org.openqa.selenium.osgi/lib/jna-3.4.0.jar deleted file mode 100644 index 3b8fb4f0e7c..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/jna-3.4.0.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/jna-platform-3.4.0.jar b/java/org.openqa.selenium.osgi/lib/jna-platform-3.4.0.jar deleted file mode 100644 index 2d9a0f2fd44..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/jna-platform-3.4.0.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/junit-dep-4.11.jar b/java/org.openqa.selenium.osgi/lib/junit-dep-4.11.jar deleted file mode 100644 index 93c717aa8a1..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/junit-dep-4.11.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/nekohtml-1.9.19.jar b/java/org.openqa.selenium.osgi/lib/nekohtml-1.9.19.jar deleted file mode 100644 index 979ffdedc8a..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/nekohtml-1.9.19.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/netty-3.5.7.Final.jar b/java/org.openqa.selenium.osgi/lib/netty-3.5.7.Final.jar deleted file mode 100644 index fb05e766d5e..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/netty-3.5.7.Final.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/okhttp-3.10.0.jar b/java/org.openqa.selenium.osgi/lib/okhttp-3.10.0.jar new file mode 100644 index 00000000000..1c3cfb826f4 Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/okhttp-3.10.0.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/okio-1.14.1.jar b/java/org.openqa.selenium.osgi/lib/okio-1.14.1.jar new file mode 100644 index 00000000000..c5eff37f989 Binary files /dev/null and b/java/org.openqa.selenium.osgi/lib/okio-1.14.1.jar differ diff --git a/java/org.openqa.selenium.osgi/lib/operadriver-1.5.jar b/java/org.openqa.selenium.osgi/lib/operadriver-1.5.jar deleted file mode 100644 index 95088bdfced..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/operadriver-1.5.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/phantomjsdriver-1.1.0.jar b/java/org.openqa.selenium.osgi/lib/phantomjsdriver-1.1.0.jar deleted file mode 100644 index 579f2b391e8..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/phantomjsdriver-1.1.0.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/protobuf-java-2.4.1.jar b/java/org.openqa.selenium.osgi/lib/protobuf-java-2.4.1.jar deleted file mode 100644 index e208b23398c..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/protobuf-java-2.4.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/sac-1.3.jar b/java/org.openqa.selenium.osgi/lib/sac-1.3.jar deleted file mode 100644 index 39b92b1d89c..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/sac-1.3.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/serializer-2.7.1.jar b/java/org.openqa.selenium.osgi/lib/serializer-2.7.1.jar deleted file mode 100644 index 99f98db9bfc..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/serializer-2.7.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/testng-6.8.5.jar b/java/org.openqa.selenium.osgi/lib/testng-6.8.5.jar deleted file mode 100644 index 9a394c9428a..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/testng-6.8.5.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/xalan-2.7.1.jar b/java/org.openqa.selenium.osgi/lib/xalan-2.7.1.jar deleted file mode 100644 index 458fa73d96e..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/xalan-2.7.1.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/lib/xercesImpl-2.10.0.jar b/java/org.openqa.selenium.osgi/lib/xercesImpl-2.10.0.jar deleted file mode 100644 index 9dcd8c38196..00000000000 Binary files a/java/org.openqa.selenium.osgi/lib/xercesImpl-2.10.0.jar and /dev/null differ diff --git a/java/org.openqa.selenium.osgi/pom.xml b/java/org.openqa.selenium.osgi/pom.xml index 80d28b3b37b..903dd440b9a 100644 --- a/java/org.openqa.selenium.osgi/pom.xml +++ b/java/org.openqa.selenium.osgi/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT org.openqa.selenium.osgi - 2.40.0 + 3.13.0 eclipse-plugin diff --git a/java/org.openqa.selenium.osgi/selenium-java-2.40.0.jar b/java/org.openqa.selenium.osgi/selenium-java-2.40.0.jar deleted file mode 100644 index 15abbb56368..00000000000 Binary files a/java/org.openqa.selenium.osgi/selenium-java-2.40.0.jar and /dev/null differ diff --git a/java/target/environment b/java/target/environment new file mode 100644 index 00000000000..99914dfa1a8 --- /dev/null +++ b/java/target/environment @@ -0,0 +1 @@ +JAVA_HOME=/opt/sapjvm_8 diff --git a/java/target/sailing@.service b/java/target/sailing@.service new file mode 100644 index 00000000000..499c9a0c651 --- /dev/null +++ b/java/target/sailing@.service @@ -0,0 +1,13 @@ +[Unit] +Description=SAP Sailing Analytics Server +Wants=apache2.service +After=network.target apache2.service + +[Service] +User=sailing +Group=sailing +EnvironmentFile=-/home/sailing/environment +EnvironmentFile=-/home/sailing/servers/%i/environment +WorkingDirectory=/home/sailing/servers/%i +ExecStart=/home/sailing/start_foreground +Restart=on-failure diff --git a/java/target/start_foreground b/java/target/start_foreground new file mode 100755 index 00000000000..da0ead5a8c3 --- /dev/null +++ b/java/target/start_foreground @@ -0,0 +1,83 @@ +#!/bin/bash + +# source variables +if [ "$TMP" = "" ]; then + TMP=/tmp +fi + +source "env.sh" + +# make some checks +JAVA_BINARY="$JAVA_HOME/bin/java" +if [[ ! -x "$JAVA_BINARY" ]] +then + echo "java not found" 1>&2 + exit 0 +fi + +if netstat -tlnp 2>/dev/null | grep ":$SERVER_PORT" >/dev/null +then + echo "There is something already running on port $SERVER_PORT. Perhaps your server that has not been stopped?" 1>&2 + exit 0 +fi + +scriptdir="$(dirname "$(readlink -f "$0")")" +LIB_DIR="$(pwd)/native-libraries" + +if [[ ! -d "logs" ]]; then + mkdir logs +fi + +if [[ ! -d "tmp" ]]; then + mkdir tmp +fi + +echo "Script directory is $scriptdir and LIB_DIR is $LIB_DIR" + +# Make really sure that parameters from config.ini are not used +sed -i "/mongo.host/d" ./configuration/config.ini +sed -i "/mongo.port/d" ./configuration/config.ini +sed -i "/expedition.udp.port/d" ./configuration/config.ini +sed -i "/replication.exchangeName/d" ./configuration/config.ini +sed -i "/replication.exchangeHost/d" ./configuration/config.ini + +# Ensure that Jetty Port is set correctly +sed -i "s/^.*jetty.http.port.*$/<\/Set>/g" ./configuration/jetty/etc/jetty-http.xml + +# Update monitoring with the right ports +sed -i "s/127.0.0.1:[0-9][0-9][0-9][0-9]\//127.0.0.1:$SERVER_PORT\//g" ./configuration/monitoring.properties + +# Inject information for system +HEAD_DATE=$(date "+%Y%m%d%H%M") +sed -i "s/System:.*$/ System: $MONGODB_HOST:$MONGODB_PORT\/$MONGODB_NAME-$EXPEDITION_PORT-$REPLICATION_HOST:$REPLICATION_PORT\/$REPLICATION_CHANNEL Started: $HEAD_DATE/g" ./configuration/jetty/version.txt + +props=( + -D$SERVER_NAME + -Djava.awt.headless=true + -Dcom.sap.sailing.server.name=$SERVER_NAME + -Dmongo.host="$MONGODB_HOST" + -Dmongo.port="$MONGODB_PORT" + -Dmongo.dbName="$MONGODB_NAME" + -Dexpedition.udp.port="$EXPEDITION_PORT" + -Dreplication.exchangeHost="$REPLICATION_HOST" + -Dreplication.exchangePort="$REPLICATION_PORT" + -Dreplication.exchangeName="$REPLICATION_CHANNEL" + -Dreplicate.on.start="$REPLICATE_ON_START" + -Dreplicate.master.servlet.host="$REPLICATE_MASTER_SERVLET_HOST" + -Dreplicate.master.servlet.port="$REPLICATE_MASTER_SERVLET_PORT" + -Dreplicate.master.queue.host="$REPLICATE_MASTER_QUEUE_HOST" + -Dreplicate.master.queue.port="$REPLICATE_MASTER_QUEUE_PORT" + -Dreplicate.master.exchange.name="$REPLICATE_MASTER_EXCHANGE_NAME" + -Djava.io.tmpdir="$PWD/tmp" + -Dfile.encoding=cp1252 + -Djetty.home=$scriptdir/configuration/jetty + -Djava.util.logging.config.file=$scriptdir/configuration/logging.properties + -Djava.library.path=$LIB_DIR + -Dosgi.shell.telnet.port=$TELNET_PORT +) + +jar="plugins/org.eclipse.equinox.launcher_1.3.0.v20140415-2008.jar" +args=(-configuration ./ -clean) + +"$JAVA_BINARY" "${props[@]}" $ADDITIONAL_JAVA_ARGS -Xmx$MEMORY -jar "$jar" "${args[@]}" + diff --git a/orchestration/.editorconfig b/orchestration/.editorconfig new file mode 100644 index 00000000000..8fe08aa7800 --- /dev/null +++ b/orchestration/.editorconfig @@ -0,0 +1,17 @@ +; indicate this is the root of the project +root = true + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = tab + +[Makefile] +indent_style = tab + +[makefile] +indent_style = tab diff --git a/orchestration/.gitignore b/orchestration/.gitignore new file mode 100644 index 00000000000..9c91c92b1c4 --- /dev/null +++ b/orchestration/.gitignore @@ -0,0 +1,65 @@ + Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Specific stuff +*.pdf +/configs/secrets/* +!/configs/secrets/*.sample +/bin +/pkg +/src/* +!/src/sapsailing.com/ +testlogs/* +*.tgz +*.log diff --git a/orchestration/.gitlab-ci.yml b/orchestration/.gitlab-ci.yml new file mode 100644 index 00000000000..feecf2a6c9c --- /dev/null +++ b/orchestration/.gitlab-ci.yml @@ -0,0 +1,77 @@ +stages: + - deps + - build + - test + - posttest + - distro + +before_script: + - pwd + - source ${CI_PROJECT_DIR}/env.sh + - echo "GOPATH is set to ${GOPATH}" + - cp ~/.ssh/id_rsa ${CI_PROJECT_DIR}/configs/secrets/id_rsa + - cp ~/.ssh/id_rsa.pub ${CI_PROJECT_DIR}/configs/secrets/id_rsa.pub + - cp ~/conf_aws.json ${CI_PROJECT_DIR}/configs/secrets/conf_aws.json + - make clean + +makedistro: + stage: distro + artifacts: + paths: + - orchestration*.tgz + expire_in: 1 month + script: + - echo "Creating distribution" + - pwd + - rm -rf ${CI_PROJECT_DIR}/bin + - mkdir -p ${CI_PROJECT_DIR}/bin + - make build + - make distro + +makedeps: + stage: deps + script: + - echo "Getting Dependencies" + - pwd + - make dep + +makebuild: + stage: build + artifacts: + paths: + - bin + expire_in: 1 week + script: + - echo "Build Started" + - pwd + - rm -rf ${CI_PROJECT_DIR}/bin + - mkdir -p ${CI_PROJECT_DIR}/bin + - make build + +testbuild: + stage: test + artifacts: + expire_in: 1 week + paths: + - ${CI_PROJECT_DIR}/testlogs + script: + - echo "Test Started" + - pwd + - rm -rf ${CI_PROJECT_DIR}/testlogs + - mkdir -p ${CI_PROJECT_DIR}/testlogs + - make test + +checktestlog: + stage: posttest + artifacts: + when: always + expire_in: 1 week + paths: + - ${CI_PROJECT_DIR}/testlogs + script: + - echo "check Tests" + - pwd + - make checktest + - make vet + - make testcover + when: on_success diff --git a/orchestration/Makefile b/orchestration/Makefile new file mode 100644 index 00000000000..e6ca4b43bb8 --- /dev/null +++ b/orchestration/Makefile @@ -0,0 +1,94 @@ +GOCMD := go +GOBUILD := $(GOCMD) build +GOINSTALL := $(GOCMD) install -v +GOCLEAN := $(GOCMD) clean -v +GOTEST := $(GOCMD) test -cover -v -timeout 1200s +GOGET := $(GOCMD) get -u -v +GOFMT := gofmt +GOCOVER := $(GOCMD) tool cover +GOVET := $(GOCMD) vet +GOBENCHMARK=$(GOCMD) test ./src/sapsailing.com/... -run=XXX -bench=. +DEPENDENCIES := \ + golang.org/x/crypto/ssh \ + github.com/aws/aws-sdk-go/aws \ + github.com/aws/aws-sdk-go/aws/session \ + github.com/aws/aws-sdk-go/service/ec2 \ + github.com/sirupsen/logrus \ + github.com/stretchr/testify/assert \ + github.com/go-resty/resty \ + github.com/mitchellh/mapstructure \ + github.com/pkg/sftp \ + mvdan.cc/sh/syntax \ + golang.org/x/text \ + github.com/pbnjay/memory +VERSION=0.0.1 +BUILD=`git rev-parse HEAD` +BRANCH=`git rev-parse --abbrev-ref HEAD` +PLATFORMS=linux +ARCHITECTURES=amd64 + +# Setup linker flags option for build that interoperate with variable names in src code +LDFLAGS=-ldflags "-X main.BuildVersion=${VERSION} -X main.BuildRevision=${BUILD} -X main.BuildBranch=${BRANCH}" + +ifndef PREFIX +$(error PREFIX is not set) +endif + +ifndef GOPATH +$(error GOPATH is not set) +endif + +all: clean build test distro + +test: + @echo "executing all tests now" + @$(GOTEST) ./src/sapsailing.com/$(PACKAGE)/... $(ARGS) 2>&1 | tee $${PREFIX}/testlogs/${VERSION}_${BUILD}_test.log ; + @echo "done." + +checktest: + @echo "Skipped Tests..." + @cat ${PREFIX}/testlogs/*_test.log | egrep -n "SKIP:"; test $$? -eq 1; + + @echo "Failed Tests..." + @cat $${PREFIX}/testlogs/*_test.log | egrep -n "FAIL:|\[build failed\]|^FAIL\s+"; test $$? -eq 1; + +vet: + @$(GOVET) sapsailing.com/... 2>&1 | tee -a $${PREFIX}/testlogs/*_${BUILD}_test.log || test 1; + @echo "done." + +testcover: + @cat $${PREFIX}/testlogs/*_test.log | grep -E '(ok|FAIL|\?)(.*)sapsailing.com\/(.*)(.*)' 2>&1 | tee -a ${PREFIX}/testlogs/*_${BUILD}_test.log + @bash ./calculateCodeCoverage.sh $${PREFIX}/testlogs/*_test.log | tee -a $${PREFIX}/testlogs/*_${BUILD}_test.log + +benchmark: + @$(GOBENCHMARK) || exit 1; + +fmt: + @find ./src/sapsailing.com -name '*.go' -exec $(GOFMT) -w -s "{}" \; + @echo "done." + +build: + $(foreach GOOS, $(PLATFORMS),\ + $(foreach GOARCH, $(ARCHITECTURES), cd ${PREFIX}/src/sapsailing.com/orchestrator; GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) ${LDFLAGS} -v -o $${PREFIX}/bin/orchestrator-$(GOOS)-$(GOARCH))) + $(foreach GOOS, $(PLATFORMS),\ + $(foreach GOARCH, $(ARCHITECTURES), cd ${PREFIX}/src/sapsailing.com/agent; GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) ${LDFLAGS} -v -o $${PREFIX}/bin/agent-$(GOOS)-$(GOARCH))) + @chmod +x $${PREFIX}/bin/* + @echo "done." + +clean: + @$(GOCLEAN) ./src/sapsailing.com/... || exit 1; + @rm -rf orchestration*.tgz + @rm -rf bin/* + @echo "done." + +dep: + @for p in $(DEPENDENCIES); do \ + echo "__ downloading $$p ...";\ + $(GOGET) $$p || exit 1; \ + done + +distro: + bash ./createDistro.sh "$(VERSION)_$(BUILD)" || exit 1; + +install: + @$(GOINSTALL) $${LDFLAGS} ./src/sapsailing.com/... diff --git a/orchestration/bash/aws-setup.sh b/orchestration/bash/aws-setup.sh new file mode 100755 index 00000000000..654b18e1d20 --- /dev/null +++ b/orchestration/bash/aws-setup.sh @@ -0,0 +1,266 @@ +#!/usr/bin/env bash + +# AWS automation script for setting up SAP Sailing Analytics instance infrastructure +# ------------------------------------------------------ + +version="1.0.0" # Sets version variable + +scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +utilsLocation="${scriptPath}/lib/utils.sh" + +# source utils.sh which itself sources all other .sh files in the /lib directory +if [ -f "${utilsLocation}" ]; then + source "${utilsLocation}" +else + echo "Please find the file util.sh and add a reference to it in this script. Exiting." + exit 1 +fi + +# delete temp files when trapped +function trapCleanup() { + echo "" + deleteTemp + die "Exit trapped. In function: '${FUNCNAME[*]}'" +} + +function safeExit() { + deleteTemp + trap - INT TERM EXIT + exit 77 +} + +function deleteTemp () { + if is_dir "${tmpDir}"; then + rm -r "${tmpDir}" + fi +} + +quiet=false +printLog=false +verbose=true +force=false +strict=false +debug=false +args=() + +# Create temp directory with three random numbers and the process ID +# in the name. This directory is removed automatically at exit. +#tmpDir="/tmp/$RANDOM.$RANDOM.$RANDOM.$$/" +#(umask 000 && mkdir "${tmpDir}") || { +# die "Could not create temporary directory! Exiting." +#} + +# Logging (not used) +# To never save a logfile change variable to '/dev/null' +# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log +# ----------------------------------- +logFile="$HOME/Library/Logs/${scriptBasename}.log" + +function mainScript() { +echo -n + +local config_file=~/.aws-automation/config + +if is_exists ${config_file}; then + source "${config_file}" +fi + +require_region + +local resource_file=./lib/resources-$region.sh +local region_config_file=~/.aws-automation/config-$region + +if is_exists ${resource_file}; then + source "${resource_file}" +fi + +if is_exists ${region_config_file}; then + source "${region_config_file}" +fi + +init_resources + +if $instance_scenario; then + instance_start + safeExit +fi + +if $shared_instance_scenario; then + shared_instance_start + safeExit +fi + +if $master_instance_scenario; then + master_instance_start + safeExit +fi + +if $replica_instance_scenario; then + replica_instance_start + safeExit +fi + +if $associate_alb_scenario; then + associate_alb_start + safeExit +fi + +} + +usage() { + echo -n "${scriptName} [OPTION]... + + This is an AWS automation bash script for deploying SAP Sailing Analytics + instances and their depending infrastructure + + ${bold}Parameter:${reset} + -r, --region AWS region (e.g. \"eu-west-2\" for London) + -d, --debug Debug mode + + // other parameters disabled for now + + ${bold}Scenarios:${reset} + --instance Create instance + --shared-instance Create sub instance + --master-instance Create master instance + --replica-instance Create replica instance + --associate-alb Associate instance with existing application load balancer. + + ${bold}Other:${reset} + --version Output version information and exit + + + ${bold}Examples:${reset} + Create instance: + > ./aws-setup.sh --instance + + Create shared instance: + > ./aws-setup.sh --shared-instance + + Associate instance with application load balancer: + > ./aws-setup.sh --associate-alb + + Create master instance: + > ./aws-setup.sh --master-instance + + Create replica instance: + > ./aws-setup.sh --replica-instance +" +} + +# Iterate over options breaking -ab into -a -b when needed and --foo=bar into +# --foo bar +optstring=h +unset options +while (($#)); do + case $1 in + # If option is of type -ab + -[!-]?*) + # Loop over each character starting with the second + for ((i=1; i < ${#1}; i++)); do + c=${1:i:1} + + # Add current char to options + options+=("-$c") + + # If option takes a required argument, and it's not the last char make + # the rest of the string its argument + if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then + options+=("${1:i+1}") + break + fi + done + ;; + + # If option is of type --foo=bar + --?*=*) options+=("${1%%=*}" "${1#*=}") ;; + # add --endopts for -- + --) options+=(--endopts) ;; + # Otherwise, nothing special + *) options+=("$1") ;; + esac + shift +done +set -- "${options[@]}" +unset options + +# Print help if no arguments were passed. +# Uncomment to force arguments when invoking the script +[[ $# -eq 0 ]] && set -- "--help" + +# Set default value of variable without parameter value to false +associate_alb_scenario=false +instance_scenario=false +shared_instance_scenario=false +master_instance_scenario=false +replica_instance_scenario=false + +# Read the options and set variables +while [[ $1 = -?* ]]; do + case $1 in + -h|--help) usage >&2; safeExit ;; + --version) echo "$(basename $0) ${version}"; safeExit ;; + -r|--region) shift; region_param=${1} ;; + -t|--instance-type) shift; instance_type_param=${1} ;; + -k|--key-name) shift; key_name_param=${1} ;; + -i|--key-file) shift; key_file_param=${1} ;; + -s|--ssh-user) shift; ssh_user_param=${1} ;; + -n|--instance-name) shift; instance_name_param=${1} ;; + -l|--instance-short-name) shift; instance_short_name_param=${1} ;; + -a|--new-admin-password) shift; new_admin_password_param=${1} ;; + -p|--public-dns-name) shift; public_dns_name_param=${1} ;; + -b|--super-instance) shift; super_instance_param=${1} ;; + -m|--event-name) shift; event_name_param=${1} ;; + -x|--mongodb-host) shift; mongodb_host_param=${1} ;; + -y|--mongodb-port) shift; mongodb_name_param=${1} ;; + -z|--build) shift; build_version_param=${1} ;; + -w|--description) shift; description_param=${1} ;; + -c|--contact-person) shift; contact_person_param=${1} ;; + -e|--contact-email) shift; contact_email_param=${1} ;; + -f|--force) force=true ;; + -d|--debug) debug=true ;; + --instance) instance_scenario=true ;; + --shared-instance) shared_instance_scenario=true ;; + --master-instance) master_instance_scenario=true ;; + --replica-instance) replica_instance_scenario=true ;; + --associate-alb) associate_alb_scenario=true ;; + --endopts) shift; break ;; + *) die "invalid option: '$1'." ;; + esac + shift +done + +# Store the remaining part as arguments. +args+=("$@") + +############# RUN SCRIPT ############# + +# Trap bad exits with your cleanup function +trap trapCleanup EXIT INT TERM + +# Set IFS to preferred implementation +IFS=$'\n\t' + +# Exit on error. Append '||true' when you run the script if you expect an error. +# set -o errexit + +# Run in debug mode, if set +if ${debug}; then set -x ; fi + +# Exit on empty variable +if ${strict}; then set -o nounset ; fi + +# Allow exit from subshells +set -o pipefail +set -E +trap '[ "$?" -ne 77 ] || exit 77' ERR + +# Invoke the checkDependenices function to test for Bash packages. Uncomment if needed. +# checkDependencies + +# Run script +mainScript + +# Exit cleanlyd +safeExit diff --git a/orchestration/bash/lib/aws-setup.sh b/orchestration/bash/lib/aws-setup.sh new file mode 100644 index 00000000000..e69de29bb2d diff --git a/orchestration/bash/lib/build-config.sh b/orchestration/bash/lib/build-config.sh new file mode 100644 index 00000000000..f24aa3087d8 --- /dev/null +++ b/orchestration/bash/lib/build-config.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# ----------------------------------------------------------- +# Scrpt for reading and writing variables to a file. Not used currently. +# ----------------------------------------------------------- + +# https://github.com/brbsix/bash-config +# Read and write config files from Bash + + +readonly PROGRAM="${0##*/}" +readonly VERSION='0.1.dev1' + +readonly SEPARATOR_BASH='=' +readonly SEPARATOR_INI=' = ' + + +error(){ + echo "ERROR: $*" >&2 +} + +get_line_number(){ + set_mode + local output=$(sed -n "/^${parameter}${separator}/=" "$config" 2>/dev/null) + [[ ! -z ${output##*[!0-9]*} ]] && line_number="$output" +} + +main(){ + if echo "$1" | grep -Eq '^(-b|--bash)$'; then + option_mode=bash + elif echo "$1" | grep -Eq '^(-c|--custom)$'; then + option_mode=custom + elif echo "$1" | grep -Eq '^(-i|--ini)$'; then + option_mode=ini + fi + + local args + if [[ $option_mode = bash || $option_mode = ini ]]; then + args=$(($# - 1)) + config=$2 + parameter=$3 + value=$4 + elif [[ $option_mode = custom ]]; then + args=$(($# - 2)) + separator_custom=$2 + config=$3 + parameter=$4 + value=$5 + else + args=$# + config=$1 + parameter=$2 + value=$3 + fi + + if (( args == 0 )); then + error "No arguments" + see_help_msg + exit 1 + elif (( args == 1 )) && echo "$1" | grep -Eq '^(-h|--help|help)$'; then + usage + elif (( args == 1 )) && echo "$1" | grep -Eq '^(-v|--version|version)$'; then + echo "$PROGRAM $VERSION" + exit 0 + elif (( args == 2 )); then + if [[ $parameter = % ]]; then + # DELETE CONFIG + if [[ ! -f $config ]]; then + error "'$config' does not exist" + exit 1 + elif [[ ! -w $config ]]; then + error "Lacking write perms to '$config'" + exit 1 + else + remove_config + fi + else + # READ PARAMETER + if [[ ! -f $config ]]; then + error "'$config' does not exist" + exit 1 + elif [[ ! -r $config ]]; then + error "No read access to '$config'" + exit 1 + else + read_parameter + fi + fi + elif (( args == 3 )); then + if [[ $value = % ]]; then + # DELETE PARAMETER + if [[ ! -f $config ]]; then + error "'$config' does not exist" + exit 1 + elif [[ ! -w $config ]]; then + error "No write access to '$config'" + exit 1 + else + remove_parameter + fi + else + # WRITE PARAMETER + if [[ -f $config && ! -w $config ]]; then + error "No write access to '$config'" + exit 1 + else + write_parameter + fi + fi + else + error "Invalid arguments" + see_help_msg + exit 1 + fi +} + +read_parameter(){ + set_mode + + local output=$(sed -n "s/^${parameter}${separator}//p" "$config" 2>/dev/null) + + if [[ -n $output ]]; then + echo "$output" + else + if grep -q "^${parameter}${separator}$" "$config" 2>/dev/null; then + error "'$parameter' is set to null in '$config'" + exit 0 + else + error "No match for '$parameter' in '$config'" + exit 1 + fi + fi +} + +remove_config(){ + rm -f "$config" &>/dev/null + + if [[ -f $config ]]; then + error "Failed to remove '$config'" + exit 1 + fi +} + +remove_parameter(){ + get_line_number + [[ -n $line_number ]] && sed -i "${line_number}d" "$config" 2>/dev/null + if grep -q "^${parameter}${separator}" "$config" 2>/dev/null; then + error "Failed to remove parameter '$parameter' from '$config'" + exit 1 + elif [[ -f $config && ! -s $config ]]; then # remove config if empty + remove_config + fi +} + +sanitize_parameter(){ + if ! echo "$parameter" | grep -q '^[A-Za-z0-9_-]\+$'; then + error "Parameter may consist of A-Z, a-z, 0-9, underscores, and dashes (no spaces or special characters)" + exit 1 + fi +} + +see_help_msg(){ + echo "Try '$PROGRAM --help' for more information." >&2 +} + +set_mode(){ + grep -q "^[A-Za-z0-9_-]\+${SEPARATOR_BASH}" "$config" &>/dev/null && mode+=(bash) + + grep -q "^[A-Za-z0-9_-]\+${SEPARATOR_INI}" "$config" &>/dev/null && mode+=(ini) + + [[ $option_mode = custom ]] && grep -q "^[A-Za-z0-9_-]\+${separator_custom}" "$config" &>/dev/null && mode+=(custom) + + if (( ${#mode[@]} > 1 )); then + error "Unable to determine a consistent configuration file format. Are you sure '$config' is a config file?" + exit 1 + fi + + # OPTION IS SET + if [[ -n $option_mode ]]; then + + if [[ -n ${mode[0]} && ( ${mode[0]} != $option_mode ) ]]; then + # use detected mode in the event of a conflicting option + echo "WARNING: '$config' was intified as ${mode[0]} mode (ignoring $option_mode option)" >&2 + mode=${mode[0]} + else + # use optioned mode + mode=$option_mode + fi + + # NO OPTION IS SET and we have detected mode + elif [[ -n ${mode[0]} ]]; then + # use detected mode + mode=${mode[0]} + fi + + set_separator "$mode" +} + +set_separator(){ + if [[ $1 = custom ]]; then + separator=$separator_custom + elif [[ $1 = ini ]]; then + separator=$SEPARATOR_INI + else + # default to bash mode + separator=$SEPARATOR_BASH + fi +} + +usage(){ + cat <<-EOF + Usage: $PROGRAM [OPTION] [value] + Read and write config files from bash. + + -b, --bash use bash mode ex. '=' + -c, --custom use custom separator string + -i, --ini use ini mode ex. ' = ' + -v, --version show program's version number and exit + + * Enter a value to create or update an entry + * Specify only a parameter to print the currently set value (if any) + * Use '%' to delete the config file or a specific parameter + EOF + exit 0 +} + +verify_write(){ + local fullstring=${parameter}${separator}${value} + if ! grep -q "^${fullstring}$" "$config" &>/dev/null; then + error "Failed to write '${fullstring}' to '$config'" + exit 1 + fi +} + +write_parameter(){ + if [[ -f $config ]]; then + get_line_number + if [[ -n $line_number ]]; then + sed -i "${line_number}c${parameter}${separator}${value}" "$config" 2>/dev/null + else + echo "${parameter}${separator}${value}" >> "$config" + sort -o "$config" "$config" + fi + else + set_separator "$option_mode" + echo "${parameter}${separator}${value}" >> "$config" + fi + verify_write +} + +main "$@" diff --git a/orchestration/bash/lib/functions_app.sh b/orchestration/bash/lib/functions_app.sh new file mode 100644 index 00000000000..6c8c15d42bb --- /dev/null +++ b/orchestration/bash/lib/functions_app.sh @@ -0,0 +1,189 @@ +#!/usr/bin/env bash + + +# ----------------------------------------------------------- +# Functions that are relevant for the configuration of the sap instance. +# ----------------------------------------------------------- + + +# ----------------------------------------------------------- +# Get latest release build from releases.sapsailing.com +# @return latest build +# ----------------------------------------------------------- +function get_latest_release(){ + + # get html from releases.sapsailing.com + local html=$(wget releases.sapsailing.com -q -O -) + + # extract all links + local links=$(grep -Po '(?<=href=")[^"]*' <<< "$html") + + # extract build strings (e.g. build-201709291756) + local builds=$(grep -Po 'build-\d+' <<< "$links") + + # sort build strings reverse using their date + local result=$(sort -k1.1,1.8 -k1.9nr <<< "$builds") + + # take latest build + echo "$result" | head -1 +} + +# ----------------------------------------------------------- +# Get access token +# @param $1 admin username +# @param $2 admin password +# @param $3 dns name of instance +# @param $4 port +# @return access token +# ----------------------------------------------------------- +function get_access_token(){ + local_echo -n "Getting access token..." + curl_until_response "200" http://$1:$2@$3:$4/security/api/restsecurity/access_token + curl_wrapper -X GET "http://$1:$2@$3:$4/security/api/restsecurity/access_token" | get_attribute '.access_token' +} + +# ----------------------------------------------------------- +# Creates a new event with event name = instance name, no regatta and venuename="Default" +# @param $1 access token +# @param $2 dns name of instance +# @param $3 port +# @param $4 event name +# @return event id of event +# ----------------------------------------------------------- +function create_event(){ + local_echo -n "Creating event with name $4..." + curl_until_response "401" http://$2:$3/sailingserver/api/v1/events/createEvent + curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:$3/sailingserver/api/v1/events/createEvent" --data "eventName=$4" --data "venuename=Default" --data "createregatta=false" | get_attribute '.eventid' +} + +# ----------------------------------------------------------- +# Changes password of user +# @param $1 access token +# @param $2 dns name of instance +# @param $3 port +# @param $4 admin username +# @param $5 admin new password +# ----------------------------------------------------------- +function change_admin_password(){ + local_echo "Changing password of user $4 to $5..." + curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:$3/security/api/restsecurity/change_password" --data "username=$4" --data "password=$5" +} + +# ----------------------------------------------------------- +# Creates new user +# @param $1 access token +# @param $2 dns name of instance +# @param $3 port +# @param $4 user username +# @param $5 user password +# ----------------------------------------------------------- +function create_new_user(){ + local_echo "Creating new user \"$4\" with password \"$5\"..." + curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:$3/security/api/restsecurity/create_user?username=$4&password=$5" +} + +# ----------------------------------------------------------- +# Creates event, changes admin password, creates user if parameters are not empty. +# @param $1 public dns name +# @param $2 port of application instance +# @param $3 event name +# @param $4 new admin password +# @param $5 user username +# @param $6 user password +# @return event id if event was created +# ----------------------------------------------------------- +function configure_application(){ + if [ -z "$3" ] && [ -z "$4" ] && [ -z "$5" ] && [ -z "$6" ]; then + return 0 + fi + + access_token=$(get_access_token "admin" "admin" $1 $2) + + if [ ! -z "$3" ]; then + event_id=$(create_event $access_token $1 $2 $3) + fi + + if [ ! -z "$4" ]; then + response=$(change_admin_password $access_token $1 $2 "admin" $4) + fi + + if [ ! -z "$5" ] && [ ! -z "$6" ]; then + user=$(create_new_user $access_token $1 $2 $5 $6) + fi + + echo ${event_id:-""} +} + +# ----------------------------------------------------------- +# Patch 001-events.conf with Even-SSL macro if event id is not empty. Else use Home-SSL macro. +# @param $1 domain +# @param $2 event id +# @param $3 ssh user +# @param $4 public dns name +# @param $5 server port +# ----------------------------------------------------------- +function append_macro_to_001_events_conf(){ + wait_for_001_events_patch $3 $4 + + if [ -z "$2" ]; then + patched_content="Use Home-SSL $1 127.0.0.1 $5" + else + patched_content="Use Event-SSL $1 \\\"$2\\\" 127.0.0.1 $5" + fi + + local_echo "Appending´\"$patched_content\" to $events_conf..." + ssh_wrapper $3@$4 echo "$patched_content >> $events_conf" +} + +# ----------------------------------------------------------- +# Append specified environment to env.sh. +# @param $1 environment +# @param $2 ssh user +# @param $3 dns name of instance +# @param $4 path to env.sh file +# ----------------------------------------------------------- +function append_environment_to_env_sh(){ + local env_file=${4:-'/home/sailing/servers/server/env.sh'} + local_echo "Appending environment '$1' to env.sh..." + env_content=$(wget -qO- http://releases.sapsailing.com/environments/$1) + + exit_on_fail ssh_wrapper $2@$3 "echo -e \"# START Environment: $1 \" >> $env_file" + exit_on_fail ssh_wrapper $2@$3 "export ADDITIONAL_JAVA_ARGS='\$ADDITIONAL_JAVA_ARGS'; export MEMORY='$MEMORY'; echo \"$env_content\" >> $env_file" + exit_on_fail ssh_wrapper $2@$3 "echo -e \"# END Environment: $1 \" >> $env_file" +} + +# ----------------------------------------------------------- +# Append string to env.sh +# @param $1 ssh user +# @param $2 dns name of instance +# @param $3 string value +# @param $4 path to env.sh file +# ----------------------------------------------------------- +function append_to_env_sh(){ + local env_file=${4:-'/home/sailing/servers/server/env.sh'} + exit_on_fail ssh_wrapper $1@$2 "echo -e \"$3\" >> $env_file" +} + +# ----------------------------------------------------------- +# Reloads httpd service after executing apachectl configtest. +# @param $1 ssh user +# @param $2 dns name of instance +# ----------------------------------------------------------- +function reload_httpd(){ + local_echo "Reloading httpd..." + ssh_wrapper $1@$2 "apachectl configtest >/dev/null 2>&1" + out=$(ssh_wrapper $1@$2 "/etc/init.d/httpd reload") +} + + + +# ----------------------------------------------------------- +# Wait until 001_events.conf file appears on instance +# @param $1 ssh user +# @param $2 dns name +# ----------------------------------------------------------- +function wait_for_001_events_patch(){ + echo "Waiting for 001-events.conf to be created..." + wait_for_ssh_connection $1 $2 + do_until_true ssh_wrapper $1@$2 test -f $events_conf +} diff --git a/orchestration/bash/lib/functions_ec2.sh b/orchestration/bash/lib/functions_ec2.sh new file mode 100644 index 00000000000..1f23049c264 --- /dev/null +++ b/orchestration/bash/lib/functions_ec2.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Functions that are relevant for the configuration of the ec2 instance. +# ----------------------------------------------------------- + +# ----------------------------------------------------------- +# Get public dns name of instance +# @param $1 instance id +# @return public dns name +# ----------------------------------------------------------- +function get_public_dns_name(){ + local public_dns_name=$(aws_wrapper ec2 describe-instances --instance-ids $1 --output text --query 'Reservations[*].Instances[*].PublicDnsName') + exit_on_fail validate_hostname $public_dns_name +} + + +# ----------------------------------------------------------- +# Get resources all resources from aws +# @return json array of resourceARNs +# ----------------------------------------------------------- +function get_array_with_resource_of_type(){ + disable_aws_success_output + # RESOURCE_ARRAY=($(aws_wrapper resourcegroupstaggingapi get-resources --resource-type-filters elasticloadbalancing:loadbalancer ec2:instance ec2:image ec2:security-group acm:certificate | jq -c ".ResourceTagMappingList[] | select(.ResourceARN)" -r | sanitize)) + aws_wrapper resourcegroupstaggingapi get-resources --resource-type-filters $1 | jq -c ".ResourceTagMappingList[] | select(.ResourceARN)" -r | sanitize + enable_aws_success_output +} + +# ----------------------------------------------------------- +# Get launch templates as json +# param $1 public dns name +# @return json array of launch templates +# ----------------------------------------------------------- +function get_array_with_launch_templates(){ + disable_aws_success_output + aws_wrapper ec2 describe-launch-templates | jq -c ".LaunchTemplates[]" -r | sanitize + enable_aws_success_output +} + + +# ----------------------------------------------------------- +# Get tag value of specified key of instance +# @param1 instance id +# @param2 tag key +# @return tag value +# ----------------------------------------------------------- +function get_tag_value_for_key(){ + aws_wrapper ec2 describe-instances --query "Reservations[*].Instances[*].Tags[?Key=='$2'].Value" --instance-ids $1 --output text +} + +# ----------------------------------------------------------- +# Get default vpc id +# @return default vpc id +# ----------------------------------------------------------- +function get_default_vpc_id(){ + aws_wrapper ec2 describe-vpcs --query 'Vpcs[?IsDefault==`true`].VpcId' --output text +} + +# ----------------------------------------------------------- +# Query for instance id by public dns name +# param $1 public dns name +# @return instance id +# ----------------------------------------------------------- +function get_instance_id(){ + local_echo "Querying for the instance id of $1..." + local instance_id=$(aws_wrapper ec2 describe-instances --query "Reservations[*].Instances[?PublicDnsName=='$1'].InstanceId" --output text) + exit_on_fail validate_instance_id $instance_id +} + + +# ----------------------------------------------------------- +# Returns the instance id part of e.g. arn:aws:ec2:eu-west-2:017363970217:instance/i-096a32ca8c28bedbb +# param $1 resource arn +# @return id part of arn +# ----------------------------------------------------------- +function get_resource_id(){ + echo "${1##*/}" +} + +# ----------------------------------------------------------- +# Allocates an elastic ip address +# @return elastic ip +# ----------------------------------------------------------- +function allocate_address(){ + local_echo "Allocating elastic ip..." + aws_wrapper ec2 allocate-address | get_attribute '.PublicIp' +} + +# ----------------------------------------------------------- +# Associate an elastic ip address with an instance +# @param $1 instance_id +# @param $2 elastic_ip +# ----------------------------------------------------------- +function associate_address(){ + local_echo "Associating instance \"$1\" with elastic ip \"$2\"..." + aws_wrapper ec2 associate-address --instance-id $1 --public-ip $2 +} + +# ----------------------------------------------------------- +# Get all availability zones of region +# @return availability zones array +# ----------------------------------------------------------- +function get_availability_zones(){ + aws_wrapper ec2 describe-availability-zones --query "AvailabilityZones[].ZoneName" +} + +# ----------------------------------------------------------- +# Wait until instance is recognized by AWS +# @param $1 instance_id +# ----------------------------------------------------------- +function wait_instance_exists(){ + aws_wrapper ec2 wait instance-exists --instance-ids $1 &> /dev/null +} + +# ----------------------------------------------------------- +# Wait until ssh connection is established +# @param $1 ssh_user +# @param $2 public dns name +# ----------------------------------------------------------- +function wait_for_ssh_connection(){ + local_echo -n "Connecting to $1@$2..." + do_until_true ssh_prewrapper -q $1@$2 true 1>&2 +} + +# ----------------------------------------------------------- +# Runs instance with specified user data. All other variables user inside function have to be initialized already. +# @param $1 user data +# @return instance id +# ----------------------------------------------------------- +function run_instance(){ + local instance_id=$(aws_wrapper ec2 run-instances --image-id $(get_resource_id $image_id) --count 1 --instance-type $instance_type --key-name $key_name \ + --security-group-ids $(get_resource_id $instance_security_group_id) --user-data "$1" \ + --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$instance_name}]" | get_attribute '.Instances[0].InstanceId') + exit_on_fail validate_instance_id $instance_id +} + +# ----------------------------------------------------------- +# Runs instance from specified launch template. +# @param $1 launch template id +# @return instance id +# ----------------------------------------------------------- +function run_instance_from_launch_template(){ + local instance_id=$(aws_wrapper ec2 run-instances --launch-template LaunchTemplateId="$1" | get_attribute '.Instances[0].InstanceId') + exit_on_fail validate_instance_id $instance_id +} + +# ----------------------------------------------------------- +# Get user data of instance with specific instance id +# @param $1 instance id +# @return user data of instance +# ----------------------------------------------------------- +function get_user_data_from_instance() { + aws_wrapper ec2 describe-instance-attribute --instance-id $1 --attribute userData --output text --query "UserData.Value" | sanitize +} + +# ----------------------------------------------------------- +# Create instance and output response +# @param $1 user data +# ----------------------------------------------------------- +function create_instance(){ + local_echo -e "Creating instance with following specifications:\n\nRegion: $region\nName: $instance_name\nShort name: $instance_short_name\nType: $instance_type\nBuild: $build_version\n\nUser data:\n${1}\n" + instance_id=$(run_instance "$1") + wait_instance_exists $instance_id + null=$(print_instance_description $instance_id) + echo $instance_id +} + +# ----------------------------------------------------------- +# Prints instance description to screen. +# @param $1 instance id +# ----------------------------------------------------------- +function print_instance_description(){ + aws_wrapper ec2 describe-instances --instance-ids $1 \ + --query "Reservations[*].Instances[0].{InstanceId:InstanceId, ImageId:ImageId, Type:InstanceType, PublicDNS:PublicDnsName, KeyName:KeyName, PrivateDnsName:PrivateDnsName, PrivateIpAddress:PrivateIpAddress}"\ + --output table +} diff --git a/orchestration/bash/lib/functions_elb.sh b/orchestration/bash/lib/functions_elb.sh new file mode 100644 index 00000000000..29423753622 --- /dev/null +++ b/orchestration/bash/lib/functions_elb.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Functions that are relevant for the configuration of load balancers. +# ----------------------------------------------------------- + +# ----------------------------------------------------------- +# Create target group for load balancer +# @param $1 target group name +# @return json result +# ----------------------------------------------------------- +function create_target_group(){ + local_echo "Creating target group $1..." + local target_group_name=$(only_letters_numbers_dash $1) + local vpc_id=$(get_default_vpc_id) + aws_wrapper elbv2 create-target-group --name $target_group_name --protocol "HTTPS" \ + --port "443" --vpc-id $vpc_id | get_attribute '.TargetGroups[0].TargetGroupArn' +} + +# ----------------------------------------------------------- +# Sets health check of target group +# @param $1 target group arn +# @param $2 health check protocol (e.g. "HTTP") +# @param $3 health check path (e.g. "/index.html") +# @param $4 health check port (e.g. "443") +# @param $5 health check interval seconds (e.g. "5") +# @param $6 health check timeouts seconds (e.g. "4") +# @param $7 healthy threshold count (e.g. "2") +# @param $8 unhealthy threshold count (e.g. "2") +# ----------------------------------------------------------- +function set_target_group_health_check(){ + local_echo "Setting target group health check (protocol: $2, path: $3, port: $4, interval seconds: $5, timeout seconds: $6, unhealthy-count: $7, healthy-count: $8)..." + aws_wrapper elbv2 modify-target-group --target-group-arn $1 --health-check-protocol $2 --health-check-path $3 --health-check-port $4\ + --health-check-interval-seconds $5 --health-check-timeout-seconds $6 --healthy-threshold-count $7 --unhealthy-threshold-count $8 +} + +# ----------------------------------------------------------- +# Add tag to target group +# @param $1 target group arn +# @param $2 key (e.g. "Description") +# @param $2 value (e.g. "This is an instance for ...") +# ----------------------------------------------------------- +function set_target_group_tag(){ + aws_wrapper elbv2 add-tags --resource-arns $1 --tags "Key=$2,Value=$3" +} + +# ----------------------------------------------------------- +# Register instances within target group +# @param $1 target group arn +# @param $2 instance id +# ----------------------------------------------------------- +function register_targets(){ + local_echo "Register instance within target group..." + aws_wrapper elbv2 register-targets --target-group-arn $1 --targets Id=$2 +} + +# ----------------------------------------------------------- +# Returns first https listener of load balancer +# @param $1 load balancer arn +# @return https listener arn +# ----------------------------------------------------------- +function get_first_https_listener(){ + aws_wrapper elbv2 describe-listeners --load-balancer-arn $1 --query "Listeners[?Protocol=='HTTPS'].ListenerArn" --output text +} +# ----------------------------------------------------------- +# Create rule for listener +# @param $1 listener arn +# @param $2 sub domain +# @param $3 target group arn +# @return json result +# ----------------------------------------------------------- +function create_rule(){ + local_echo "Creating rule for listener..." + local subdomain=$(alphanumeric "$2") + local priority=$(($(get_rule_with_highest_priority $1) + 1)) + + # For testing purpose + if [ $region == "eu-west-2" ]; then + local domain="$subdomain.dummy2.sapsailing.com" + else + local domain="$subdomain.sapsailing.com" + fi + + rule=$(aws_wrapper elbv2 create-rule --listener-arn $1 --priority $priority \ + --conditions Field=host-header,Values=$domain --actions Type=forward,TargetGroupArn=$3) + echo $domain +} + +# ----------------------------------------------------------- +# Get rule with highest priority out of listener +# @param $1 listener arn +# @return highest priority +# ----------------------------------------------------------- +function get_rule_with_highest_priority(){ + local max_priority=$(aws --region=$region elbv2 describe-rules --listener-arn $1 --query "Rules[*].{P:Priority}" --output text | sanitize | sort -n | tail -1) + if is_number $max_priority; then + echo $max_priority + else + echo 0 + fi +} diff --git a/orchestration/bash/lib/functions_io.sh b/orchestration/bash/lib/functions_io.sh new file mode 100644 index 00000000000..7eeb0f26d96 --- /dev/null +++ b/orchestration/bash/lib/functions_io.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Functions that are relevant for the treatment of user input and user configuration. +# ----------------------------------------------------------- + +# ----------------------------------------------------------- +# Prompt user for input and save value to variable +# @param $1 prompt message +# @param $2 default value +# @param $3 variable that is receiving the value +# @param $4 hide text +# ----------------------------------------------------------- +function require_input(){ + if [ ${#RESOURCE_MAP[@]} -eq 0 ]; then + if [[ ( $NUMBER_OF_TAGGED_RESOURCES -eq 1 || ${#OPTION_KEYS[@]} -eq 1 ) && "$force" == "true" ]]; then + local_echo "$1" + if [ ! -z "$DEFAULT_TAGGED_RESOURCE" ]; then + read -r $3 <<< $DEFAULT_TAGGED_RESOURCE + else + read -r $3 <<< ${OPTION_KEYS[0]} + fi + elif [ ${#OPTION_KEYS[@]} -gt 0 ]; then + local_echo "$1" + selectedValue="" + select option in ${OPTION_VALUES[@]}; do + selectedValue=$option + break + done + # workaround cant access option_keys from within select + read -r $3 <<< $(echo $selectedValue | sed 's/.*(\(.*\)).*/\1/') + + else + read_input_normal "$1" "$2" $3 $4 + fi + + else + read_input_normal "$1" "$2" $3 $4 + fi + + unset OPTION_KEYS + unset OPTION_VALUES + unset RESOURCE_MAP +} + +function read_input_normal(){ + # if hide input + if [ "$4" == "true" ]; then + read -e -s -p "$1" -i "$2" $3 + # print new line after read -s + echo + else + read -e -p "$1" -i "$2" $3 + fi +} + + +# ----------------------------------------------------------- +# If the needed variable was not passed as a parameter and is not +# already set, then prompt the user to input a value and also +# show a default value. If the variable was passed by using a parameter, +# then set its value to the parameter value. +# @param $1 parameter value +# @param $2 variable +# @param $3 default value +# @param $4 prompt message +# @param $5 true if optional +# @param $6 true if text input be hidden +# @param $7 filter +# ----------------------------------------------------------- +function require_variable(){ + # if parameter is empty + if [ -z "$1" ]; then + # if required variable value is empty + if [ -z "${!2}" ]; then + # if force is enabled + if [ "$force" = true ] && [ ${#RESOURCE_MAP[@]} -eq 0 ]; then + # if variable is optional + if [ "$5" == "true" ]; then + # variable should be empty + read -r "$2" <<< "" + return 0 + else + # read default value into required variable + read -r "$2" <<< "$3" + return 0 + fi + fi + # if force not enabled or filter is set + # if variable value is optional, then value of "" is possible + if [ "$5" == "true" ]; then + require_input "$4" "$3" $2 $6 + else + # if variable value is not optional, repeat until value is set for variable + while [[ -z "${!2}" ]] + do + require_input "$4" "$3" $2 $6 + done + fi + fi + else + # if parameter is not empty, read its value into required variable + read -r "$2" <<< "$1" + return 0 + fi +} + + +function fill_resource_map_with_resources_of_type(){ + declare -a RESOURCE_ARRAY + + if [ "$1" == "ec2:launch-template" ]; then + RESOURCE_ARRAY=($(get_array_with_launch_templates)) + else + RESOURCE_ARRAY=($(get_array_with_resource_of_type $1)) + fi + + NUMBER_OF_TAGGED_RESOURCES=0 + DEFAULT_TAGGED_RESOURCE="" + local o=0 + declare -A RESOURCE_MAP + + for resource in "${RESOURCE_ARRAY[@]}"; do + local id="" + + if [ "$1" == "ec2:launch-template" ]; then + id=$(echo "$resource" | jq -c ".LaunchTemplateId" -r | sanitize) + tmp_name=$(echo "$resource" | jq -c ".LaunchTemplateName" -r | sanitize) + else + id=$(echo $resource | jq -c ".ResourceARN" -r | sanitize) + tmp_name=$(echo $resource | jq -c ".Tags[] | select(.Key==\"Name\") | .Value" -r | sanitize) + fi + + local name=${tmp_name:-"Unnamed"} + local tagged=$(echo $resource | jq -c ".Tags[] | select(.Key==\"AutoDiscover\") | .Value" -r | sanitize) + local display_name="$name ($id)" + + + OPTION_KEYS[$o]=$id + + if [ ! -z "$tagged" ] && [ "$tagged" == "true" ] || [ "$tagged" == "True" ]; then + DEFAULT_TAGGED_RESOURCE=$id + option_value=$(highlight $display_name) + ((NUMBER_OF_TAGGED_RESOURCES++)) + else + tagged="false" + option_value=$display_name + fi + + OPTION_VALUES[$o]=$option_value + ((o++)) + + RESOURCE_MAP[$id]="$name,$tagged" + done +} + +function highlight(){ + local highlight_start=$(tput setaf 3) + local reset=$(tput sgr0) + echo $highlight_start$1$reset +} + +# ----------------------------------------------------------- +# Workaround: use stderr stream for console output +# @param $1 message +# ----------------------------------------------------------- +function local_echo(){ + echo "$@" >&2 +} + +function init_resources(){ + if ! is_exists ~/.aws-automation/config-$region; then + local_echo "Creating ~/.aws-automation/config-$region..." + create_region_configuration + fi + + if ! is_exists ~/.aws-automation/config; then + local_echo "Creating ~/.aws-automation/config..." + create_global_configuration + fi +} + +function create_region_configuration(){ + mkdir -p ~/.aws-automation + touch ~/.aws-automation/config-$region + + local config="" + config+="default_instance_type=\n" + config+="default_ssh_user=\n" + config+="default_key_name=\n" + config+="default_key_file=\n" + config+="default_mongodb_host=\n" + config+="default_mongodb_port=\n" + config+="default_new_admin_password=\n" + + echo -e $config > ~/.aws-automation/config-$region +} + +function create_global_configuration(){ + mkdir -p ~/.aws-automation + touch ~/.aws-automation/config + + local config="" + config+="default_region=\n" + config+="default_server_startup_notify=\n" + config+="default_build_complete_notify=\n" + + + echo -e $config > ~/.aws-automation/config +} + +function set_config_variable(){ + ./lib/build-config.sh --bash ~/.aws-automation/config-$region "$1" "$2" +} + +function get_config_variable(){ + ./lib/build-config.sh --bash ~/.aws-automation/config-$region "$1" +} diff --git a/orchestration/bash/lib/functions_wrapper.sh b/orchestration/bash/lib/functions_wrapper.sh new file mode 100644 index 00000000000..3378313d4ea --- /dev/null +++ b/orchestration/bash/lib/functions_wrapper.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Functions that encapsulate error checking or looping logic whilst calling other functions. +# ----------------------------------------------------------- + +DISPLAY_SUCCESS_FOR_THIS_COMMAND="true" +HTTP_RETRY_INTERVAL=5 +TIMEOUT=300 + +# ----------------------------------------------------------- +# Does the error checking for curl commands. +# Captures response and http code of curl command into variable $out. +# Checks if http code is 200. +# If yes print success and return response. +# If not then print error. +# @return latest build +# ----------------------------------------------------------- +function curl_wrapper(){ + local out; + out=$(curl -qSfsw '\n%{http_code}' "$@") + #local return_code=$? + local command=$(echo "$@" | tr '\n' ' ') + local status_code=$(get_status_code "$out") + local response=$(get_response "$out") + local message=$(get_http_code_message $status_code) + + if is_http_ok $status_code; then + success ${response:-"[ OK ]"} + echo $response + else + error "[ ERROR ]" + notice "Function (${FUNCNAME[1]}): [$status_code] $message $response\n\n$command" + return 1 + fi +} + +# ----------------------------------------------------------- +# Does the error checking for aws commands. It also remove all +# carriage return symbols from response. +# ----------------------------------------------------------- +function aws_wrapper(){ + local out; + out=$(aws --region $region "$@" 2>&1) + if command_was_successful $?; then + if [ "$DISPLAY_SUCCESS_FOR_THIS_COMMAND" != "false" ]; then + success ${out:-"[ OK ]"} + fi + echo $out | sanitize + else + error "[ ERROR ] $out" + safeExit + fi +} + +function disable_aws_success_output(){ + DISPLAY_SUCCESS_FOR_THIS_COMMAND="false" +} + +function enable_aws_success_output(){ + DISPLAY_SUCCESS_FOR_THIS_COMMAND="true" +} + +function ssh_prewrapper(){ + if [ -z $key_file ]; then + ssh -o StrictHostKeyChecking=no "$@" + else + ssh -o StrictHostKeyChecking=no -i $key_file "$@" + fi +} + +# ----------------------------------------------------------- +# Automatically uses key file for ssh if possible. +# ----------------------------------------------------------- +function ssh_wrapper(){ + out=$(ssh_prewrapper "$@") + if command_was_successful $?; then + success ${out:-"[ OK ]"} + echo $out + else + if ssh -o StrictHostKeyChecking=no "$@"; then + echo $out + return 0 + fi + error "[ ERROR ] $out" + return 1 + fi +} + +# ----------------------------------------------------------- +# Executes curl command until status code is 200 +# ----------------------------------------------------------- +function curl_until_response(){ + response_code=$1 + shift + start_time="$(date -u +%s)" + while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $HTTP_RETRY_INTERVAL "$@") != "$response_code" ]]; + do + fail_on_timeout $start_time + + local_echo -n "." + sleep $HTTP_RETRY_INTERVAL; + done + local_echo "" +} + +function do_until_true(){ + start_time="$(date -u +%s)" + until "$@" + do + fail_on_timeout $start_time + + echo -n . + sleep $HTTP_RETRY_INTERVAL + done +} + +function fail_on_timeout(){ + end_time="$(date -u +%s)" + if [ "$(($end_time-$1))" -gt $TIMEOUT ]; then + error "TIMEOUT" + safeExit + fi +} + +function exit_on_fail(){ + "$@" || { safeExit; } +} diff --git a/orchestration/bash/lib/require_variables.sh b/orchestration/bash/lib/require_variables.sh new file mode 100644 index 00000000000..2788bbc2962 --- /dev/null +++ b/orchestration/bash/lib/require_variables.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Define variable properties and user messages here. +# ----------------------------------------------------------- + + +# ----------------------------------------------------------- +# Messages for user input. +# ----------------------------------------------------------- + +image_ask_message="Please select an image to use: " +instance_ask_message="Please enter an instance to use: " +event_name_ask_message="Please enter an event name (leave blank to skip event creation): " +instance_security_group_ask_message="Please select the security group for the instance: " +load_balancer_ask_message="Please select or enter the load balancer dns name: " +region_ask_message="Please enter the region you want to use (e.g. eu-west-2): " +instance_type_ask_message="Please enter the instance type (e.g. t2.medium): " +key_name_ask_message="Please enter the name of your keypair to user for the instance (e.g. leon-keypair). " +instance_name_ask_message="Please enter a name for the instance: (e.g \"WC Santander 2017\"): " +instance_short_name_ask_message="Please enter a short name for the instance (e.g. \"wcs17\"): " +key_file_ask_message="Please enter the file path of the keypair or leave empty to use ~/.ssh key: " +new_admin_password_ask_message="Please enter a new password for the admin user (leave blank to skip password change): " +mongodb_host_ask_message="Please enter the ip adress/dns of the mongo db: " +mongodb_port_ask_message="Please enter the port of the mongo db: " +user_username_ask_message="Please enter the username of your new user (leave blank to skip user creation): " +user_password_ask_message="Please enter the password of your new user (leave blank to skip user creation): " +ssh_user_ask_message="Please enter the ssh user to connect to the instance: " +super_instance_message="Please select/enter the dns name of the superior instance: " +description_message="Please enter a description for the server (leave blank to skip): " +contact_person_message="Please enter the name of a contact person (leave blank to skip): " +contact_email_message="Please enter the email of the contact person (leave blank to skip): " +build_version_message="Please enter a build version to use (releases.sapsailing.com): " +launch_tempate_ask_message="Please select the launch template for the replica: " + +IS_OPTIONAL="true" +IS_NOT_OPTIONAL="false" +SHOW_INPUT="false" +HIDE_INPUT="true" +NO_DEFAULT_VALUE="" +NOT_A_PARAMETER="" + + +# ----------------------------------------------------------- +# Functions that are responsible for: Connection of variable to respective use rmessage. Setting if variable is required or can be left empty. +# Set the variables default value. Choose if user input will be shown or hidden (e.g. passwords). Choose whether the variable is a aws resources +# and can be selected. If yes then append a regex as last parameter (e.g. ValidImageARNRegex). Then the selection will be limited to resources +# of the associative array RESOURCE_MAP (functions_io.sh) that have a key matching the regex. +# ----------------------------------------------------------- + +function require_launch_template(){ + fill_resource_map_with_resources_of_type "ec2:launch-template" + require_variable "$launch_template_param" launch_template "$NO_DEFAULT_VALUE" "$launch_tempate_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_image(){ + fill_resource_map_with_resources_of_type "ec2:image" + require_variable "$image_param" image "$NO_DEFAULT_VALUE" "$image_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_event_name(){ + require_variable "$event_name_param" event_name "$NO_DEFAULT_VALUE" "$event_name_ask_message" "$IS_OPTIONAL" "$SHOW_INPUT" +} + +function require_instance_security_group(){ + fill_resource_map_with_resources_of_type "ec2:security-group" + require_variable "$security_group_param" instance_security_group "$NO_DEFAULT_VALUE" "$instance_security_group_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_load_balancer(){ + fill_resource_map_with_resources_of_type "elasticloadbalancing:loadbalancer" + require_variable "$load_balancer_param" load_balancer "$NO_DEFAULT_VALUE" "$load_balancer_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" $ValidLoadBalancerARNRegex +} + +function require_instance(){ + fill_resource_map_with_resources_of_type "ec2:instance" + require_variable "$instance_param" instance "$NO_DEFAULT_VALUE" "$instance_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_region(){ + require_variable "$region_param" region "$default_region" "$region_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_instance_type(){ + require_variable "$instance_type_param" instance_type "$default_instance_type" "$instance_type_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" + +} +function require_instance_name(){ + require_variable "$instance_name_param" instance_name "$script_start_time" "$instance_name_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_instance_short_name(){ + require_variable "$instance_short_name_param" instance_short_name "$timestamp" "$instance_short_name_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_key_name(){ + require_variable "$key_name_param" key_name "$default_key_name" "$key_name_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_key_file(){ + require_variable "$key_file_param" key_file "$default_key_file" "$key_file_ask_message" "$IS_OPTIONAL" "$SHOW_INPUT" +} + +function require_new_admin_password(){ + require_variable "$new_admin_password_param" new_admin_password "$default_new_admin_password" "$new_admin_password_ask_message" "$IS_OPTIONAL" "$HIDE_INPUT" +} + +function require_user_username(){ + require_variable "$user_username_param" user_username "$default_user_username" "$user_username_ask_message" "$IS_OPTIONAL" "$SHOW_INPUT" + +} + +function require_mongodb_port(){ + require_variable "$mongodb_port_param" mongodb_port "$default_mongodb_port" "$mongodb_port_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_mongodb_host(){ + require_variable "$mongodb_host_param" mongodb_host "$default_mongodb_host" "$mongodb_host_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_user_password(){ + require_variable "$user_password_param" user_password "$default_user_password" "$user_password_ask_message" "$IS_OPTIONAL" "$HIDE_INPUT" +} + +function require_ssh_user(){ + require_variable "$ssh_user_param" ssh_user "root" "$ssh_user_ask_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_super_instance(){ + fill_resource_map_with_resources_of_type "ec2:instance" + require_variable "$super_instance_param" super_instance "$NO_DEFAULT_VALUE" "$super_instance_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} + +function require_description(){ + require_variable "$description_param" description "" "$description_message" "$IS_OPTIONAL" "$SHOW_INPUT" +} + +function require_contact_person(){ + require_variable "$contact_person_param" contact_person "$default_contact_person" "$contact_person_message" "$IS_OPTIONAL" "$SHOW_INPUT" +} + +function require_contact_email(){ + require_variable "$contact_email_param" contact_email "$default_contact_email" "$contact_email_message" "$IS_OPTIONAL" "$SHOW_INPUT" +} + +function require_build_version(){ + require_variable "$build_version_param" build_version "$latest_release" "$build_version_message" "$IS_NOT_OPTIONAL" "$SHOW_INPUT" +} diff --git a/orchestration/bash/lib/scenario_associate_alb.sh b/orchestration/bash/lib/scenario_associate_alb.sh new file mode 100644 index 00000000000..7a33dc4875f --- /dev/null +++ b/orchestration/bash/lib/scenario_associate_alb.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Associates a chosen instance with a chosen application load balancer. +# +# Steps: +# Creates target group for instance. +# Configures health check of target group. +# Creates rule inside https listener of application load balancer. +# Registers target within target group. +# Configures apache of ec2 instance to revere proxy. +# ----------------------------------------------------------- + + +function associate_alb_start(){ + associate_alb_require + associate_alb_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function associate_alb_require(){ + require_instance + require_instance_short_name + require_load_balancer + require_key_file + require_ssh_user +} + +function associate_alb_execute() { + + disable_aws_success_output + instance_id=$(get_resource_id $instance) + public_dns_name=$(get_public_dns_name $instance_id) + enable_aws_success_output + + # create target group for instance + local target_group_arn=$(exit_on_fail create_target_group "S-dedicated-$instance_short_name") + exit_on_fail set_target_group_health_check "$target_group_arn" "HTTP" "/index.html" "80" "5" "4" "2" "2" + + listener_arn=$(exit_on_fail get_first_https_listener $load_balancer) + exit_on_fail register_targets $target_group_arn $instance_id + + # create rule for host-based forwarding within alb + local domain=$(exit_on_fail create_rule $listener_arn $instance_short_name $target_group_arn) + + header "Apache configuration" + + # configure apache with event macro + exit_on_fail append_macro_to_001_events_conf $domain "" $ssh_user $public_dns_name "8888" + exit_on_fail reload_httpd $ssh_user $public_dns_name +} diff --git a/orchestration/bash/lib/scenario_instance.sh b/orchestration/bash/lib/scenario_instance.sh new file mode 100644 index 00000000000..31fb463be21 --- /dev/null +++ b/orchestration/bash/lib/scenario_instance.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +# Scenario for creating an ec2 instance +# +# Steps: +# Creates ec2 instance +# ------------------------------------------------------ + +function instance_start(){ + instance_require + instance_preview + instance_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function instance_require(){ + require_region + require_load_balancer + require_instance_type + require_instance_security_group + require_image + require_instance_name + require_instance_short_name + require_mongodb_host + require_mongodb_port + require_ssh_user + require_build_version + require_key_name + require_key_file + require_event_name + require_new_admin_password +} + +function instance_preview(){ + header "Preview" + image_id=$(get_resource_id $image) + instance_security_group_id=$(get_resource_id $instance_security_group) + user_data=$(build_user_data) + local_echo "An instance will be created with the following specifications: " + local_echo "To be implemented..." + seek_confirmation "Do you want to continue?" + if is_confirmed; then + : + else + safeExit + fi +} + +# ----------------------------------------------------------- +# Execute instance scenario +# @param $1 user data +# ----------------------------------------------------------- +function instance_execute() { + + header "Instance Initialization" + + instance_id=$(create_instance "$user_data") + public_dns_name=$(get_public_dns_name $instance_id) + wait_for_ssh_connection $ssh_user $public_dns_name + + header "Event and user creation" + + local port="8888" + event_id=$(configure_application "$public_dns_name" "$port" "$event_name" "$new_admin_password" "$user_username" "$user_password") + + header "Configuring ALB" + + listener_arn=$(get_first_https_listener $load_balancer) + local target_group_arn=$(exit_on_fail create_target_group "S-dedicated-$instance_short_name") + set_target_group_health_check "$target_group_arn" "HTTPS" "/index.html" "443" "5" "4" "2" "2" + + register_targets $target_group_arn $instance_id + local domain=$(create_rule $listener_arn $instance_short_name $target_group_arn) + + header "Configuring Apache" + + append_macro_to_001_events_conf "$domain" "$event_id" "root" "$public_dns_name" "$port" + + header "Conclusion" + + success "Instance should be reachable through $public_dns_name:8888." +} + +function build_user_data(){ + build_configuration "MONGODB_HOST=$mongodb_host" "MONGODB_PORT=$mongodb_port" "MONGODB_NAME=$(alphanumeric $instance_name)" \ + "REPLICATION_CHANNEL=$(alphanumeric $instance_name)" "SERVER_NAME=$(alphanumeric $instance_name)" "USE_ENVIRONMENT=live-server" \ + "INSTALL_FROM_RELEASE=$build_version" "SERVER_STARTUP_NOTIFY=$default_server_startup_notify" +} diff --git a/orchestration/bash/lib/scenario_master_instance.sh b/orchestration/bash/lib/scenario_master_instance.sh new file mode 100644 index 00000000000..a0d0774decd --- /dev/null +++ b/orchestration/bash/lib/scenario_master_instance.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +#!/usr/bin/env bash + +# Scenario for creating an instance +# ------------------------------------------------------ + +function master_instance_start(){ + master_instance_require + master_instance_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function master_instance_require(){ + require_region + require_instance_type + require_image + require_instance_name + require_instance_short_name + require_mongodb_host + require_mongodb_port + require_instance_security_group + require_ssh_user + require_build_version + require_key_name + require_key_file + require_new_admin_password +} + +# ----------------------------------------------------------- +# Execute instance scenario +# @param $1 user data +# ----------------------------------------------------------- +function master_instance_execute() { + header "Master Instance Initialization" + event_name=$instance_name + image_id=$(get_resource_id $image) + instance_security_group_id=$(get_resource_id $instance_security_group) + + local user_data_master=$(build_configuration "MONGODB_HOST=$mongodb_host" "MONGODB_PORT=$mongodb_port" "MONGODB_NAME=$(alphanumeric $instance_name)" \ + "REPLICATION_CHANNEL=$(alphanumeric $instance_name)" "SERVER_NAME=$(alphanumeric $instance_name)" "USE_ENVIRONMENT=live-master-server" \ + "INSTALL_FROM_RELEASE=$build_version" "SERVER_STARTUP_NOTIFY=$default_server_startup_notify") + + instance_id=$(exit_on_fail create_instance "$user_data_master") + public_dns_name=$(get_public_dns_name $instance_id) + + wait_for_ssh_connection $ssh_user $public_dns_name + + header "Event and user creation" + + port="8888" + event_id=$(configure_application $public_dns_name $port $event_name $new_admin_password $user_username $user_password) + + local user_data_replica=$(build_configuration "MONGODB_HOST=$mongodb_host" "MONGODB_PORT=$mongodb_port" "MONGODB_NAME=$(alphanumeric $instance_name)-replica" \ + "REPLICATION_CHANNEL=$(alphanumeric $instance_name)" "SERVER_NAME=$(alphanumeric $instance_name)" "USE_ENVIRONMENT=live-replica-server" \ + "REPLICATE_MASTER_SERVLET_HOST=$public_dns_name" "REPLICATE_MASTER_EXCHANGE_NAME=$(alphanumeric $instance_name)" "EVENT_ID=$event_id" "INSTALL_FROM_RELEASE=$build_version" "SERVER_STARTUP_NOTIFY=$default_server_startup_notify") + + local base64_user_data=$(echo "$user_data_replica" | base64 -w 0) + local launch_template_for_replica=$(printf '{"UserData":"%s","ImageId":"%s","InstanceType":"%s","TagSpecifications":[{"ResourceType":"instance","Tags":[{"Key":"Name","Value":"%s"}]}], "SecurityGroupIds": ["%s"], "KeyName": "%s"}'\ + "${base64_user_data}" "$image_id" "$instance_type" "$instance_name (Replica)" "$instance_security_group_id" "$key_name") + + aws_wrapper ec2 create-launch-template --launch-template-name "Replica_$instance_short_name" --version-description "Replica for $instance_name." --launch-template-data $launch_template_for_replica + +} diff --git a/orchestration/bash/lib/scenario_replica_instance.sh b/orchestration/bash/lib/scenario_replica_instance.sh new file mode 100644 index 00000000000..9e5468dd2a9 --- /dev/null +++ b/orchestration/bash/lib/scenario_replica_instance.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +#!/usr/bin/env bash + +# Scenario for creating an instance +# ------------------------------------------------------ + +function replica_instance_start(){ + replica_instance_require + replica_instance_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function replica_instance_require(){ + require_region + require_launch_template + require_ssh_user + require_key_file +} + +# ----------------------------------------------------------- +# Execute instance scenario +# @param $1 user data +# ----------------------------------------------------------- +function replica_instance_execute() { + + header "Replica Instance Initialization" + + instance_id=$(run_instance_from_launch_template $launch_template) + wait_instance_exists $instance_id + public_dns_name=$(get_public_dns_name $instance_id) + user_data=$(get_user_data_from_instance $instance_id | base64 --decode) + echo "$user_data" + + header "Conclusion" + + success "Instance should be reachable through $public_dns_name:8888." +} diff --git a/orchestration/bash/lib/scenario_shared_instance.sh b/orchestration/bash/lib/scenario_shared_instance.sh new file mode 100644 index 00000000000..9649c518059 --- /dev/null +++ b/orchestration/bash/lib/scenario_shared_instance.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash + +# Scenario for creating a shared instance +# ------------------------------------------------------ + +function shared_instance_start(){ + shared_instance_require + shared_instance_check_preconditions + shared_instance_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function shared_instance_require(){ + require_super_instance + require_load_balancer + + require_instance_name + require_instance_short_name + require_mongodb_host + require_mongodb_port + require_build_version + require_key_file + require_event_name + require_new_admin_password + require_ssh_user + require_contact_person + require_contact_email + require_description + + disable_aws_success_output + local instance_id=$(get_resource_id $super_instance) + super_instance=$(get_public_dns_name $instance_id) + enable_aws_success_output +} + +function shared_instance_check_preconditions(){ + sailing_dir="/home/sailing" + servers_dir="$sailing_dir/servers" + server_dir="$servers_dir/$instance_short_name" + + header "Checking preconditions" + + local instance_id=$(get_instance_id $super_instance) + + # echo "Checking if instance has tag: 'ssh_user'." + # ssh_user=$(exit_on_fail get_tag_value_for_key $instance_id "ssh_user") + + echo "Checking if directory $server_dir exists already..." + exit_on_fail execute_remote "[ ! -d $server_dir ]" + + echo "Checking if ssh connection $ssh_user@$super_instance is working..." + exit_on_fail execute_remote ls + + # echo "Checking if super instance has user data.\ + # The instance should not have any user data set, otherwise the refreshInstance script will not work properly." + # exit_on_fail [ $(get_user_data_from_instance $instance_id) == "None" ] +} + +function shared_instance_execute() { + local server_env_file="$server_dir/env.sh" + local readme_file="$servers_dir/README" + local comment_out_line_in_env_with_pattern='JAVA_HOME=\/opt\/jdk1.8.0_20' + local comment_in_line_in_env_with_pattern='sapjvm_gc' + local refreshInstance_file='/home/sailing/code/java/target/refreshInstance.sh' + + header "Sub instance setup" + + # create sub instance server directory + local_echo "Creating directory $server_dir ..." + exit_on_fail execute_remote mkdir $server_dir + + local_echo "Getting next unused SERVER_PORT..." + local server_port=$(find_first_unused_port "SERVER_PORT" "8888") + + local_echo "Getting next unused TELNET_PORT..." + local telnet_port=$(find_first_unused_port "TELNET_PORT" "14900") + + local_echo "Getting next unused EXPEDITION_PORT..." + local expedition_port=$(find_first_unused_port "EXPEDITION_PORT" "2000") + + # copy refreshInstance.sh from /servers/server to sub instance directory + local_echo "Copying $refreshInstance_file to $server_dir..." + exit_on_fail execute_remote cp $refreshInstance_file $server_dir + + # Execute refreshInstance.sh + local_echo "Executing refreshInstance.sh with build version $build_version ..." + exit_on_fail execute_remote "set -o pipefail;export DEPLOY_TO=$instance_short_name;cd $server_dir;./refreshInstance.sh install-release $build_version > /dev/null 2>&1;" + + # uncommenting lines containing pattern + local_echo "Commenting in $comment_in_line_in_env_with_pattern inside $server_env_file..." + execute_remote "sed -i '/$comment_in_line_in_env_with_pattern/s/^#//g' $server_env_file" + + # commenting out lines containing pattern + local_echo "Commenting out $comment_out_line_in_env_with_pattern inside $server_env_file..." + execute_remote "sed -i '/$comment_out_line_in_env_with_pattern/s/^/#/g' $server_env_file" + + local MEMORY="10000m" + exit_on_fail execute_remote "echo -e \"MEMORY=$MEMORY\" >> $server_env_file" + + append_environment_to_env_sh "live-server" $ssh_user $super_instance $server_env_file $env_file + + local env_patch=$(build_configuration "# START Script $script_start_time" "SERVER_NAME=$(alphanumeric $instance_name)" "TELNET_PORT=$telnet_port" \ + "SERVER_PORT=$server_port" "EXPEDITION_PORT=$expedition_port" "MONGODB_NAME=$(alphanumeric $instance_name)" "MONGODB_HOST=$default_mongodb_host" \ + "MONGODB_PORT=$default_mongodb_port" "DEPLOY_TO=$instance_short_name" "SERVER_STARTUP_NOTIFY=${default_server_startup_notify:-" "}" \ + "BUILD_COMPLETE_NOTIFY=${default_build_complete_notifiy:-" "}" "# END Script $script_start_time") + + exit_on_fail execute_remote "echo -e \"$env_patch\" >> $server_env_file" + + + + local_echo "Creating README file if it does not exist already..." + execute_remote touch $readme_file + + local_echo "Appending patch to README file..." + execute_remote "echo -e \"\n# $instance_short_name (${description:-"Unknown"}, ${contact_person:-"Unknown"},${contact_email:-"Unknown"})\n$env_patch\" >> $readme_file" + + # start server and redirect both stderr and stdout to /dev/null (&>/dev/null). Send command to background by &. Do this to avoid blocking. + local_echo "Starting server..." + exit_on_fail execute_remote -f "set -o pipefail;sh -c \"cd $server_dir; nohup ./start > /dev/null 2>&1 &\"" + + header "Event and user creation" + + event_id=$(configure_application $super_instance $server_port $event_name $new_admin_password $user_username $user_password) + + header "Configuring ALB" + + listener_arn=$(get_first_https_listener $load_balancer) + local target_group_arn=$(exit_on_fail create_target_group "S-shared-$instance_short_name") + set_target_group_health_check "$target_group_arn" "HTTP" "/index.html" "$server_port" "5" "4" "2" "2" + + register_targets $target_group_arn $(get_instance_id $super_instance) + + local domain=$(create_rule $listener_arn $instance_short_name $target_group_arn) + + header "Configuring Apache" + + execute_remote_root "echo -e \"\n# $instance_short_name (${description:-"Unknown"}, ${contact_person:-"Unknown"}, ${contact_email:-"Unknown"})\" >> $events_conf" + append_macro_to_001_events_conf "$domain" "$event_id" "root" "$super_instance" "$server_port" + reload_httpd "root" $super_instance + header "Conclusion" + + success "Sub instance should be reachable through https://$domain." +} + +# ----------------------------------------------------------- +# For a given variable name, find first number that is not already used within all env.sh files inside /home/sailing/servers. +# If no number is found, return $2 +# @param $1 variable name (e.g. SERVER_PORT) +# @param $2 default number (e.g. 8880) +# ----------------------------------------------------------- +function find_first_unused_port(){ + last_port=($(get_last_used_port "$1")) + if [ $? -ne 0 ]; then + port_to_use=$2 + else + port_to_use=$(($last_port+1)) + fi + success $port_to_use + echo $port_to_use +} + +function execute_remote(){ + ssh_wrapper $ssh_user@$super_instance "$@" +} + +function execute_remote_root(){ + ssh_wrapper "root"@$super_instance "$@" +} + +function get_last_used_port(){ + ssh_prewrapper $ssh_user@$super_instance "set -o pipefail; for i in /home/sailing/servers/*/env.sh; do cat \$i | grep '^ *$1=' | tr -d '$1='; done | sort -n | tail -1" +} diff --git a/orchestration/bash/lib/util_functions.sh b/orchestration/bash/lib/util_functions.sh new file mode 100644 index 00000000000..91d1cf6c944 --- /dev/null +++ b/orchestration/bash/lib/util_functions.sh @@ -0,0 +1,423 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------- +# Appends multiple parameters (e.g. "SERVER_NAME=Test") +# Also includes parameters starting with "#" as comments. +# @param $@ variable key and value (e.g. "MONGODB_HOST=123.123.123.123") +# ----------------------------------------------------------- +function build_configuration(){ + for var in "$@" + do + # if parameters starts with #, add parameters to content + if [[ $var == \#* ]]; then + content+="$var\n" + continue + fi + + key=${var%=*} + value=${var#*=} + + if [[ ! -z "$key" && ! -z "$value" ]]; then + content+="$key=$value\n" + fi + done + echo -e $content +} + +function command_was_successful(){ + [ $1 -eq 0 ] +} + +# ----------------------------------------------------------- +# Uncomments a line starting with a specific pattern +# @param $1 pattern +# @param $2 file +# ----------------------------------------------------------- +function uncomment_line_starting_with(){ + sed -i '/$1/s/^#//g' $2 +} + + +# ----------------------------------------------------------- +# Checks if variable is a number +# @param $1 returnvariablevalue +# @return 0 if variable is a number +# ----------------------------------------------------------- +function is_number(){ + [[ $1 =~ ^-?[0-9]+$ ]] +} + + + +# ----------------------------------------------------------- +# Check if variable is a number and its value is 200 +# @param $1 variable +# @return 0 if value is 200 +# ----------------------------------------------------------- +function is_http_ok(){ + is_number $1 && [ $1 == 200 ] +} + +function get_response(){ + echo "$1" | head -n-1 +} + +function get_status_code(){ + echo "$1" | tail -n1 +} + +function get_attribute(){ + jq -r $1 | sanitize +} + +function sanitize(){ + tr -d '\r' +} + +function alphanumeric(){ + lower_trim $1 | only_letters_and_numbers +} + +function only_letters_numbers_dash(){ + echo $1 | trim | tr -d -c '[:alnum:]-' +} + + +# ------------------------------------------------------ +# The following functions were part of a bash template +# and could be useful in the future +# ------------------------------------------------------ + +# Traps +# ------------------------------------------------------ +# These functions are for use with different trap scenarios +# ------------------------------------------------------ + +# Non destructive exit for when script exits naturally. +# Usage: Add this function at the end of every script +function safeExit() { + # Delete temp files, if any + if is_dir "${tmpDir}"; then + rm -r "${tmpDir}" + fi + kill -WINCH $$ + trap - INT TERM EXIT + exit +} + +# readFile +# ------------------------------------------------------ +# Function to read a line from a file. +# +# Most often used to read the config files saved in my etc directory. +# Outputs each line in a variable named $result +# ------------------------------------------------------ +function readFile() { + unset "${result}" + while read result + do + echo "${result}" + done < "$1" +} + + + +# Escape a string +# ------------------------------------------------------ +# usage: var=$(escape "String") +# ------------------------------------------------------ +escape() { echo "${@}" | sed 's/[]\.|$(){}?+*^]/\\&/g'; } + +# needSudo +# ------------------------------------------------------ +# If a script needs sudo access, call this function which +# requests sudo access and then keeps it alive. +# ------------------------------------------------------ +function needSudo() { + # Update existing sudo time stamp if set, otherwise do nothing. + sudo -v + while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & +} + +# convertsecs +# ------------------------------------------------------ +# Convert Seconds to human readable time +# +# To use this, pass a number (seconds) into the function as this: +# print "$(convertsecs $TOTALTIME)" +# +# To compute the time it takes a script to run use tag the start and end times with +# STARTTIME=$(date +"%s") +# ENDTIME=$(date +"%s") +# TOTALTIME=$(($ENDTIME-$STARTTIME)) +# ------------------------------------------------------ +function convertsecs() { + ((h=${1}/3600)) + ((m=(${1}%3600)/60)) + ((s=${1}%60)) + printf "%02d:%02d:%02d\n" $h $m $s +} + +# Join +# ---------------------------------------------- +# This function joins items together with a user specified separator +# Taken whole cloth from: http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array +# +# Usage: +# join , a "b c" d #a,b c,d +# join / var local tmp #var/local/tmp +# join , "${FOO[@]}" #a,b,c +# ---------------------------------------------- +function join() { local IFS="${1}"; shift; echo "${*}"; } + +# File Checks +# ------------------------------------------------------ +# A series of functions which make checks against the filesystem. For +# use in if/then statements. +# +# Usage: +# if is_file "file"; then +# ... +# fi +# ------------------------------------------------------ + +function is_exists() { + if [[ -e "$1" ]]; then + return 0 + fi + return 1 +} + +function is_not_exists() { + if [[ ! -e "$1" ]]; then + return 0 + fi + return 1 +} + +function is_file() { + if [[ -f "$1" ]]; then + return 0 + fi + return 1 +} + +function is_not_file() { + if [[ ! -f "$1" ]]; then + return 0 + fi + return 1 +} + +function is_dir() { + if [[ -d "$1" ]]; then + return 0 + fi + return 1 +} + +function is_not_dir() { + if [[ ! -d "$1" ]]; then + return 0 + fi + return 1 +} + +function is_symlink() { + if [[ -L "$1" ]]; then + return 0 + fi + return 1 +} + +function is_not_symlink() { + if [[ ! -L "$1" ]]; then + return 0 + fi + return 1 +} + +function is_empty() { + if [[ -z "$1" ]]; then + return 0 + fi + return 1 +} + +function is_not_empty() { + if [[ -n "$1" ]]; then + return 0 + fi + return 1 +} + + +# SEEKING CONFIRMATION +# ------------------------------------------------------ +# Asks questions of a user and then does something with the answer. +# y/n are the only possible answers. +# +# USAGE: +# seek_confirmation "Ask a question" +# if is_confirmed; then +# some action +# else +# some other action +# fi +# +# Credt: https://github.com/kevva/dotfiles +# ------------------------------------------------------ + +# Ask the question +function seek_confirmation() { + # echo "" + input "$@" + read -p " (y/n) " -n 1 + echo "" +} + +# Test whether the result of an 'ask' is a confirmation +function is_confirmed() { + if "${force}"; then + return 0 + else + if [[ "${REPLY}" =~ ^[Yy]$ ]]; then + return 0 + fi + return 1 + fi +} + +function is_not_confirmed() { + if "${force}"; then + return 1 + else + if [[ "${REPLY}" =~ ^[Nn]$ ]]; then + return 0 + fi + return 1 + fi +} + +# Skip something +# ------------------------------------------------------ +# Offer the user a chance to skip something. +# Credit: https://github.com/cowboy/dotfiles +# ------------------------------------------------------ +function skip() { + REPLY=noskip + read -t 5 -n 1 -s -p "${bold}To skip, press ${underline}X${reset}${bold} within 5 seconds.${reset}" + if [[ "$REPLY" =~ ^[Xx]$ ]]; then + notice " Skipping!" + return 0 + else + notice " Continuing..." + return 1 + fi +} + +# help +# ------------------------------------------------------ +# Prints help for a script when invoked from the command +# line. Typically via '-h'. If additional flags or help +# text is available in the script they will be printed +# in the '$usage' variable. +# ------------------------------------------------------ +function help () { + echo "" 1>&2 + input " $@" 1>&2 + if [ -n "${usage}" ]; then # print usage information if available + echo " ${usage}" 1>&2 + fi + echo "" 1>&2 + exit 1 +} + + +function pauseScript() { + # A simple function used to pause a script at any point and + # only continue on user input + seek_confirmation "Ready to continue?" + if is_confirmed; then + info "Continuing" + else + warning "Exiting Script." + safeExit + fi +} + +# Text Transformations +# ----------------------------------- +# Transform text using these functions. +# Adapted from https://github.com/jmcantrell/bashful +# ----------------------------------- + +lower() { + # Convert stdin to lowercase. + # usage: text=$(lower <<<"$1") + # echo "MAKETHISLOWERCASE" | lower + tr '[:upper:]' '[:lower:]' +} + +function lower_trim(){ + echo $1 | lower | trim +} + +# Removes all whitespaces +trim() { + tr -d '[:space:]' +} + +only_letters_and_numbers(){ + tr -d -c '[:alnum:]' +} + + +function get_http_code_message(){ + case $1 in + 000) status="Not responding within timeout" ;; + 100) status="Informational: Continue" ;; + 101) status="Informational: Switching Protocols" ;; + 200) status="Successful: OK within timeout" ;; + 201) status="Successful: Created" ;; + 202) status="Successful: Accepted" ;; + 203) status="Successful: Non-Authoritative Information" ;; + 204) status="Successful: No Content" ;; + 205) status="Successful: Reset Content" ;; + 206) status="Successful: Partial Content" ;; + 300) status="Redirection: Multiple Choices" ;; + 301) status="Redirection: Moved Permanently" ;; + 302) status="Redirection: Found residing temporarily under different URI" ;; + 303) status="Redirection: See Other" ;; + 304) status="Redirection: Not Modified" ;; + 305) status="Redirection: Use Proxy" ;; + 306) status="Redirection: status not defined" ;; + 307) status="Redirection: Temporary Redirect" ;; + 400) status="Client Error: Bad Request" ;; + 401) status="Client Error: Unauthorized" ;; + 402) status="Client Error: Payment Required" ;; + 403) status="Client Error: Forbidden" ;; + 404) status="Client Error: Not Found" ;; + 405) status="Client Error: Method Not Allowed" ;; + 406) status="Client Error: Not Acceptable" ;; + 407) status="Client Error: Proxy Authentication Required" ;; + 408) status="Client Error: Request Timeout within timeout" ;; + 409) status="Client Error: Conflict" ;; + 410) status="Client Error: Gone" ;; + 411) status="Client Error: Length Required" ;; + 412) status="Client Error: Precondition Failed" ;; + 413) status="Client Error: Request Entity Too Large" ;; + 414) status="Client Error: Request-URI Too Long" ;; + 415) status="Client Error: Unsupported Media Type" ;; + 416) status="Client Error: Requested Range Not Satisfiable" ;; + 417) status="Client Error: Expectation Failed" ;; + 500) status="Server Error: Internal Server Error" ;; + 501) status="Server Error: Not Implemented" ;; + 502) status="Server Error: Bad Gateway" ;; + 503) status="Server Error: Service Unavailable" ;; + 504) status="Server Error: Gateway Timeout within timeout" ;; + 505) status="Server Error: HTTP Version Not Supported" ;; + *) status="status not defined." ;; + esac + echo "$status" +} diff --git a/orchestration/bash/lib/utils.sh b/orchestration/bash/lib/utils.sh new file mode 100644 index 00000000000..491f03f8a23 --- /dev/null +++ b/orchestration/bash/lib/utils.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +# Logging and Colors +# ------------------------------------------------------ +# Here we set the colors for our script feedback. +# Example usage: success "sometext" +#------------------------------------------------------ + +# Set Colors +bold=$(tput bold) +underline=$(tput sgr 0 1) +reset=$(tput sgr0) +purple='\033[0;35m' +red='\033[0;31m' +green='\033[0;32m' +tan=$(tput setaf 3) +blue='\033[0;36m' + +function _alert() { + if [ "${1}" = "emergency" ]; then + local color="${bold}${red}" + fi + if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi + if [ "${1}" = "warning" ]; then local color="${red}"; fi + if [ "${1}" = "success" ]; then local color="${green}"; fi + if [ "${1}" = "debug" ]; then local color="${purple}"; fi + if [ "${1}" = "header" ]; then local color="${bold}""${tan}"; fi + if [ "${1}" = "input" ]; then local color="${bold}"; printLog="false"; fi + if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi + + # Don't use colors on pipes or non-recognized terminals + # if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi + + # Print to $logFile + if [[ ${printLog} = "true" ]] || [ "${printLog}" == "1" ]; then + echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}"; + fi + + # Print to console when script is not 'quiet' + if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then + return + else + echo -e "${color}$(printf "" "${1}") ${_message}${reset}" >&2; + fi +} + +function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;} +function error () { local _message="\r${*}"; echo "$(_alert error)" >&2; } +function warning () { local _message="\r${*}"; echo "$(_alert warning)" >&2; } +function notice () { local _message="\r${*}"; echo "$(_alert notice)" >&2; } +function info () { local _message="\r${*}"; echo "$(_alert info)" >&2; } +function debug () { local _message="\r${*}"; echo "$(_alert debug)" >&2; } +function success () { local _message="\r${*}"; echo "$(_alert success)" >&2; } +function input() { local _message="${*}"; echo "$(_alert input)" >&2; } +function header() { local _message="\n========== ${*} ==========\n "; echo "$(_alert header)"; } + +# Log messages when verbose is set to "true" +verbose() { + if [[ "${verbose}" = "true" ]] || [ "${verbose}" == "1" ]; then + debug "$@" + fi +} + + +# Source additional /lib files +# ------------------------------------------------------ + +# First we locate this script and populate the $SCRIPTPATH variable +# Doing so allows us to source additional files from this utils file. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "${SOURCE}" ]; do # resolve ${SOURCE} until the file is no longer a symlink + DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" + SOURCE="$(readlink "${SOURCE}")" + [[ ${SOURCE} != /* ]] && SOURCE="${DIR}/${SOURCE}" # if ${SOURCE} was a relative symlink, we need to resolve it relative to the path where the symlink file was located +done +SOURCEPATH="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" + +wget -q --spider releases.sapsailing.com --timeout=5 + +if [ ! $? -eq 0 ]; then + error "No internet connection. Exiting." + safeExit +fi + +if [ ! -d "${SOURCEPATH}" ] +then + die "Failed to find library files expected in: ${SOURCEPATH}" +fi +for utility_file in "${SOURCEPATH}"/*.sh +do + if [ -e "${utility_file}" ]; then + + # Don't source self + if [[ "${utility_file}" == *"utils.sh"* ]]; then + continue + fi + + # Don't source configurator + if [[ "${utility_file}" == *"build-config.sh"* ]]; then + continue + fi + + # Don't source resources file (will be sourced later) + if [[ "${utility_file}" == *"resources"* ]]; then + continue + fi + + source "$utility_file" + fi +done diff --git a/orchestration/bash/lib/validation.sh b/orchestration/bash/lib/validation.sh new file mode 100644 index 00000000000..3a70ac12b5d --- /dev/null +++ b/orchestration/bash/lib/validation.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +ValidHostnameRegex='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' +ValidIpAddressRegex='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' +ValidInstanceIdRegex='i-[a-zA-Z0-9]{17}' +ValidLaunchTemplateIdRegex='lt-[a-zA-Z0-9]{17}' +ValidResourceARNRegex="arn:.+:.+:.+:.+:.+\/.+" +ValidInstanceARNRegex="arn:.+:.+:.+:.+:instance\/.+" +ValidSecurityGroupARNRegex="arn:.+:.+:.+:.+:security-group\/.+" +ValidImageARNRegex="arn:.+:.+:.+:.+:image\/.+" +ValidCertificateARNRegex="arn:.+:.+:.+:.+:certificate\/.+" +ValidLoadBalancerARNRegex="arn:.+:.+:.+:.+:loadbalancer\/.+" + +function validate_resource_arn(){ + [[ "$1" =~ $ValidResourceARNRegex ]] && echo "$1" || error_message "ResourceARN \"$1\" not valid." +} + +function validate_instance_id(){ + [[ "$1" =~ $ValidInstanceIdRegex ]] && echo "$1" || error_message "Instance id \"$1\" not valid." +} + +function validate_launch_template_id(){ + [[ "$1" =~ $ValidLaunchTemplateIdRegex ]] && echo "$1" || error_message "Launch template id \"$1\" not valid." +} + +function validate_hostname(){ + [[ "$1" =~ $ValidHostnameRegex ]] && echo "$1" || error_message "Hostname \"$1\" not valid." +} + +function validate_ipaddress(){ + [[ "$1" =~ $ValidIpAddressRegex ]] && echo "$1" || error_message "Ip address \"$1\" not valid." +} + +function error_message(){ + error "[ ERROR ] $1 (${FUNCNAME[@]})" + return 1 +} diff --git a/orchestration/bash/lib/variables.sh b/orchestration/bash/lib/variables.sh new file mode 100644 index 00000000000..976004b10bf --- /dev/null +++ b/orchestration/bash/lib/variables.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +latest_release=$(get_latest_release) +events_conf='/etc/httpd/conf.d/001-events.conf' +scriptName=`basename $0` #Set Script Name variable +scriptBasename="$(basename ${scriptName} .sh)" # Strips '.sh' from scriptName +declare -r script_start_time=$(LC_ALL=C date +"%Y-%m-%d %H:%M:%S") # Returns: 2015-06-15 22:34:40 +declare -r timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440 +today=$(LC_ALL=C date +"%m-%d-%Y") # Returns: 06-14-2015 +longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500 +gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT +thisHost=$(hostname) diff --git a/orchestration/calculateCodeCoverage.sh b/orchestration/calculateCodeCoverage.sh new file mode 100644 index 00000000000..ba8f2dee10a --- /dev/null +++ b/orchestration/calculateCodeCoverage.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +regex='(ok)[[:space:]]+([[:blank:]])(.*?)([[:blank:]])(.*)(coverage:)[[:space:]]([0-9]{1,3})+(\.[0-9][0-9]?)(.*)' +total=0 +percent=0 +while IFS='' read -r line || [[ -n "$line" ]]; do + if [[ $line =~ $regex ]]; + then + let percent=$percent+${BASH_REMATCH[7]} + let total=$total+1 + fi +done < "$1" +let total=$percent/$total +echo "Total code coverage: $total%" diff --git a/orchestration/configs/instance_template/master.json b/orchestration/configs/instance_template/master.json new file mode 100644 index 00000000000..a990401d806 --- /dev/null +++ b/orchestration/configs/instance_template/master.json @@ -0,0 +1,12 @@ +{ + "image": "ami-9e627974", + "instance-type": "t2.large", + "vpc": "vpc-14b3477f", + "subnet": "subnet-eab34781", + "count": 1, + "security-groups": [ + "sg-eaf31e85" + ], + "tags": { + } +} diff --git a/orchestration/configs/secrets/conf_aws.json.sample b/orchestration/configs/secrets/conf_aws.json.sample new file mode 100644 index 00000000000..4d2eb7b3681 --- /dev/null +++ b/orchestration/configs/secrets/conf_aws.json.sample @@ -0,0 +1,7 @@ +{ + "AWS_ACCESS_KEY_ID": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_DEFAULT_REGION": "", + "AWS_ELB_NAME": "", + "AWS_SNS_TOPIC_ARN": "" +} diff --git a/orchestration/createDistro.sh b/orchestration/createDistro.sh new file mode 100644 index 00000000000..c7560637098 --- /dev/null +++ b/orchestration/createDistro.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +if [ "$#" == 1 ] +then + version=${1} + echo "creating distribution with version ${version}..." + + tar -cvzf ./orchestration-${version}.tgz -T ./includeFilesDistro + ret=$? + if [ "$ret" != "0" ] + then + echo "did not create distribution correctly." + else + echo "successfully created distribution." + fi + + exit ${ret} +else + echo "version not specified/entered wrong number of parameters" + exit 1 +fi + diff --git a/orchestration/env.sh b/orchestration/env.sh new file mode 100755 index 00000000000..7d9d6a10197 --- /dev/null +++ b/orchestration/env.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +export PREFIX="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export GOPATH=${HOME}/.go:${PREFIX} +export GOMAXPROCS=4 diff --git a/orchestration/includeFilesDistro b/orchestration/includeFilesDistro new file mode 100644 index 00000000000..72d5e77a31d --- /dev/null +++ b/orchestration/includeFilesDistro @@ -0,0 +1,5 @@ +bin/agent-linux-amd64 +bin/orchestrator-linux-amd64 +configs/instance_template/master.json +configs/secrets/conf_aws.json.sample +env.sh diff --git a/orchestration/src/sapsailing.com/agent/agent.go b/orchestration/src/sapsailing.com/agent/agent.go new file mode 100644 index 00000000000..485cafa0169 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/agent.go @@ -0,0 +1,36 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + "os" + "sapsailing.com/agent/scenario" + "sapsailing.com/common" +) + +func main() { + log.SetFormatter(&common.SimpleFormatter{Source: "Agent"}) + + log.Info("Started!") + + input := &common.AgentInput{} + var err error + if len(os.Args) > 1 { + confPath := os.Args[1] + log.Debugf("Loading input from config: %s", confPath) + err = common.LoadFromFile(confPath, input) + } else { + log.Debugf("Loading input from stdin") + err = common.LoadFromReader(os.Stdin, input) + } + if err != nil { + log.Errorf("Error while reading configuration: %s", err) + os.Exit(1) + } + + log.Debugf("Input: %s", *input) + success := scenario.Invoke(input) + + if !success { + os.Exit(1) + } +} diff --git a/orchestration/src/sapsailing.com/agent/paths/paths.go b/orchestration/src/sapsailing.com/agent/paths/paths.go new file mode 100644 index 00000000000..bb80aab43ee --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/paths/paths.go @@ -0,0 +1,42 @@ +package paths + +import ( + "os" + "os/user" + "path" +) + +func GetHome() string { + // get the home of the current user if possible + currentUser, err := user.Current() + if err == nil { + return currentUser.HomeDir + } + // assume a unixoid environment and return the HOME env value + homeEnv, homeEnvFound := os.LookupEnv("HOME") + if homeEnvFound { + return homeEnv + } + // last resort: assume typical setup + return "/home/sailing" +} + +func GetCode() string { + return path.Join(GetHome(), "code") +} + +func GetConfiguration() string { + return path.Join(GetCode(), "configuration") +} + +func GetServers() string { + return path.Join(GetHome(), "servers") +} + +func GetServer(name string) string { + return path.Join(GetServers(), name) +} + +func GetApacheConf() string { + return path.Join(GetHome(), "apache-events") +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/apache.go b/orchestration/src/sapsailing.com/agent/primitive/apache.go new file mode 100644 index 00000000000..b434f1b8186 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/apache.go @@ -0,0 +1,76 @@ +package primitive + +import ( + log "github.com/sirupsen/logrus" + "os" + "path" + "sapsailing.com/agent/paths" + "sapsailing.com/common" +) + +func TestApacheConfig() bool { + success, output, err := common.RunCommandSimple("sudo", "apachectl", "configtest") + if err != nil { + log.Errorf("Error testing apache config: %s\n%s", err, output) + } + return success +} + +func marcoEventSsl(hostname string, eventID string) string { + return "Event-SSL " + hostname + " \"" + eventID + "\" 127.0.0.1 8888" +} + +func macroPlainSsl(internalIP string) string { + return "Plain-SSL " + internalIP + " 127.0.0.1 8888" +} + +func macroStatus(internalHostname string) string { + return "Status " + internalHostname + " internal-server-status" +} + +func ReloadApacheDaemon() error { + if !TestApacheConfig() { + return nil + } + _, err := ControlDaemon("apache2", DaemonReload) + return err +} + +func CreateApacheEvent(hostname string, eventID string, internalIP string) (common.UndoFunction, error) { + macros := []string{ + macroPlainSsl(internalIP), + marcoEventSsl(hostname, eventID), + } + + return createApacheMacroFile(hostname, macros) +} + +func CreateApacheStatus(internalHostname string) (common.UndoFunction, error) { + macros := []string{ + macroStatus(internalHostname), + } + + return createApacheMacroFile("status", macros) +} + +func createApacheMacroFile(name string, macros []string) (common.UndoFunction, error) { + filePath := path.Join(paths.GetApacheConf(), name+".conf") + f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_SYNC|os.O_TRUNC, 0755) + if err != nil { + return nil, err + } + + for _, line := range macros { + _, err = f.Write([]byte("Use " + line + "\n")) + if err != nil { + return nil, err + } + } + + err = f.Close() + + return func() { + log.Warnf("Rolling back the new event config %s...", filePath) + os.Remove(filePath) + }, err +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/common.go b/orchestration/src/sapsailing.com/agent/primitive/common.go new file mode 100644 index 00000000000..f9b8b4faba5 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/common.go @@ -0,0 +1,38 @@ +package primitive + +import ( + "fmt" + + "github.com/pbnjay/memory" + log "github.com/sirupsen/logrus" +) + +const ( + MemoryReservedForOsInMB uint64 = 2048 + MinimumMemoryForJavaInstanceInMB uint64 = 1024 +) + +func GetMemoryForJavaInstanceInMB(numberOfJavaInstances uint64, dryRun bool) (uint64, error) { + var memory uint64 + total := GetHostMemoryInMB() + if dryRun { + return total, nil + } + if total < MemoryReservedForOsInMB { + log.Warningf("host memory %d mb smaller than reservation for os", total) + return 0, fmt.Errorf("not enough available memory") + } else if total < (MemoryReservedForOsInMB + MinimumMemoryForJavaInstanceInMB) { + log.Warningf("host memory %d mb smaller than reservation for os and java minimum", total) + return 0, fmt.Errorf("not enough available memory") + } + log.Debugf("host has %d mb memory", total) + + memory = (total - MemoryReservedForOsInMB) / numberOfJavaInstances + log.Debugf("calculated %d mb memory for java instance", memory) + + return memory, nil +} + +func GetHostMemoryInMB() uint64 { + return memory.TotalMemory() / 1000000 +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/common_test.go b/orchestration/src/sapsailing.com/agent/primitive/common_test.go new file mode 100644 index 00000000000..c1f9a8ac6c1 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/common_test.go @@ -0,0 +1,17 @@ +package primitive + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetHostMemoryInMB(t *testing.T) { + assert.NotEmpty(t, GetHostMemoryInMB()) +} + +func TestGetMemoryForJavaInstanceInMB(t *testing.T) { + memory, err := GetMemoryForJavaInstanceInMB(1, true) + assert.NoError(t, err) + assert.NotEmpty(t, memory) +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/instance.go b/orchestration/src/sapsailing.com/agent/primitive/instance.go new file mode 100644 index 00000000000..114c959401c --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/instance.go @@ -0,0 +1,179 @@ +package primitive + +import ( + "fmt" + "github.com/go-resty/resty" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "math" + "net" + "os" + "path" + "sapsailing.com/agent/paths" + "sapsailing.com/common" + "sapsailing.com/common/sailing" + "strings" + "time" +) + +func SetupNewServer(name string, release string, environment string) (common.UndoFunction, error) { + serverPath := paths.GetServer(name) + + err := os.MkdirAll(serverPath, 0755) + if err != nil { + return nil, err + } + + refreshInstanceScript := path.Join(paths.GetCode(), "java", "target", "refreshInstance.sh") + installReleaseCmd := []string{refreshInstanceScript, "install-release", release} + env := []string{"DEPLOY_TO=" + name} + success, output, err := common.RunCommand(serverPath, env, "bash", installReleaseCmd...) + if err != nil { + return nil, errors.Wrapf(err, "command failed: %s", output) + } + if !success { + return nil, fmt.Errorf("command failed: %s", output) + } + + rollback := func() { + log.Warnf("Rolling back the new instance %s...", name) + os.RemoveAll(serverPath) + } + + log.Infof("Installing environment: %s...", environment) + err = InstallEnvironment(environment, path.Join(serverPath, "env.sh")) + if err != nil { + rollback() + return nil, err + } + + return rollback, nil +} + +func InstallEnvironment(env string, to string) error { + response, err := resty.R().Get("http://releases.sapsailing.com/environments/" + env) + if err != nil { + return err + } + + data := response.String() + f, err := os.OpenFile(to, os.O_APPEND|os.O_RDWR|os.O_SYNC, 0755) + if err != nil { + return err + } + + f.WriteString("\n\n# Environment: " + env + "\n") + f.WriteString(data) + f.WriteString("\n") + + err = f.Close() + if err != nil { + return err + } + + return nil +} + +func UpdateCode() (common.UndoFunction, error) { + var env []string + codeDir := paths.GetCode() + success, output, err := common.RunCommand(codeDir, env, "git", "rev-parse", "HEAD") + if !success { + return nil, fmt.Errorf("failed to parse HEAD") + } + if err != nil { + return nil, err + } + oldHead := strings.TrimSpace(output) + rollback := func() { + log.Warnf("Rolling back the git pull by resetting to %s...", oldHead) + common.RunCommand(codeDir, env, "git", "reset", "--hard", oldHead) + } + success, _, err = common.RunCommand(codeDir, env, "git", "pull") + if err != nil { + rollback() + return nil, err + } + if !success { + rollback() + return nil, fmt.Errorf("git pull was unsuccessful") + } + if err != nil { + return nil, err + } + + return rollback, nil +} + +func CreateEvents(appPort uint16, specs []common.EventSpec) ([]sailing.SailingEvent, error) { + appConf := &sailing.ApplicationConfig{ + Host: "localhost", + Port: appPort, + ApiVersion: 1, + AdminPassword: "admin", + AdminUsername: "admin", + } + + appClient, err := sailing.WaitForAuthenticatedClient(appConf, 5*time.Second) + if err != nil { + return nil, err + } + + events := make([]sailing.SailingEvent, len(specs)) + + for i, spec := range specs { + log.Infof("Creating event %s (%s)", spec.DisplayName, spec.Hostname) + event, err := retryCreateEvent(appClient, spec.DisplayName, 5*time.Second) + if err != nil { + return nil, err + } + events[i] = *event + } + + return events, nil +} + +func retryCreateEvent(client *sailing.ApplicationClient, displayName string, delay time.Duration) (*sailing.SailingEvent, error) { + try := 0 + for { + event, err := client.CreateEvent(displayName, false) + if err == nil { + return event, nil + } + switch err.(type) { + case *sailing.ApiError: + apiErr := err.(*sailing.ApiError) + if !(apiErr.ResponseStatus == 404 || apiErr.ResponseStatus >= 500) { + return nil, err + } + default: + return nil, err + } + try++ + if try >= 10 { + return event, err + } + time.Sleep(delay) + } +} + +func SetEnvShValues(serverName string, values map[string]string) (common.UndoFunction, error) { + envsh := path.Join(paths.GetServer(serverName), "env.sh") + return PatchBashConf(envsh, values) +} + +func FindNextFreePortFrom(from uint16) uint16 { + return FindNextFreePortFromTo(from, math.MaxUint16) +} + +func FindNextFreePortFromTo(from uint16, to uint16) uint16 { + i := from + for i <= to { + _, err := net.Dial("tcp", "localhost:"+string(from)) + if err != nil { + return i + } + i++ + } + return 0 +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/patch_bash.go b/orchestration/src/sapsailing.com/agent/primitive/patch_bash.go new file mode 100644 index 00000000000..1c398374c13 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/patch_bash.go @@ -0,0 +1,106 @@ +package primitive + +import ( + "io/ioutil" + "os" + "path" + + log "github.com/sirupsen/logrus" + bash "mvdan.cc/sh/syntax" + "sapsailing.com/common" +) + +func buildQuotedString(value string) *bash.Word { + literalValue := bash.WordPart(&bash.Lit{Value: value}) + quotedString := bash.WordPart(&bash.DblQuoted{Parts: []bash.WordPart{literalValue}}) + word := &bash.Word{Parts: []bash.WordPart{quotedString}} + + return word +} + +func buildAssignment(name string, value string) *bash.Stmt { + assignment := &bash.Assign{Name: &bash.Lit{Value: name}, Value: buildQuotedString(value)} + return &bash.Stmt{Cmd: bash.Command(&bash.CallExpr{Assigns: []*bash.Assign{assignment}})} +} + +func PatchBashConf(scriptPath string, values map[string]string) (common.UndoFunction, error) { + + f, err := os.Open(scriptPath) + if err != nil { + return nil, err + } + + parser := bash.NewParser(bash.KeepComments) + parsed, err := parser.Parse(f, path.Base(scriptPath)) + if err != nil { + return nil, err + } + + err = f.Close() + if err != nil { + return nil, err + } + + changeCounter := 0 + applied := map[string]bool{} + + bash.Walk(parsed, func(node bash.Node) bool { + switch node.(type) { + case *bash.Assign: + assignment := node.(*bash.Assign) + + varName := assignment.Name.Value + newValue, toBeChanged := values[varName] + + if toBeChanged { + applied[varName] = true + changeCounter++ + + assignment.Value = buildQuotedString(newValue) + } + return false + default: + return true + } + }) + + for k, v := range values { + _, alreadyApplied := applied[k] + + if !alreadyApplied { + changeCounter++ + parsed.Stmts = append(parsed.Stmts, buildAssignment(k, v)) + } + } + + if changeCounter > 0 { + backupDir, err := ioutil.TempDir("/tmp", "bash-patch-") + if err != nil { + return nil, err + } + backupFile := path.Join(backupDir, path.Base(scriptPath)) + err = common.Copy(scriptPath, backupFile) + if err != nil { + return nil, err + } + + f, err = os.OpenFile(scriptPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_TRUNC, 0755) + if err != nil { + return nil, err + } + + printer := bash.NewPrinter(bash.KeepPadding) + printer.Print(f, parsed) + err = f.Close() + if err != nil { + return nil, err + } + + return func() { + log.Warnf("Rolling back patch to %s...", scriptPath) + os.Rename(backupFile, scriptPath) + }, nil + } + + return func() {}, nil +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/patch_bash_test.go b/orchestration/src/sapsailing.com/agent/primitive/patch_bash_test.go new file mode 100644 index 00000000000..436a36c1677 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/patch_bash_test.go @@ -0,0 +1,32 @@ +package primitive + +import ( + "io/ioutil" + "path" + "testing" + + "github.com/stretchr/testify/require" + "sapsailing.com/common" + "sapsailing.com/orchestrator/paths" +) + +func TestPatchBashConf(t *testing.T) { + filePath := "/tmp/test.sh" + sourceFile := path.Join(paths.GetPrefix(), "testdata", "test.sh") + err := common.Copy(sourceFile, filePath) + require.NoError(t, err, "Copying the file to tmp should not fail") + + fields := map[string]string{ + "NEW_VAR": "new value", + "NEW_VAR2": "new value2", + "INSTANCE_ID": "patched-id", + "SERVER_STARTUP_NOTIFY": "test", + } + _, err = PatchBashConf(filePath, fields) + require.NoError(t, err, "Patching bash file should not fail") + + _, err = ioutil.ReadFile(filePath) + require.NoError(t, err, "Reading the file after patching should not fail") + + //TODO: verify content +} diff --git a/orchestration/src/sapsailing.com/agent/primitive/system.go b/orchestration/src/sapsailing.com/agent/primitive/system.go new file mode 100644 index 00000000000..955340c98ff --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/primitive/system.go @@ -0,0 +1,47 @@ +package primitive + +import ( + "fmt" + "github.com/sirupsen/logrus" + "sapsailing.com/common" +) + +type DaemonAction string + +const ( + DaemonStart DaemonAction = "start" + DaemonStop DaemonAction = "stop" + DaemonEnable DaemonAction = "enable" + DaemonDisable DaemonAction = "disable" + DaemonRestart DaemonAction = "restart" + DaemonReload DaemonAction = "reload" + DaemonStatus DaemonAction = "status" +) + +func ControlDaemon(daemon string, action DaemonAction) (common.UndoFunction, error) { + success, output, err := common.RunCommandSimple("sudo", "systemctl", string(action), daemon) + if !success { + return nil, fmt.Errorf("failed to %s daemon %s: err=%s, output=%s", action, daemon, err, output) + } + return func() { + var newAction DaemonAction + switch action { + case DaemonStop: + newAction = DaemonStart + case DaemonStart: + newAction = DaemonStop + case DaemonDisable: + newAction = DaemonEnable + case DaemonEnable: + newAction = DaemonDisable + } + if newAction != "" { + logrus.Warnf("Rolling back service action %s by doing %s...", action, newAction) + ControlDaemon(daemon, action) + } + }, nil +} + +func SailingDaemon(name string) string { + return "sailing@" + name +} diff --git a/orchestration/src/sapsailing.com/agent/scenario/invoke.go b/orchestration/src/sapsailing.com/agent/scenario/invoke.go new file mode 100644 index 00000000000..cbf3247c165 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/scenario/invoke.go @@ -0,0 +1,17 @@ +package scenario + +import ( + log "github.com/sirupsen/logrus" + "sapsailing.com/common" + "strings" +) + +func Invoke(input *common.AgentInput) bool { + switch strings.ToLower(input.Scenario) { + case "master": + return Master(input) + default: + log.Errorf("Unknown scenario: %s", input.Scenario) + return false + } +} diff --git a/orchestration/src/sapsailing.com/agent/scenario/master.go b/orchestration/src/sapsailing.com/agent/scenario/master.go new file mode 100644 index 00000000000..3bec0d43e59 --- /dev/null +++ b/orchestration/src/sapsailing.com/agent/scenario/master.go @@ -0,0 +1,139 @@ +package scenario + +import ( + log "github.com/sirupsen/logrus" + "sapsailing.com/agent/primitive" + "sapsailing.com/common" + "strconv" +) + +func Master(input *common.AgentInput) bool { + log.Info("Master scenario started") + + undoStack := &common.UndoStack{} + + log.Info("Updating the code repository...") + undo, err := primitive.UpdateCode() + if err != nil { + log.Errorf("Failed to update the code: %s", err) + return false + } + undoStack.Push(undo) + + sailingDaemon := primitive.SailingDaemon(input.ServerName) + + log.Infof("Setting up new server instance %s...", input.ServerName) + undo, err = primitive.SetupNewServer(input.ServerName, input.AnalyticsRelease, input.AnalyticsEnvironment) + if err != nil { + log.Errorf("Failed to create analytics server: %s", err) + undoStack.UndoAll() + return false + } + undoStack.Push(undo) + + serverPortFrom := uint16(8888) + serverPortTo := uint16(8950) + serverPort := primitive.FindNextFreePortFromTo(serverPortFrom, serverPortTo) + if serverPort == 0 { + log.Errorf("Failed to find a free server port in the range %d-%d", serverPortFrom, serverPortTo) + undoStack.UndoAll() + return false + } + + log.Debugf("Calculating memory for java instance...") + memory, err := primitive.GetMemoryForJavaInstanceInMB(1, false) + if err != nil { + log.Errorf("Failed to calculate memory for java instance: %s", err) + return false + } + + log.Infof("Updating env.sh for server instance %s...", input.ServerName) + undo, err = primitive.SetEnvShValues(input.ServerName, map[string]string{ + "DEPLOY_TO": input.ServerName, + "MEMORY": strconv.FormatUint(memory, 10) + "m", + "USE_ENVIRONMENT": input.AnalyticsEnvironment, + "MONGODB_NAME": common.HostnameToMasterDbName(input.ServerName), + "SERVER_NAME": input.ServerName, + "REPLICATION_CHANNEL": input.ServerName, + "TELNET_PORT": strconv.FormatUint(uint64(primitive.FindNextFreePortFrom(14888)), 10), + "SERVER_PORT": strconv.FormatUint(uint64(serverPort), 10), + "INSTANCE_NAME": input.AwsInstanceName, + "INSTANCE_IPV4": input.ExternalIp, + "INSTANCE_INTERNAL_IPV4": input.InternalIp, + "INSTANCE_DNS": input.InternalHostname, + "BUILD_COMPLETE_NOTIFY": input.Notify, + "SERVER_STARTUP_NOTIFY": input.Notify, + }) + if err != nil { + log.Errorf("Failed to set env.sh values: %s", err) + undoStack.UndoAll() + return false + } + undoStack.Push(undo) + + log.Infof("Starting the systemd daemon %s...", sailingDaemon) + undo, err = primitive.ControlDaemon(sailingDaemon, primitive.DaemonStart) + if err != nil { + log.Errorf("Failed start the sailing daemon: %s", err) + undoStack.UndoAll() + return false + } + undoStack.Push(undo) + + log.Infof("Checking for systemd daemon %s...", sailingDaemon) + _, err = primitive.ControlDaemon(sailingDaemon, primitive.DaemonStatus) + if err != nil { + log.Errorf("The daemon did not start properly: %s", err) + undoStack.UndoAll() + return false + } + + log.Info("Creating the events...") + events, err := primitive.CreateEvents(serverPort, input.Events) + if err != nil { + log.Errorf("Failed create events: %s", err) + undoStack.UndoAll() + return false + } + + log.Info("Creating apache internal-server-status config...") + undo, err = primitive.CreateApacheStatus(input.InternalHostname) + if err != nil { + log.Errorf("Failed to create the apache internal-server-status config: %s", err) + undoStack.UndoAll() + return false + } + undoStack.Push(undo) + + for i, event := range events { + domain := common.HostnameToDomain(input.Events[i].Hostname) + + log.Infof("Creating apache event config for event %s (%s) on domain %s...", event.Id, event.DisplayName, domain) + undo, err = primitive.CreateApacheEvent(domain, event.Id, input.InternalIp) + if err != nil { + log.Errorf("Failed to create the apache event config: %s", err) + undoStack.UndoAll() + return false + } + undoStack.Push(undo) + } + + log.Infof("Enabling the systemd daemon %s...", sailingDaemon) + undo, err = primitive.ControlDaemon(sailingDaemon, primitive.DaemonEnable) + if err != nil { + log.Errorf("Failed enable the sailing daemon: %s", err) + undoStack.UndoAll() + return false + } + undoStack.Push(undo) + + log.Info("Reloading the apache2 daemon...") + err = primitive.ReloadApacheDaemon() + if err != nil { + log.Errorf("Failed to reload apache: %s", err) + undoStack.UndoAll() + return false + } + + return true +} diff --git a/orchestration/src/sapsailing.com/common/agent.go b/orchestration/src/sapsailing.com/common/agent.go new file mode 100644 index 00000000000..e662f29391f --- /dev/null +++ b/orchestration/src/sapsailing.com/common/agent.go @@ -0,0 +1,32 @@ +package common + +type UndoFunction func() + +type UndoStack []UndoFunction + +func (s *UndoStack) Push(function UndoFunction) { + *s = append(*s, function) +} + +func (s *UndoStack) UndoAll() { + for i := len(*s) - 1; i >= 0; i-- { + (*s)[i]() + } + *s = UndoStack{} +} + +type AgentInput struct { + BasePath string + InternalIp string + ExternalIp string + ServerName string + InternalHostname string + Events []EventSpec + AnalyticsRelease string + AnalyticsEnvironment string + AwsInstanceName string + Notify string + + Scenario string + DryRun bool +} diff --git a/orchestration/src/sapsailing.com/common/cmd/cmd.go b/orchestration/src/sapsailing.com/common/cmd/cmd.go new file mode 100644 index 00000000000..68bb1f9c1f2 --- /dev/null +++ b/orchestration/src/sapsailing.com/common/cmd/cmd.go @@ -0,0 +1,136 @@ +package cmd + +import ( + "fmt" + "github.com/mitchellh/mapstructure" + "os" + "reflect" + "strings" +) + +type CommandFactory func() Command +type CommandMap map[string]CommandSpec +type Command interface { + Execute() uint8 +} +type CommandSpec struct { + Help string + Factory CommandFactory +} + +func DefaultMain(commands CommandMap) uint8 { + return RunCommands(os.Args[1:], commands, PrintCommandsFallback) +} + +func RunCommands(args []string, commands CommandMap, fallback func(commandMap CommandMap, err error) uint8) uint8 { + if len(args) > 0 { + name := strings.ToLower(args[0]) + command, ok := commands[name] + if ok { + data := command.Factory() + err := MapParams(&data, ParseParams(args[1:])) + if err != nil { + return fallback(commands, err) + } + return data.Execute() + } + } + return fallback(commands, nil) +} + +func ParseParams(rawParams []string) map[string][]string { + params := map[string][]string{} + + for i := range rawParams { + rawParam := rawParams[i] + sepIdx := strings.Index(rawParam, "=") + var name string + if sepIdx >= 0 { + name = rawParam[0:sepIdx] + } else { + name = rawParam + } + name = strings.ToLower(name) + paramValues, seen := params[name] + if !seen { + paramValues = []string{} + } + if sepIdx >= 0 { + params[name] = append(paramValues, rawParam[sepIdx+1:]) + } else { + params[name] = paramValues + } + } + + return params +} + +func flattenOnDemand(source reflect.Type, target reflect.Type, val interface{}) (interface{}, error) { + if source.Kind() == reflect.Slice && target.Kind() != reflect.Slice && source.Elem() == reflect.TypeOf("") { + slice := val.([]string) + return slice[len(slice)-1], nil + } + return val, nil +} + +func MapParams(cmd *Command, params map[string][]string) error { + meta := mapstructure.Metadata{} + conf := &mapstructure.DecoderConfig{ + DecodeHook: flattenOnDemand, + Result: cmd, + WeaklyTypedInput: true, + TagName: "cmd", + Metadata: &meta, + ZeroFields: false, + } + decoder, _ := mapstructure.NewDecoder(conf) + err := decoder.Decode(params) + if err != nil { + return err + } + + structPtr := reflect.ValueOf(cmd).Elem().Elem() + structVal := structPtr.Elem() + structType := structVal.Type() + supplied := map[string]bool{} + for _, key := range meta.Keys { + supplied[strings.ToLower(key)] = true + } + + missingCounter := 0 + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + name := field.Tag.Get("cmd") + if name == "" { + name = strings.ToLower(field.Name) + } + _, fieldOptional := field.Tag.Lookup("cmd_optional") + _, fieldSupplied := supplied[name] + if !fieldSupplied && !fieldOptional { + missingCounter++ + + fmt.Printf("Missing argument: %s=<%s>\n", name, field.Type) + helpText := field.Tag.Get("cmd_help") + if helpText != "" { + fmt.Printf("\t%s\n", helpText) + } + } + } + if missingCounter > 0 { + return fmt.Errorf("missing arguments") + } + + return err +} + +func PrintCommandsFallback(commandMap CommandMap, err error) uint8 { + if err != nil { + fmt.Printf("\nError: %s\n", err.Error()) + } else { + fmt.Println("Possible commands:") + for name, command := range commandMap { + fmt.Printf("- %s: %s\n", name, command.Help) + } + } + return 1 +} diff --git a/orchestration/src/sapsailing.com/common/cmd/cmd_test.go b/orchestration/src/sapsailing.com/common/cmd/cmd_test.go new file mode 100644 index 00000000000..ee81e1fa27e --- /dev/null +++ b/orchestration/src/sapsailing.com/common/cmd/cmd_test.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParseParams(t *testing.T) { + args := []string{"a=b", "c", "d=e", "d=f", "with-dash=works"} + params := ParseParams(args) + expected := map[string][]string{ + "a": {"b"}, + "c": {}, + "d": {"e", "f"}, + "with-dash": {"works"}, + } + + assert.Equal(t, expected, params, "Params should parse correctly.") +} + +type TestCommand struct { + Name string + Age uint + WithDash string `cmd:"with-dash"` + Different string `cmd:"sooo_different"` +} + +func (c *TestCommand) Execute() uint8 { + return 2 +} + +func TestRunCommands(t *testing.T) { + + params := []string{"test", "name=test", "age=1", "with-dash=works", "sooo_different=works"} + + commands := CommandMap{ + "test": CommandSpec{ + Help: "Creates a master instance", + Factory: func() Command { + return Command(&TestCommand{}) + }, + }, + } + result := RunCommands(params, commands, func(commandMap CommandMap, err error) uint8 { + return 1 + }) + + assert.Equal(t, uint8(2), result) +} diff --git a/orchestration/src/sapsailing.com/common/common.go b/orchestration/src/sapsailing.com/common/common.go new file mode 100644 index 00000000000..12a3149b633 --- /dev/null +++ b/orchestration/src/sapsailing.com/common/common.go @@ -0,0 +1,176 @@ +package common + +import ( + "encoding/json" + "golang.org/x/text/runes" + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" + "io" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "regexp" + "strings" + "unicode" +) + +const ( + SapSailingDomain = "sapsailing.com" + SshUser = "sailing" +) + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func RandStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +func GetEnvOrElse(s string, def string) string { + v, found := os.LookupEnv(s) + if found { + return v + } + return def +} + +func LoadFromFile(path string, dst interface{}) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + return LoadFromReader(f, &dst) +} + +func LoadFromReader(r io.Reader, dst interface{}) error { + b, err := ioutil.ReadAll(r) + if err != nil { + return err + } + + if len(b) == 0 { + return nil + } + + err = json.Unmarshal(b, &dst) + if err != nil { + return err + } + + return nil +} + +func RunCommandSimple(bin string, args ...string) (bool, string, error) { + return RunCommand("", []string{}, bin, args...) +} + +func RunCommand(workDir string, env []string, bin string, args ...string) (bool, string, error) { + cmd := exec.Command(bin, args...) + cmd.Dir = workDir + cmd.Env = append(os.Environ(), env...) + output, err := cmd.CombinedOutput() + if err != nil { + return false, string(output), err + } + return cmd.ProcessState.Success(), string(output), nil +} + +func Copy(src string, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return nil +} + +func DomainToHostname(domain string) string { + return strings.Replace(domain, "."+SapSailingDomain, "", 1) +} + +func HostnameToDomain(hostname string) string { + return hostname + "." + SapSailingDomain +} + +func HostnameToMasterDomain(hostname string) string { + return hostname + "-master." + SapSailingDomain +} + +func HostnameToMasterDbName(hostname string) string { + return hostname +} + +func HostnameToReplicaDbName(hostname string) string { + return HostnameToMasterDbName(hostname) + "-replica" +} + +var eventNameAttachNums = regexp.MustCompile("[^\\p{L}\\d]+(\\d+)") +var eventNameStripPattern = regexp.MustCompile("[^\\p{L}\\d]+") +var eventNameLeadingNumToTail = regexp.MustCompile("^((?:\\d+-)+)(.+)") +var diacriticsTransform = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) + +func EventNameToHostname(eventName string) string { + sep := "-" + lower := strings.ToLower(eventName) + numsAttached := eventNameAttachNums.ReplaceAllString(lower, "$1") + dashed := strings.Trim(eventNameStripPattern.ReplaceAllString(numsAttached, sep), sep) + noLeadingNum := strings.TrimRight(eventNameLeadingNumToTail.ReplaceAllString(dashed, "$2"+sep+"$1"), sep) + noDiacitics, _, err := transform.String(diacriticsTransform, noLeadingNum) + if err == nil { + return noDiacitics + } + return noLeadingNum +} + +type EventSpec struct { + DisplayName string + Hostname string +} + +func ParseEvents(rawEvents []string) []EventSpec { + events := make([]EventSpec, len(rawEvents)) + + for i, rawEvent := range rawEvents { + parts := strings.SplitN(rawEvent, ":", 2) + event := EventSpec{} + if len(parts) > 1 { + event.Hostname = parts[0] + event.DisplayName = parts[1] + } else { + event.DisplayName = parts[0] + event.Hostname = EventNameToHostname(event.DisplayName) + } + events[i] = event + } + + return events +} + +func GetEnvMap() map[string]string { + out := map[string]string{} + for _, env := range os.Environ() { + parts := strings.SplitN(env, "=", 2) + if len(parts) > 1 { + out[parts[0]] = parts[1] + } else { + out[parts[0]] = "" + } + } + return out +} diff --git a/orchestration/src/sapsailing.com/common/common_test.go b/orchestration/src/sapsailing.com/common/common_test.go new file mode 100644 index 00000000000..4535be86753 --- /dev/null +++ b/orchestration/src/sapsailing.com/common/common_test.go @@ -0,0 +1,11 @@ +package common + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestEventNameToHostname(t *testing.T) { + assert.Equal(t, "bundesliga2018-1", EventNameToHostname("1. Bundesliga - 2018")) + assert.Equal(t, "segel-bundesliga2018-travemunde-1", EventNameToHostname("1. Segel-Bundesliga 2018 - Travemünde")) +} diff --git a/orchestration/src/sapsailing.com/common/logging.go b/orchestration/src/sapsailing.com/common/logging.go new file mode 100644 index 00000000000..c79b615a7a6 --- /dev/null +++ b/orchestration/src/sapsailing.com/common/logging.go @@ -0,0 +1,54 @@ +package common + +import ( + "bytes" + log "github.com/sirupsen/logrus" + "io" + "os" + "time" +) + +const ( + LogEnvVar = "LOG" + LogFile = "sailing-automation.log" +) + +type SimpleFormatter struct { + Source string +} + +func (f *SimpleFormatter) Format(e *log.Entry) ([]byte, error) { + var buf bytes.Buffer + if f.Source != "" { + buf.WriteString("<") + buf.WriteString(f.Source) + buf.WriteString("> ") + } + buf.WriteString(e.Time.Format(time.RFC3339)) + buf.WriteString(" [") + buf.WriteString(e.Level.String()) + buf.WriteString("] ") + buf.WriteString(e.Message) + buf.WriteString("\n") + + return buf.Bytes(), nil +} + +var LogOutput io.Writer + +func init() { + level, err := log.ParseLevel(GetEnvOrElse(LogEnvVar, "")) + if err != nil { + level = log.InfoLevel + } + log.SetLevel(level) + log.SetFormatter(&SimpleFormatter{}) + + f, err := os.OpenFile(LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0766) + if err == nil { + LogOutput = io.MultiWriter(os.Stdout, f) + } else { + LogOutput = os.Stdout + } + log.SetOutput(LogOutput) +} diff --git a/orchestration/src/sapsailing.com/common/sailing/api.go b/orchestration/src/sapsailing.com/common/sailing/api.go new file mode 100644 index 00000000000..e5e08542180 --- /dev/null +++ b/orchestration/src/sapsailing.com/common/sailing/api.go @@ -0,0 +1,246 @@ +package sailing + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/go-resty/resty" + "time" +) + +func init() { + resty.SetDisableWarn(true) +} + +const ReleasesEndpoint = "http://releases.sapsailing.com/?C=M;O=D" + +// API doc: https://www.sapsailing.com/sailingserver/webservices/api/v1/index.html + +func isOk(resp *resty.Response) bool { + status := resp.StatusCode() + return status >= 200 && status < 300 +} + +func FindLatestBuild() (string, error) { + r, err := resty.R().Get(ReleasesEndpoint) + + if err != nil { + return "", err + } + + if !isOk(r) { + return "", fmt.Errorf("release index returned an error: %d", r.StatusCode()) + } + + releasePattern := regexp.MustCompile("([^<>]+)/") + matches := releasePattern.FindAllStringSubmatch(r.String(), -1) + + if len(matches) == 0 { + return "", fmt.Errorf("no builds found") + } + + return matches[0][1], nil +} + +type AccessToken struct { + Username string + AccessToken string `json:"access_token"` +} + +type SailingEvent struct { + Id string `json:"eventid"` + DisplayName string `json:"eventname"` +} + +type ApplicationConfig struct { + Host string + Port uint16 + ApiVersion uint + AdminUsername string + AdminPassword string +} + +type ApplicationClient struct { + Host string + Token AccessToken + ApiVersion uint + Port uint16 +} + +type ApiError struct { + Method string + Url string + Response string + ResponseStatus int +} + +func (e *ApiError) Error() string { + return fmt.Sprintf("request `%s %s` failed: %d %s", e.Method, e.Url, e.ResponseStatus, e.Response) +} + +func (c *ApplicationClient) authenticatedRequest() *resty.Request { + r := resty.R() + r.SetAuthToken(c.Token.AccessToken) + return r +} + +func BaseUrl(host string, port uint16) string { + var address string + var proto string + if port == 80 { + address = host + proto = "http" + } else if port == 443 { + address = host + proto = "https" + } else { + address = fmt.Sprintf("%s:%d", host, port) + proto = "http" + } + return fmt.Sprintf("%s://%s", proto, address) +} + +func (c *ApplicationClient) BaseUrl() string { + return BaseUrl(c.Host, c.Port) +} + +func (c *ApplicationClient) Api(path string) string { + return fmt.Sprintf("%s/sailingserver/api/v%d/%s", c.BaseUrl(), c.ApiVersion, path) +} + +func SecurityApi(host string, port uint16, path string) string { + return fmt.Sprintf("%s/security/api/restsecurity/%s", BaseUrl(host, port), path) +} + +func (c *ApplicationClient) SecurityApi(path string) string { + return SecurityApi(c.Host, c.Port, path) +} + +type ReqData struct { + FormData map[string]string + Result interface{} + Query map[string]string +} + +func (c *ApplicationClient) exec(method string, endpoint string, data *ReqData) error { + req := c.authenticatedRequest() + if data.FormData != nil { + req.SetFormData(data.FormData) + } + if data.Result != nil { + req.SetResult(data.Result) + } + if data.Query != nil { + req.SetQueryParams(data.Query) + } + res, err := req.Execute(method, endpoint) + if err != nil { + return err + } + + if !isOk(res) { + return error(&ApiError{ + Url: endpoint, + Method: method, + Response: res.String(), + ResponseStatus: res.StatusCode(), + }) + } + + return nil +} + +func (c *ApplicationClient) post(endpoint string, data *ReqData) error { + return c.exec("POST", endpoint, data) +} + +func (c *ApplicationClient) CreateEvent(name string, withLeaderboardGroup bool) (*SailingEvent, error) { + input := map[string]string{ + "eventName": name, + "venuename": "Default", + "createregatta": "false", + "createleaderboardgroup": strconv.FormatBool(withLeaderboardGroup), + } + event := &SailingEvent{} + err := c.post(c.Api("events/createEvent"), &ReqData{FormData: input, Result: event}) + + if err != nil { + return nil, err + } + + return event, nil +} + +func (c *ApplicationClient) ChangePassword(user string, newPassword string) error { + input := map[string]string{ + "username": user, + "password": newPassword, + } + return c.post(c.SecurityApi("change_password"), &ReqData{FormData: input}) +} + +func (c *ApplicationClient) CreateUser(name string, password string) error { + query := map[string]string{ + "username": name, + "password": password, + } + return c.post(c.SecurityApi("create_user"), &ReqData{Query: query}) +} + +func (c *ApplicationClient) InitializeApplication(eventName string, adminPassword string, username string, password string) (*SailingEvent, error) { + event, eventErr := c.CreateEvent(eventName, false) + changePassErr := c.ChangePassword("admin", adminPassword) + createUserErr := c.CreateUser(username, password) + + if eventErr != nil || changePassErr != nil || createUserErr != nil { + return event, fmt.Errorf("errors while initializing:\nEvent: %s\nChange Admin Password: %s\nCreate User: %s", eventErr, changePassErr, createUserErr) + } + + return event, nil +} + +func CreateAuthenticatedClient(config *ApplicationConfig) (*ApplicationClient, error) { + data := AccessToken{} + req := resty.R() + req.SetResult(&data) + req.SetBasicAuth(config.AdminUsername, config.AdminPassword) + res, err := req.Get(SecurityApi(config.Host, config.Port, "access_token")) + if err != nil { + return nil, err + } + + if !isOk(res) { + println(res.String()) + return nil, fmt.Errorf("access token endpoint returned error: %d", res.StatusCode()) + } + + client := &ApplicationClient{ + Host: config.Host, + Port: config.Port, + Token: data, + ApiVersion: config.ApiVersion, + } + + return client, nil +} + +func IsApplicationAvailable(config *ApplicationConfig) bool { + _, err := CreateAuthenticatedClient(config) + return err == nil +} + +func WaitForAuthenticatedClient(config *ApplicationConfig, delay time.Duration) (*ApplicationClient, error) { + try := 0 + for { + client, err := CreateAuthenticatedClient(config) + if err == nil { + return client, nil + } + try++ + if try >= 10 { + return client, err + } + time.Sleep(delay) + } +} diff --git a/orchestration/src/sapsailing.com/common/sailing/api_test.go b/orchestration/src/sapsailing.com/common/sailing/api_test.go new file mode 100644 index 00000000000..2620a9e61c2 --- /dev/null +++ b/orchestration/src/sapsailing.com/common/sailing/api_test.go @@ -0,0 +1,37 @@ +package sailing + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestBaseUrl(t *testing.T) { + assert.Equal(t, BaseUrl("test", 80), "http://test") + assert.Equal(t, BaseUrl("test", 443), "https://test") + assert.Equal(t, BaseUrl("test", 8888), "http://test:8888") +} + +func TestApplicationClient_BaseUrl(t *testing.T) { + assert.Equal(t, (&ApplicationClient{Host: "test", Port: 80}).BaseUrl(), "http://test") + assert.Equal(t, (&ApplicationClient{Host: "test", Port: 443}).BaseUrl(), "https://test") + assert.Equal(t, (&ApplicationClient{Host: "test", Port: 8888}).BaseUrl(), "http://test:8888") +} + +func TestSecurityApi(t *testing.T) { + assert.Equal(t, SecurityApi("test", 80, "test"), "http://test/security/api/restsecurity/test") +} + +func TestApplicationClient_SecurityApi(t *testing.T) { + assert.Equal(t, (&ApplicationClient{Host: "test", Port: 80}).SecurityApi("test"), "http://test/security/api/restsecurity/test") +} + +func TestApplicationClient_Api(t *testing.T) { + assert.Equal(t, (&ApplicationClient{Host: "test", Port: 80, ApiVersion: 2}).Api("test"), "http://test/sailingserver/api/v2/test") +} + +func TestApplicationClient_AuthenticatedRequest(t *testing.T) { + tok := AccessToken{Username: "admin", AccessToken: "token"} + req := (&ApplicationClient{Host: "test", Port: 80, ApiVersion: 2, Token: tok}).authenticatedRequest() + + assert.Equal(t, req.Token, tok.AccessToken) +} diff --git a/orchestration/src/sapsailing.com/orchestrator/agent/agent.go b/orchestration/src/sapsailing.com/orchestrator/agent/agent.go new file mode 100644 index 00000000000..c25e3106dad --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/agent/agent.go @@ -0,0 +1,48 @@ +package agent + +import ( + "bytes" + "encoding/json" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + "os" + "sapsailing.com/common" + "sapsailing.com/orchestrator/aws" + "sapsailing.com/orchestrator/paths" +) + +func DeployAgent(sshConnection *AgentConnection) error { + agentFile, err := os.Open(paths.GetAgent()) + if err != nil { + return err + } + defer agentFile.Close() + + err = sshConnection.Upload(agentFile, aws.RemoteAgentPath, 0755) + if err != nil { + return err + } + + return nil +} + +func InvokeAgent(sshConnection *AgentConnection, input *common.AgentInput) error { + serialData, err := json.Marshal(input) + if err != nil { + return err + } + + inputReader := bytes.NewReader(serialData) + env := map[string]string{ + common.LogEnvVar: log.GetLevel().String(), + } + log.Debugf("Env to forward: %s", env) + _, err = sshConnection.RunCommand(aws.RemoteAgentPath, env, inputReader, common.LogOutput, common.LogOutput) + + switch err.(type) { + case *ssh.ExitError: + exitErr := err.(*ssh.ExitError) + log.Debugf("Agent exited with code %d!", exitErr.ExitStatus()) + } + return err +} diff --git a/orchestration/src/sapsailing.com/orchestrator/agent/client.go b/orchestration/src/sapsailing.com/orchestrator/agent/client.go new file mode 100644 index 00000000000..539801a8588 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/agent/client.go @@ -0,0 +1,184 @@ +package agent + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "strings" + "time" + + "github.com/pkg/sftp" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" +) + +const ( + DefaultTimeout = 3 // second +) + +type AgentConnection struct { + Ip string + User string + Auth ssh.AuthMethod + Port int +} + +func KeyPairAuthFromPrivateKey(file string) (ssh.AuthMethod, error) { + buffer, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + key, err := ssh.ParsePrivateKey(buffer) + if err != nil { + return nil, err + } + return ssh.PublicKeys(key), nil +} + +func PassphraseAuthFromString(passphrase string) ssh.AuthMethod { + return ssh.Password(passphrase) +} + +func (c *AgentConnection) WaitForConnectivity(delay time.Duration) error { + try := 0 + for { + err := c.ProbeConnectivity() + if err == nil { + return nil + } + try++ + if try >= 10 { + return err + } + time.Sleep(delay) + } +} + +func (c *AgentConnection) ProbeConnectivity() error { + out, err := c.RunSimpleCommand("whoami") + if err != nil { + return err + } + user := strings.TrimSpace(out) + if user != c.User { + return fmt.Errorf("command succeeded, but user mismatched: %s(local) != %s(remote)", c.User, user) + } + + return nil +} + +func (c *AgentConnection) newClient() (*ssh.Client, error) { + + sshConfig := &ssh.ClientConfig{ + User: c.User, + Auth: []ssh.AuthMethod{c.Auth}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: time.Second * DefaultTimeout, + } + + port := c.Port + if port == 0 { + port = 22 + } + + client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", c.Ip, port), sshConfig) + if err != nil { + return nil, err + } + + return client, nil +} + +func (c *AgentConnection) RunCommand(cmd string, env map[string]string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (string, error) { + client, err := c.newClient() + if err != nil { + return "", err + } + defer client.Close() + + sess, err := client.NewSession() + if err != nil { + return "", err + } + defer sess.Close() + + for key, value := range env { + log.Debugf("forwarding env: %s=%s", key, value) + err = sess.Setenv(key, value) + if err != nil { + // TODO currently all env request fail. In the future propagate the error + log.Errorf("Setting env %s=%s failed: %s", key, value, err) + //return "", err + } + } + var output []byte + sess.Stdin = stdin + if stdout != nil && stderr != nil { + log.Debug("setup forwarding of stdout and stderr") + sess.Stdout = stdout + sess.Stderr = stderr + log.Debugf("running the command with forwarded output: %s", cmd) + err = sess.Run(cmd) + } else { + log.Debugf("running the command and return the output: %s", cmd) + output, err = sess.CombinedOutput(cmd) + } + + return string(output), err +} + +func (c *AgentConnection) RunSimpleCommand(cmd string) (string, error) { + out, err := c.RunCommand(cmd, map[string]string{}, bytes.NewReader(nil), nil, nil) + return out, err +} + +func (c *AgentConnection) newSftpClient() (*sftp.Client, error) { + sshClient, err := c.newClient() + if err != nil { + return nil, err + } + + client, err := sftp.NewClient(sshClient) + if err != nil { + return nil, err + } + + return client, nil +} + +func (c *AgentConnection) Upload(reader io.Reader, to string, mode os.FileMode) error { + sftpClient, err := c.newSftpClient() + if err != nil { + return err + } + defer sftpClient.Close() + + directory := path.Dir(to) + err = sftpClient.MkdirAll(directory) + if err != nil { + return err + } + + f, err := sftpClient.Create(to) + if err != nil { + return err + } + + defer f.Close() + + _, err = io.Copy(f, reader) + if err != nil { + return err + } + + err = f.Chmod(mode) + if err != nil { + return err + } + + return nil +} diff --git a/orchestration/src/sapsailing.com/orchestrator/agent/client_test.go b/orchestration/src/sapsailing.com/orchestrator/agent/client_test.go new file mode 100644 index 00000000000..2a588d6c164 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/agent/client_test.go @@ -0,0 +1,35 @@ +package agent + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "os" + "sapsailing.com/orchestrator/paths" +) + +func TestValidSSHConnection(t *testing.T) { + user := "root" + + privateKeyPath, _ := paths.GetKeyPair() + auth, err := KeyPairAuthFromPrivateKey(privateKeyPath) + + // Skip this test if the private key does not exist. + if os.IsNotExist(err) { + t.SkipNow() + } + + assert.NoError(t, err, "Loading the keyboard should not fail!") + + client := &AgentConnection{ + Ip: "sapsailing.com", + User: user, + Port: 22, + Auth: auth, + } + out, err := client.RunSimpleCommand("whoami") + + assert.NoError(t, err, "Command should not fail to execute") + assert.Equal(t, user, strings.TrimSpace(out)) +} diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch.go b/orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch.go new file mode 100644 index 00000000000..41001338857 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch.go @@ -0,0 +1,149 @@ +package aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go/service/elbv2" + log "github.com/sirupsen/logrus" + "sapsailing.com/orchestrator/configuration" +) + +func ConstructNameForAlarm(metricName string, targetGroupName string, nameSuffix string) string { + if nameSuffix != "" { + return metricName + "_" + targetGroupName + "_" + nameSuffix + } else { + return metricName + "_" + targetGroupName + } +} + +func newDefaultElbUnhealthyHostsAlarm(svc *cloudwatch.CloudWatch, tg *elbv2.TargetGroup, nameSuffix string) (string, error) { + awsConfig, err := configuration.LoadAWSConfig() + if err != nil { + return "", err + } + if awsConfig.SnsTopicARN == "" { + return "", fmt.Errorf("error, SnsTopicArn was not found, maybe check config") + } + + sess, err := NewSession() + if err != nil { + return "", err + } + + elbSvc := elbv2.New(sess) + + //get lb + lb, err := GetLbFromName(elbSvc, awsConfig.ElbName) + + //concat alarm name + alarmName := ConstructNameForAlarm(MetricUnHealthyHostCount, *tg.TargetGroupName, nameSuffix) + + if err != nil { + log.Warningf("could not get lb with name %s", awsConfig.ElbName) + return "", err + } + _, err = svc.PutMetricAlarm(&cloudwatch.PutMetricAlarmInput{ + AlarmName: aws.String(alarmName), + ComparisonOperator: aws.String(cloudwatch.ComparisonOperatorGreaterThanOrEqualToThreshold), + EvaluationPeriods: aws.Int64(3), + MetricName: aws.String(MetricUnHealthyHostCount), + Namespace: aws.String("AWS/ApplicationELB"), + Period: aws.Int64(60), + Statistic: aws.String(cloudwatch.StatisticAverage), + Threshold: aws.Float64(1.0), + ActionsEnabled: aws.Bool(true), + AlarmDescription: aws.String("Alarm when UnHealthyHostCount exceeds one for three data points"), + Unit: aws.String(cloudwatch.StandardUnitSeconds), + TreatMissingData: aws.String("missing"), + + // Send out a notification through SNS + AlarmActions: []*string{ + aws.String(awsConfig.SnsTopicARN), + }, + OKActions: []*string{ + aws.String(awsConfig.SnsTopicARN), + }, + + Dimensions: []*cloudwatch.Dimension{ + { + Name: aws.String("Load­Balancer"), + Value: aws.String(*lb.LoadBalancerArn), + }, + { + Name: aws.String("Target­Group"), + Value: aws.String(*tg.TargetGroupArn), + }, + }, + }) + if err != nil { + log.Warningf("failed PutMetricAlarm with name %s: %s", alarmName, err) + return "", err + } + return alarmName, nil +} + +func enableAlarm(svc *cloudwatch.CloudWatch, tg *elbv2.TargetGroup, nameSuffix string) error { + name, err := newDefaultElbUnhealthyHostsAlarm(svc, tg, nameSuffix) + if err != nil { + log.Warningf("could not create default alarm: %s", err) + return err + } + _, err = svc.EnableAlarmActions(&cloudwatch.EnableAlarmActionsInput{ + AlarmNames: []*string{ + aws.String(name), + }, + }) + if err != nil { + log.Warningf("could not enable alarm: %s", err) + return err + } + return nil +} + +func deleteAlarm(svc *cloudwatch.CloudWatch, name string) error { + _, err := svc.DeleteAlarms(&cloudwatch.DeleteAlarmsInput{ + AlarmNames: []*string{ + aws.String(name), + }, + }) + if err != nil { + log.Warningf("could not delete alarm with name %s", name) + return err + } + log.Debugf("successfully deleted alarm with name %s", name) + return nil +} + +func CreateDefaultTgAlarm(tg *elbv2.TargetGroup, nameSuffix string) error { + sess, err := NewSession() + if err != nil { + return err + } + svc := cloudwatch.New(sess) + err = enableAlarm(svc, tg, nameSuffix) + if err != nil { + log.Warningf("could not enable alarm for target group %s: %s", string(*tg.TargetGroupArn), err) + return err + } + + log.Debugf("successfully added alarm for target group %s", string(*tg.TargetGroupName)) + return nil +} + +func DeleteDefaultLbAlarm(tg *elbv2.TargetGroup, nameSuffix string) error { + sess, err := NewSession() + if err != nil { + return err + } + + svc := cloudwatch.New(sess) + + alarmName := ConstructNameForAlarm(MetricUnHealthyHostCount, *tg.TargetGroupName, nameSuffix) + err = deleteAlarm(svc, alarmName) + if err != nil { + return err + } + return nil +} diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch_test.go b/orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch_test.go new file mode 100644 index 00000000000..0fcd6bc7867 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch_test.go @@ -0,0 +1,67 @@ +package aws + +import ( + "math/rand" + "testing" + "time" + + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/stretchr/testify/require" + "sapsailing.com/common" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func TestCreateDeleteAlarm(t *testing.T) { + var event common.EventSpec + event.Hostname = common.RandStringRunes(5) + event.DisplayName = common.RandStringRunes(5) + var alarmRandom = common.RandStringRunes(5) + + //integration test, as the elbv2 api does not provide a dryRun option + tgRole := RolesMaster + tgName := ConstructNameForTargetGroup(tgRole, event) + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + config := &TargetGroupConfig{ + Name: tgName, + Port: TargetGroupPort, + Protocol: "HTTPS", + VpcId: "vpc-14b3477f", + Tags: targetGroupTags{ + HostName: event.Hostname, + Role: tgRole, + }, + HealthCheck: targetGroupHealthCheck{ + Protocol: "HTTPS", + Path: "/index.html", + Port: TargetGroupHealthCheckPort, + HealthyThresholdCount: 2, + UnhealthyThresholdCount: 2, + Timeout: 4, + Interval: 5, + SuccessCode: "200", + }, + } + + //create + out, err := createTargetGroup(svc, config) + require.NoError(t, err) + + //create alarm + err = CreateDefaultTgAlarm(out, "testAlarm"+alarmRandom) + require.NoError(t, err) + + //delete alarm + err = DeleteDefaultLbAlarm(out, "testAlarm"+alarmRandom) + require.NoError(t, err) + + //delete tg + require.NoError(t, DeleteTargetGroup(svc, *out.TargetGroupArn)) + +} diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/convert.go b/orchestration/src/sapsailing.com/orchestrator/aws/convert.go new file mode 100644 index 00000000000..c910c0c2d93 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/convert.go @@ -0,0 +1,130 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/sirupsen/logrus" + "strings" +) + +func StatusFromAwsName(s *string) InstanceStatus { + if s == nil { + logrus.Warn("No status given", *s) + return InstanceStatusUnknown + } + switch strings.ToUpper(*s) { + case string(InstanceStatusPassed): + return InstanceStatusPassed + case string(InstanceStatusFailed): + return InstanceStatusFailed + case string(InstanceStatusRunning): + return InstanceStatusRunning + case string(InstanceStatusInitializing): + return InstanceStatusInitializing + case string(InstanceStatusInsufficientData): + return InstanceStatusInsufficientData + case string(InstanceStatusPending): + return InstanceStatusPending + case string(InstanceStatusShuttingDown): + return InstanceStatusShuttingDown + case string(InstanceStatusTerminated): + return InstanceStatusTerminated + case string(InstanceStatusStopping): + return InstanceStatusStopping + case string(InstanceStatusStopped): + return InstanceStatusStopped + default: + logrus.Warn("Unknown status: %s", *s) + return InstanceStatusUnknown + } +} + +func (s Status) EitherStatus() InstanceStatus { + if s.Instance == InstanceStatusUnknown { + return s.System + } else { + return s.Instance + } +} + +func TagsMapFromAws(tags []*ec2.Tag) map[string]string { + tagMap := map[string]string{} + for _, e := range tags { + tagMap[strings.ToLower(aws.StringValue(e.Key))] = aws.StringValue(e.Value) + } + return tagMap +} + +func TagsMapToAws(tagMap map[string]string) []*ec2.Tag { + var tags []*ec2.Tag + for name, value := range tagMap { + tags = append(tags, &ec2.Tag{Key: aws.String(name), Value: aws.String(value)}) + } + return tags +} + +func awsInstanceToDescription(instance *ec2.Instance) Description { + return Description{ + Id: aws.StringValue(instance.InstanceId), + VpcId: aws.StringValue(instance.VpcId), + SubnetId: aws.StringValue(instance.SubnetId), + PrivateIp: aws.StringValue(instance.PrivateIpAddress), + PublicIp: aws.StringValue(instance.PublicIpAddress), + PublicHostname: aws.StringValue(instance.PublicDnsName), + Tags: TagsMapFromAws(instance.Tags), + } +} + +func awsStatusToStatus(status *ec2.InstanceStatus) Status { + return Status{ + Instance: StatusFromAwsName(status.InstanceStatus.Status), + System: StatusFromAwsName(status.SystemStatus.Status), + } +} + +func AllDescriptionsFromReservation(res *ec2.Reservation) []Description { + if res == nil { + return []Description{} + } + descs := make([]Description, len(res.Instances)) + for i, instance := range res.Instances { + descs[i] = awsInstanceToDescription(instance) + } + + return descs +} + +func AllStatusesFromInstanceStatus(statuses []*ec2.InstanceStatus) []Status { + if statuses == nil { + return []Status{} + } + outStatuses := make([]Status, len(statuses)) + for i, status := range statuses { + outStatuses[i] = awsStatusToStatus(status) + } + return outStatuses +} + +func InstanceRoleFromString(s string) InstanceRole { + switch strings.ToLower(s) { + case string(RoleSingleMaster): + return RoleSingleMaster + case string(RoleMultiMaster): + return RoleMultiMaster + case string(RoleSingleReplica): + return RoleSingleReplica + default: + return RoleUnknown + } +} + +func TargetGroupRoleFromString(s string) TargetGroupRole { + switch strings.ToLower(s) { + case string(RolesMaster): + return RolesMaster + case string(RolesDefault): + return RolesDefault + default: + return RolesDefault + } +} diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/data.go b/orchestration/src/sapsailing.com/orchestrator/aws/data.go new file mode 100644 index 00000000000..e59b96e8cf6 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/data.go @@ -0,0 +1,129 @@ +package aws + +type InstanceStatus string + +const ( + InstanceStatusCreating InstanceStatus = "CREATING" + InstanceStatusConfiguring InstanceStatus = "CONFIGURING" + InstanceStatusPending InstanceStatus = "PENDING" + InstanceStatusRunning InstanceStatus = "RUNNING" + InstanceStatusShuttingDown InstanceStatus = "SHUTTING-DOWN" + InstanceStatusTerminated InstanceStatus = "TERMINATED" + InstanceStatusStopping InstanceStatus = "STOPPING" + InstanceStatusStopped InstanceStatus = "STOPPED" + InstanceStatusNotFound InstanceStatus = "NOTFOUND" + InstanceStatusNotReachable InstanceStatus = "NOTREACHABLE" + InstanceStatusPassed InstanceStatus = "PASSED" + InstanceStatusFailed InstanceStatus = "FAILED" + InstanceStatusInitializing InstanceStatus = "INITIALIZING" + InstanceStatusInsufficientData InstanceStatus = "INSUFFICIENTDATA" + InstanceStatusUnknown InstanceStatus = "-" +) + +type InstanceRole string + +const ( + RoleSingleMaster InstanceRole = "single-master" + RoleMultiMaster InstanceRole = "multi-master" + RoleSingleReplica InstanceRole = "single-replica" + RoleUnknown InstanceRole = "-" +) + +type TargetGroupRole string + +const ( + RolesDefault TargetGroupRole = "default" + RolesMaster TargetGroupRole = "master" +) + +const ( + TargetGroupNamePrefix = "S" + InstanceNamePrefix = "S" +) + +const ( + TargetHealthStateInitial = "initial" + TargetHealthStateHealthy = "healthy" + TargetHealthStateUnhealthy = "unhealthy" + TargetHealthStateUnused = "unused" + TargetHealthStateDraining = "draining" + TargetHealthStateUnavailable = "unavailable" +) + +const ( + InstanceTagRole = "instance-role" + InstanceTagName = "Name" + TargetGroupTagRole = "targetgroup-role" + TagHostName = "hostname" + TagEventName = "eventname" +) + +type Template struct { + Name string + + Vpc string + Subnet string + + Image string + InstanceType string `json:"instance-type"` + Count int64 + SecurityGroups []string `json:"security-groups"` + Tags map[string]string + UserData string `json:"user-data"` +} + +type Status struct { + Instance InstanceStatus + System InstanceStatus +} + +type Description struct { + Id string + VpcId string + SubnetId string + AvailibilityZone string + PublicIp string + PrivateIp string + Tags map[string]string + PublicHostname string +} + +const ( + ApachePort uint16 = 443 + TargetGroupHealthCheckPort uint16 = 443 + TargetGroupPort uint16 = 443 + LbListenerPort uint16 = 443 +) + +const ( + RemoteAgentPath = "/tmp/sailing-agent" +) + +type targetGroupTags struct { + HostName string + Role TargetGroupRole +} + +type targetGroupHealthCheck struct { + Protocol string + Path string + Port uint16 + HealthyThresholdCount int64 + UnhealthyThresholdCount int64 + Timeout int64 + Interval int64 + SuccessCode string +} + +type TargetGroupConfig struct { + Name string + Port uint16 + Protocol string + VpcId string + Tags targetGroupTags + HealthCheck targetGroupHealthCheck +} + +const ( + MetricUnHealthyHostCount string = "UnHealthyHostCount" +) diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/instance.go b/orchestration/src/sapsailing.com/orchestrator/aws/instance.go new file mode 100644 index 00000000000..8868dac8307 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/instance.go @@ -0,0 +1,221 @@ +package aws + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "sapsailing.com/orchestrator/configuration" + + log "github.com/sirupsen/logrus" + "sapsailing.com/common" +) + +func ConstructInstanceTagName(name string, role string) string { + return InstanceNamePrefix + " " + name + " " + role +} + +func NewSession() (*session.Session, error) { + awsConfig, err := configuration.LoadAWSConfig() + if err != nil { + return nil, err + } + + sess, err := session.NewSession(&aws.Config{ + Region: aws.String(awsConfig.DefaultRegion), + }) + + if err != nil { + log.Warningf("failed to get aws session: %s", err) + return nil, err + } + + return sess, nil +} + +func LaunchTemplate(template *Template, events []common.EventSpec, role InstanceRole, dryRun bool) ([]Description, error) { + sess, err := NewSession() + + if err != nil { + return nil, err + } + + svc := ec2.New(sess) + + //Build tags + + var groups []*string = nil + if len(template.SecurityGroups) > 0 { + groups = aws.StringSlice(template.SecurityGroups) + } + + tags := template.Tags + tags[InstanceTagRole] = string(role) + + //use first event as primary server name + tags[InstanceTagName] = ConstructInstanceTagName(events[0].DisplayName, string(role)) + if len(events) == 1 { + tags[TagEventName] = events[0].DisplayName + tags[TagHostName] = events[0].Hostname + } else { + for _, event := range events { + tags[TagEventName] = fmt.Sprintf("%s,%s", tags[TagEventName], event) + tags[TagHostName] = fmt.Sprintf("%s,%s", tags[TagHostName], event) + } + } + + tagSpec := ec2.TagSpecification{ + ResourceType: aws.String("instance"), + Tags: TagsMapToAws(tags), + } + + //Build instance + runInstanceInput := &ec2.RunInstancesInput{ + DryRun: aws.Bool(dryRun), + ImageId: aws.String(template.Image), + InstanceType: aws.String(template.InstanceType), + SubnetId: aws.String(template.Subnet), + + MinCount: aws.Int64(template.Count), + MaxCount: aws.Int64(template.Count), + SecurityGroupIds: groups, + UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(template.UserData))), + TagSpecifications: []*ec2.TagSpecification{&tagSpec}, + } + + log.Infof("launching instance with details: %s", runInstanceInput) + reservation, err := svc.RunInstances(runInstanceInput) + if err != nil { + return nil, err + } + + return AllDescriptionsFromReservation(reservation), nil +} + +func makeDescribeInput(id string, dryRun bool) *ec2.DescribeInstancesInput { + return &ec2.DescribeInstancesInput{ + InstanceIds: []*string{ + aws.String(id), + }, + DryRun: aws.Bool(dryRun), + } +} + +func makeDescribeStatusInput(id string, dryRun bool) *ec2.DescribeInstanceStatusInput { + return &ec2.DescribeInstanceStatusInput{ + InstanceIds: []*string{ + aws.String(id), + }, + DryRun: aws.Bool(dryRun), + } +} + +func waitUntilInstanceRunning(svc *ec2.EC2, id string, dryRun bool) error { + input := makeDescribeInput(id, dryRun) + err := svc.WaitUntilInstanceExists(input) + if err != nil { + return err + } + + return svc.WaitUntilInstanceRunning(input) +} + +func waitUntilInstanceStatusOk(svc *ec2.EC2, id string, dryRun bool) error { + input := makeDescribeStatusInput(id, dryRun) + err := svc.WaitUntilInstanceStatusOk(input) + if err != nil { + return err + } + return svc.WaitUntilInstanceStatusOk(input) +} + +func WaitUntilInstanceUsable(id string, dryRun bool) error { + sess, err := NewSession() + if err != nil { + return err + } + + svc := ec2.New(sess) + + err = waitUntilInstanceRunning(svc, id, dryRun) + if err != nil { + return err + } + + err = waitUntilInstanceStatusOk(svc, id, dryRun) + if err != nil { + return err + } + + return nil +} + +func DescribeInstance(id string, dryRun bool) (*Description, error) { + log.Debugf("trying to describe instance %s", id) + + sess, err := NewSession() + if err != nil { + return nil, err + } + svc := ec2.New(sess) + + //Get Filter criteria + params := &ec2.DescribeInstancesInput{ + InstanceIds: []*string{ + aws.String(id), + }, + DryRun: aws.Bool(dryRun), + } + //Describe instance + descriptionResult, err := svc.DescribeInstances(params) + if err != nil { + log.Warningf("requested instance %s could not be described", id) + return nil, err + } + + descriptions := AllDescriptionsFromReservation(descriptionResult.Reservations[0]) + + // lookup by ID returns exactly on result + return &descriptions[0], nil +} + +func TemplateFromFile(filename string, userData map[string]string) (*Template, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + template := &Template{} + + err = json.Unmarshal(data, &template) + if err != nil { + return nil, err + } + + for key, value := range userData { + template.UserData += key + "=" + value + "\n" + } + + return template, nil +} + +func EditTags(instanceId string, tags map[string]string, dryRun bool) (*ec2.CreateTagsOutput, error) { + sess, err := NewSession() + if err != nil { + return nil, err + } + + svc := ec2.New(sess) + + awsTags := ec2.CreateTagsInput{ + DryRun: aws.Bool(dryRun), + Resources: []*string{&instanceId}, + Tags: TagsMapToAws(tags), + } + + return svc.CreateTags(&awsTags) +} diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/instance_test.go b/orchestration/src/sapsailing.com/orchestrator/aws/instance_test.go new file mode 100644 index 00000000000..2f7d15629a9 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/instance_test.go @@ -0,0 +1,89 @@ +package aws + +import ( + "testing" + + "sapsailing.com/orchestrator/configuration" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sapsailing.com/common" + "sapsailing.com/orchestrator/paths" +) + +func createData() (map[string]string, *Template, map[string]string) { + tags := map[string]string{ + "k": "v", + } + + awsConfig, _ := configuration.LoadAWSConfig() + + //Prepare request to get SAPSailing imageId + sess := session.Must(session.NewSession(&aws.Config{ + Region: aws.String(awsConfig.DefaultRegion), + })) + + svc := ec2.New(sess) + + filter := &ec2.Filter{ + Name: aws.String("name"), + Values: []*string{aws.String("SAP Sailing*")}, //TODO get latest via tag Version and Role + } + + imageInput := &ec2.DescribeImagesInput{ + Filters: []*ec2.Filter{filter}, + } + + images, _ := svc.DescribeImages(imageInput) + + imageId := images.Images[0].ImageId + + instance := Template{ + Image: *imageId, + InstanceType: "t2.micro", + Count: 1, + Tags: tags, + } + + userData := map[string]string{ + "Key1": "Value1", + "Key2": "Value2", + } + + return tags, &instance, userData +} + +func TestInstanceLaunch(t *testing.T) { + _, template, _ := createData() + _, err := LaunchTemplate(template, []common.EventSpec{{Hostname: "test", DisplayName: "test"}}, RoleSingleMaster, true) + + assert.Contains(t, err.Error(), "Request would have succeeded", "Should not fail in dry run") +} + +func TestInstanceEditTags(t *testing.T) { + tags, _, _ := createData() + _, err := EditTags("", tags, true) + + assert.Contains(t, err.Error(), "Request would have succeeded", "Should not fail in dry run") +} + +func TestInstanceConstruction(t *testing.T) { + _, _, userData := createData() + instance, err := TemplateFromFile(paths.GetInstanceTemplate("master"), userData) + assert.NoError(t, err, "Reading the config should not fail.") + + assert.Equal(t, int64(1), instance.Count) +} + +func TestDescribeInstance(t *testing.T) { + desc, err := DescribeInstance("123", true) + require.Error(t, err, "Describe dryrun must return an error") + assert.Contains(t, err.Error(), "Request would have succeeded", "Should not fail in dry run") + + assert.Nil(t, desc) +} + +//TODO: more extensive testing of Describe() diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/lb.go b/orchestration/src/sapsailing.com/orchestrator/aws/lb.go new file mode 100644 index 00000000000..8de60050027 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/lb.go @@ -0,0 +1,662 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws/awserr" + "sapsailing.com/common" + "sapsailing.com/orchestrator/configuration" + "sort" + "strconv" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/elbv2" + log "github.com/sirupsen/logrus" +) + +func ConstructNameForTargetGroup(role TargetGroupRole, event common.EventSpec) string { + if role == RolesMaster { + return TargetGroupNamePrefix + "-" + event.Hostname + "-" + string(role) + } else { + return TargetGroupNamePrefix + "-" + event.Hostname + } + +} + +func ensureTargetGroup(svc *elbv2.ELBV2, vpcId string, event common.EventSpec, role TargetGroupRole) (*elbv2.TargetGroup, error) { + tgName := ConstructNameForTargetGroup(role, event) + tg, err := GetTargetGroup(svc, tgName) + if err != nil { + log.Warningf("could not get target group: %s", err) + return nil, err + } + + if len(tg) == 1 { + log.Debugf("target group found %s", string(*tg[0].TargetGroupArn)) + return tg[0], nil + } + + config := &TargetGroupConfig{ + Name: tgName, + Port: 443, + Protocol: "HTTPS", + VpcId: vpcId, + Tags: targetGroupTags{ + HostName: event.Hostname, + Role: role, + }, + HealthCheck: targetGroupHealthCheck{ + Protocol: "HTTPS", + Path: "/index.html", + Port: 443, + HealthyThresholdCount: 2, + UnhealthyThresholdCount: 2, + Timeout: 4, + Interval: 5, + SuccessCode: "200", + }, + } + + out, err := createTargetGroup(svc, config) + if err != nil { + log.Warningf("could not create new target %s group: %s", config.Name, err) + return nil, err + } + + log.Debugf("successfully created new target group %s", config.Name) + return out, nil +} + +func GetTargetGroup(svc *elbv2.ELBV2, name string) ([]*elbv2.TargetGroup, error) { + describeInput := &elbv2.DescribeTargetGroupsInput{ + Names: []*string{aws.String(name)}, + } + + log.Debugf("searching a target group named %s", name) + describeOutput, err := svc.DescribeTargetGroups(describeInput) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case elbv2.ErrCodeTargetGroupNotFoundException: + return nil, nil + default: + log.Warningf("could not describe target groups: %s", aerr.Error()) + return nil, err + } + } + + } + + return describeOutput.TargetGroups, nil +} + +func createTargetGroup(svc *elbv2.ELBV2, config *TargetGroupConfig) (*elbv2.TargetGroup, error) { + createInput := &elbv2.CreateTargetGroupInput{ + Name: aws.String(config.Name), + Port: aws.Int64(int64(config.Port)), + Protocol: aws.String(config.Protocol), + VpcId: aws.String(config.VpcId), + HealthCheckIntervalSeconds: aws.Int64(config.HealthCheck.Interval), + HealthCheckPath: aws.String(config.HealthCheck.Path), + HealthCheckPort: aws.String(fmt.Sprintf("%d", config.HealthCheck.Port)), + HealthCheckProtocol: aws.String(config.HealthCheck.Protocol), + HealthCheckTimeoutSeconds: aws.Int64(config.HealthCheck.Timeout), + HealthyThresholdCount: aws.Int64(config.HealthCheck.HealthyThresholdCount), + UnhealthyThresholdCount: aws.Int64(config.HealthCheck.UnhealthyThresholdCount), + Matcher: &elbv2.Matcher{HttpCode: aws.String(config.HealthCheck.SuccessCode)}, + } + + //create the new target group + out, err := svc.CreateTargetGroup(createInput) + if err != nil { + log.Warningf("new target group %s could not be created", config.Name) + return nil, err + } + + //set tags to target group + err1 := setTargetGroupTags(svc, out.TargetGroups[0], &config.Tags) + if err1 != nil { + log.Warningf("tags for target group %s could not be added", config.Name) + return nil, err1 + } + + //set attributes to target group + _, err2 := modifyTargetGroupAttribute(svc, *out.TargetGroups[0].TargetGroupArn) + if err2 != nil { + log.Warningf("attributes for target group %s could not be modified", config.Name) + } + + return out.TargetGroups[0], nil +} + +func setTargetGroupTags(svc *elbv2.ELBV2, tg *elbv2.TargetGroup, tags *targetGroupTags) error { + input := &elbv2.AddTagsInput{ + ResourceArns: []*string{ + aws.String(*tg.TargetGroupArn), + }, + Tags: []*elbv2.Tag{ + { + Key: aws.String(TagHostName), + Value: aws.String(tags.HostName), + }, + { + Key: aws.String(TargetGroupTagRole), + Value: aws.String(string(tags.Role)), + }, + }, + } + + _, err := svc.AddTags(input) + if err != nil { + return err + } + + return nil +} + +func getTagsOfTargetGroup(svc *elbv2.ELBV2, tg *elbv2.TargetGroup) (map[string]string, error) { + input := &elbv2.DescribeTagsInput{ + ResourceArns: aws.StringSlice([]string{*tg.TargetGroupArn}), + } + + res, err := svc.DescribeTags(input) + if err != nil { + return nil, err + } + + tags := map[string]string{} + if len(res.TagDescriptions) == 0 { + return tags, nil + } + for _, t := range res.TagDescriptions[0].Tags { + tags[*t.Key] = *t.Value + } + + return tags, nil +} + +func getEventOfTargetGroup(svc *elbv2.ELBV2, tg *elbv2.TargetGroup) (common.EventSpec, error) { + var event common.EventSpec + + tags, err := getTagsOfTargetGroup(svc, tg) + if err != nil { + log.Warningf("could not get tags of target group %s: %s", string(*tg.TargetGroupArn), err) + return event, err + } + + for idx, tag := range tags { + if idx == TagHostName { + event.Hostname = tag + } + } + + if event.Hostname != "" { + return event, nil + } else { + return event, fmt.Errorf("required tags %s seem to be empty", TagHostName) + } +} + +func getRoleOfTargetGroup(svc *elbv2.ELBV2, tg *elbv2.TargetGroup) (TargetGroupRole, error) { + var role TargetGroupRole + + tags, err := getTagsOfTargetGroup(svc, tg) + if err != nil { + log.Warningf("could not get tags of target group %s: %s", string(*tg.TargetGroupArn), err) + return "", err + } + + for idx, tag := range tags { + if idx == TargetGroupTagRole { + role = TargetGroupRoleFromString(tag) + } + } + + if role != "" { + return role, nil + } else { + return "", fmt.Errorf("required tag %s seem to be empty", TargetGroupTagRole) + } +} + +func modifyTargetGroupAttribute(svc *elbv2.ELBV2, arn string) (*elbv2.ModifyTargetGroupAttributesOutput, error) { + input := &elbv2.ModifyTargetGroupAttributesInput{ + Attributes: []*elbv2.TargetGroupAttribute{ + { + Key: aws.String("deregistration_delay.timeout_seconds"), + Value: aws.String("30"), + }, + }, + TargetGroupArn: aws.String(arn), + } + + return svc.ModifyTargetGroupAttributes(input) +} + +func getListenersFromLb(svc *elbv2.ELBV2, lb *elbv2.LoadBalancer) ([]*elbv2.Listener, error) { + input := &elbv2.DescribeListenersInput{ + LoadBalancerArn: aws.String(*lb.LoadBalancerArn), + } + + result, err := svc.DescribeListeners(input) + if err != nil { + log.Warningf("error with DescribeListeners: %s", err) + return nil, err + } + + log.Debugf("found listeners for lb %s", string(*lb.LoadBalancerArn)) + return result.Listeners, nil +} + +func GetLbFromName(svc *elbv2.ELBV2, name string) (*elbv2.LoadBalancer, error) { + input := &elbv2.DescribeLoadBalancersInput{ + Names: []*string{aws.String(name)}, + } + + result, err := svc.DescribeLoadBalancers(input) + if err != nil { + log.Warningf("could not describe lb with name %s", name) + return nil, err + } + + log.Debugf("found lb with arn %s", string(*result.LoadBalancers[0].LoadBalancerArn)) + return result.LoadBalancers[0], nil +} + +func getRulesFromListener(svc *elbv2.ELBV2, listener *elbv2.Listener) ([]*elbv2.Rule, error) { + input := &elbv2.DescribeRulesInput{ + ListenerArn: aws.String(string(*listener.ListenerArn)), + } + + result, err := svc.DescribeRules(input) + if err != nil { + log.Warningf("could not describe rules for listener %s: err", string(*listener.ListenerArn), err) + return nil, err + } + return result.Rules, nil +} + +func getRuleForHostname(svc *elbv2.ELBV2, listener *elbv2.Listener, event common.EventSpec) (*elbv2.Rule, error) { + rules, err := getRulesFromListener(svc, listener) + if err != nil { + return nil, err + } + + if len(rules) == 0 { + log.Warningf("no rules for listener %s found", string(*listener.ListenerArn)) + return nil, nil + } + + domain := common.HostnameToDomain(event.Hostname) + + for _, rule := range rules { + //default rule does not have values... + if len(rule.Conditions) > 0 { + if rule.Conditions[0].Values[0] == &domain { + return rule, nil + } + } + } + + return nil, fmt.Errorf("could not find rule for hostname %s in listener %s", event.Hostname, *listener.LoadBalancerArn) +} + +func GetRuleForEvent(svc *elbv2.ELBV2, listener *elbv2.Listener, event common.EventSpec) (*elbv2.Rule, error) { + out, err := getRuleForHostname(svc, listener, event) + if err != nil { + log.Warningf("could not find rule for event %s", event.Hostname) + return nil, err + } + log.Debugf("found rule %s for event %s", string(*out.RuleArn), event.Hostname) + return out, nil +} + +func DeleteTargetGroup(svc *elbv2.ELBV2, arn string) error { + input := &elbv2.DeleteTargetGroupInput{ + TargetGroupArn: aws.String(arn), + } + + _, err := svc.DeleteTargetGroup(input) + if err != nil { + log.Warningf("could not delete target group with arn %s", arn) + return err + } + + log.Debugf("successfully deleted target group %s", arn) + return nil +} + +func AddTargetToGroup(event common.EventSpec, role TargetGroupRole, target Description) (*elbv2.TargetGroup, error) { + sess, err := NewSession() + if err != nil { + return nil, err + } + + svc := elbv2.New(sess) + + out, err := ensureTargetGroup(svc, target.VpcId, event, role) + if err != nil { + log.Warningf("could not ensure target group for event %s", event.Hostname) + return nil, err + } + + input := &elbv2.RegisterTargetsInput{ + TargetGroupArn: aws.String(*out.TargetGroupArn), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(target.Id), + Port: aws.Int64(int64(ApachePort)), + }, + }, + } + + _, err = svc.RegisterTargets(input) + if err != nil { + log.Warningf("could not register target %s to target group %s", target.Id, string(*out.TargetGroupName)) + } else { + log.Debugf("successfully added target %s to target group %s", target.Id, string(*out.TargetGroupName)) + } + + tg, err := GetTargetGroup(svc, ConstructNameForTargetGroup(role, event)) + if err != nil { + log.Warningf("could not get the just added target group: %s", err) + } + + return tg[0], nil +} + +func RemoveTargetFromTargetGroup(target *ec2.Instance, tg *elbv2.TargetGroup) (*elbv2.DeregisterTargetsOutput, error) { + sess, err := NewSession() + if err != nil { + return nil, err + } + + svc := elbv2.New(sess) + + input := &elbv2.DeregisterTargetsInput{ + TargetGroupArn: aws.String(*tg.TargetGroupArn), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(*target.InstanceId), + }, + }, + } + + result, err := svc.DeregisterTargets(input) + if err != nil { + log.Warningf("could not de-register target %s from lb %s", string(*target.InstanceId), string(*tg.TargetGroupArn)) + return nil, err + } + + log.Debugf("successfully removed target %s from target group %s", string(*target.InstanceId), string(*tg.TargetGroupArn)) + return result, nil +} + +func getHighestPriorityFromListener(svc *elbv2.ELBV2, listener *elbv2.Listener) (int64, error) { + var priorities []int + + rules, err := getRulesFromListener(svc, listener) + if err != nil { + log.Warningf("could not get rules from listener %s", string(*listener.ListenerArn)) + return 0, err + } + + for _, rule := range rules { + priority, _ := strconv.Atoi(aws.StringValue(rule.Priority)) + priorities = append(priorities, priority) + } + + sort.Ints(priorities) + + log.Debugf("found a rule with highest priority") + return int64(priorities[len(priorities)-1]), nil +} + +func createRuleInListener(svc *elbv2.ELBV2, listener *elbv2.Listener, tg *elbv2.TargetGroup, role TargetGroupRole) (common.EventSpec, error) { + var domain string + + event, err := getEventOfTargetGroup(svc, tg) + if err != nil { + log.Warningf("could not get event from target group %s: %s", string(*tg.TargetGroupArn), err) + return event, err + } + + highestPriority, err := getHighestPriorityFromListener(svc, listener) + if err != nil { + return event, err + } + priority := highestPriority + 1 + + if role == RolesDefault { + domain = common.HostnameToDomain(event.Hostname) + } else if role == RolesMaster { + domain = common.HostnameToMasterDomain(event.Hostname) + } else { + return event, fmt.Errorf("unknown role type %s", role) + } + + input := &elbv2.CreateRuleInput{ + Actions: []*elbv2.Action{ + { + TargetGroupArn: aws.String(*tg.TargetGroupArn), + Type: aws.String("forward"), + }, + }, + Conditions: []*elbv2.RuleCondition{ + { + Field: aws.String("host-header"), + Values: []*string{ + aws.String(domain), + }, + }, + }, + ListenerArn: aws.String(*listener.ListenerArn), + Priority: aws.Int64(priority), + } + + _, err = svc.CreateRule(input) + + if err != nil { + log.Warningf("could not create rule for event %s in listener %s", event.Hostname, string(*listener.ListenerArn)) + return event, err + } + + log.Debugf("successfully created rule for event %s in listener %s", event.Hostname, string(*listener.ListenerArn)) + return event, nil +} + +func CreateRulesInLbForTg(tg *elbv2.TargetGroup, role TargetGroupRole) (common.EventSpec, error) { + var listener *elbv2.Listener + var event common.EventSpec + + sess, err := NewSession() + if err != nil { + return event, err + } + + svc := elbv2.New(sess) + + awsConfig, err := configuration.LoadAWSConfig() + if err != nil { + return event, err + } + + lb, err := GetLbFromName(svc, awsConfig.ElbName) + if err != nil { + return event, err + } + + listeners, err := getListenersFromLb(svc, lb) + if err != nil { + return event, err + } + + found := false + for _, list := range listeners { + if aws.Int64Value(list.Port) == int64(LbListenerPort) { + listener = list + found = true + log.Debugf("found a matching listener on port %d", *list.Port) + break + } else { + found = false + log.Debugf("did not find a matching listener in %s, trying next", string(*list.ListenerArn)) + } + } + + if !found { + return event, fmt.Errorf("could not find https(443) lb listener for %s", string(*lb.LoadBalancerArn)) + } + + _, err = createRuleInListener(svc, listener, tg, role) + if err != nil { + log.Warningf("could not create rule in listener: %s", err) + return event, err + } + log.Debugf("successfully created rule in lb %s", string(*lb.LoadBalancerArn)) + return event, nil +} + +func DeleteEventRuleFromListener(listener *elbv2.Listener, event common.EventSpec) error { + sess, err := NewSession() + if err != nil { + return err + } + + svc := elbv2.New(sess) + + arn, err := GetRuleForEvent(svc, listener, event) + if err != nil { + return err + } + + input := &elbv2.DeleteRuleInput{ + RuleArn: aws.String(*arn.RuleArn), + } + + _, err = svc.DeleteRule(input) + if err != nil { + log.Warningf("could not delete rule for event %s", event) + return err + } + + log.Debugf("successfully deleted rule %s from listener %s", string(*arn.RuleArn), string(*listener.ListenerArn)) + return nil +} + +func GetMetadataForTg(tg *elbv2.TargetGroup) (string, TargetGroupRole, error) { + var domain string + + sess, err := NewSession() + if err != nil { + return "", "", err + } + + svc := elbv2.New(sess) + + event, err := getEventOfTargetGroup(svc, tg) + if err != nil { + log.Warningf("could not get event of target group: %s", err) + return "", "", nil + } + + role, err := getRoleOfTargetGroup(svc, tg) + if err != nil { + log.Warningf("could not get role of target group: %s", err) + return "", "", nil + } + + if role == RolesMaster { + domain = common.HostnameToMasterDomain(event.Hostname) + } else if role == RolesDefault { + domain = common.HostnameToDomain(event.Hostname) + } else { + return "", "", fmt.Errorf("wrong role of target group, got %s", role) + } + + return domain, role, nil +} + +func WaitUntilTargetGroupUsable(tg *elbv2.TargetGroup, retries int) error { + log.Debugf("waiting for a healthy target in target group %s", string(*tg.TargetGroupArn)) + for i := 1; i <= retries; i++ { + healthy, err := isTargetGroupHealthy(tg) + if err != nil { + return err + } + + if !healthy { + time.Sleep(10 * time.Second) + } + + if (i == retries) && !(healthy) { + return fmt.Errorf("maximum number of retries reached, no healthy target found") + } + } + return nil +} + +func isTargetGroupHealthy(tg *elbv2.TargetGroup) (bool, error) { + sess, err := NewSession() + if err != nil { + return false, err + } + + svc := elbv2.New(sess) + + input := &elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(*tg.TargetGroupArn), + } + + result, err := svc.DescribeTargetHealth(input) + if err != nil { + log.Warningf("could not describe health of targets for target group %s: %s", string(*tg.TargetGroupArn), err) + return false, err + } + + //at least one target must be healthy to conclude target group is usable + found := false + for _, target := range result.TargetHealthDescriptions { + if found { + break + } + if aws.StringValue(target.TargetHealth.State) == TargetHealthStateHealthy { + log.Debugf("found healthy target %s in target group %s", string(*target.Target.Id), string(*tg.TargetGroupArn)) + found = true + } else { + log.Debugf("found target %s in target group %s with state %s", string(*target.Target.Id), string(*tg.TargetGroupArn), string(*target.TargetHealth.State)) + } + } + return found, nil +} + +func CreateTargetGroupsWithAlarm(events []common.EventSpec, roles []TargetGroupRole, target Description) []*elbv2.TargetGroup { + var tgs []*elbv2.TargetGroup + + for _, event := range events { + for _, role := range roles { + tg, err := AddTargetToGroup(event, role, target) + if err != nil { + log.Errorf("Failed to add target %s to group: %s", target.Id, err) + //TODO: could remove target group again, if created before + continue + } + + _, err = CreateRulesInLbForTg(tg, role) + if err != nil { + log.Errorf("Failed to add rule to target groups: %s", err) + //TODO: could remove target group again, if created before + continue + } + + err = CreateDefaultTgAlarm(tg, "") + if err != nil { + log.Warningf("Failed to add cloudwatch alarm for target group %s: %s", string(*tg.TargetGroupArn), err) + } + + tgs = append(tgs, tg) + } + } + return tgs +} diff --git a/orchestration/src/sapsailing.com/orchestrator/aws/lb_test.go b/orchestration/src/sapsailing.com/orchestrator/aws/lb_test.go new file mode 100644 index 00000000000..1ecfdd32cba --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/aws/lb_test.go @@ -0,0 +1,249 @@ +package aws + +import ( + "math/rand" + "testing" + "time" + + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sapsailing.com/common" + "sapsailing.com/orchestrator/configuration" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +var vpcId = "vpc-14b3477f" + +func TestCreateDeleteTargetGroup(t *testing.T) { + var event common.EventSpec + event.Hostname = common.RandStringRunes(5) + event.DisplayName = common.RandStringRunes(5) + + //integration test, as the elbv2 api does not provide a dryRun option + tgRole := RolesMaster + tgName := ConstructNameForTargetGroup(tgRole, event) + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + config := &TargetGroupConfig{ + Name: tgName, + Port: TargetGroupPort, + Protocol: "HTTPS", + VpcId: vpcId, + Tags: targetGroupTags{ + HostName: event.Hostname, + Role: tgRole, + }, + HealthCheck: targetGroupHealthCheck{ + Protocol: "HTTPS", + Path: "/index.html", + Port: TargetGroupHealthCheckPort, + HealthyThresholdCount: 2, + UnhealthyThresholdCount: 2, + Timeout: 4, + Interval: 5, + SuccessCode: "200", + }, + } + + //create + out, err := createTargetGroup(svc, config) + require.NoError(t, err) + + //describe + desc, err := GetTargetGroup(svc, tgName) + require.NoError(t, err) + assert.Equal(t, out.TargetGroupArn, desc[0].TargetGroupArn) + + //delete + require.NoError(t, DeleteTargetGroup(svc, *out.TargetGroupArn)) +} + +func TestEnsureCreateDeleteTargetGroup(t *testing.T) { + var event common.EventSpec + event.Hostname = common.RandStringRunes(5) + event.DisplayName = common.RandStringRunes(5) + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + //ensure created + out, err := ensureTargetGroup(svc, vpcId, event, RolesMaster) + assert.NoError(t, err) + + //delete + require.NoError(t, DeleteTargetGroup(svc, *out.TargetGroupArn)) +} + +func TestGetLbFromName(t *testing.T) { + awsConfig, err := configuration.LoadAWSConfig() + require.NoError(t, err) + + if awsConfig.ElbName == "" { + t.SkipNow() + } + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + name, err := GetLbFromName(svc, awsConfig.ElbName) + + require.NotEmpty(t, name) +} + +func TestFailGetLbFromName(t *testing.T) { + name := "NOTEXISTING" + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + _, err = GetLbFromName(svc, name) + + assert.Error(t, err) +} + +func TestGetListenersFromLb(t *testing.T) { + awsConfig, err := configuration.LoadAWSConfig() + require.NoError(t, err) + + if awsConfig.ElbName == "" { + t.SkipNow() + } + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + lb, err := GetLbFromName(svc, awsConfig.ElbName) + + listeners, err := getListenersFromLb(svc, lb) + + require.NotEmpty(t, listeners) +} + +func TestFailDeleteTargetGroup(t *testing.T) { + arn := "NOTEXISTING" + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + err = DeleteTargetGroup(svc, arn) + + assert.Error(t, err) +} + +func TestCreateRuleInLb(t *testing.T) { + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + //just use a target group that should be always there + tg, err := GetTargetGroup(svc, "HttpToHttpsForwarder") + + //assume that some random tg doesnt have the eventName tag set + _, err = CreateRulesInLbForTg(tg[0], RolesDefault) + assert.Error(t, err) +} + +func TestFailDeleteEventRuleFromListener(t *testing.T) { + awsConfig, err := configuration.LoadAWSConfig() + require.NoError(t, err) + + if awsConfig.ElbName == "" { + t.SkipNow() + } + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + lb, err := GetLbFromName(svc, awsConfig.ElbName) + + listeners, err := getListenersFromLb(svc, lb) + require.NotEmpty(t, listeners) + + var event common.EventSpec + event.Hostname = common.RandStringRunes(5) + event.DisplayName = common.RandStringRunes(5) + + //just take the first listener + listener := listeners[0] + err = DeleteEventRuleFromListener(listener, event) + assert.Error(t, err) +} + +func TestGetRulesFromListener(t *testing.T) { + awsConfig, err := configuration.LoadAWSConfig() + require.NoError(t, err) + + if awsConfig.ElbName == "" { + t.SkipNow() + } + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + lb, err := GetLbFromName(svc, awsConfig.ElbName) + + listeners, err := getListenersFromLb(svc, lb) + require.NotEmpty(t, listeners) + + _, err = getRulesFromListener(svc, listeners[0]) + require.NoError(t, err) +} + +func TestGetHighestPriorityFromListener(t *testing.T) { + awsConfig, err := configuration.LoadAWSConfig() + require.NoError(t, err) + + if awsConfig.ElbName == "" { + t.SkipNow() + } + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + lb, err := GetLbFromName(svc, awsConfig.ElbName) + + listeners, err := getListenersFromLb(svc, lb) + require.NotEmpty(t, listeners) + + number, err := getHighestPriorityFromListener(svc, listeners[0]) + require.NoError(t, err) + + //there should be at least one rule with prio 1 in it + assert.True(t, number > 0) +} + +func TestGetMetadataForTg(t *testing.T) { + var event common.EventSpec + event.Hostname = common.RandStringRunes(5) + event.DisplayName = common.RandStringRunes(5) + + sess, err := NewSession() + require.NoError(t, err) + svc := elbv2.New(sess) + + //ensure created + out, err := ensureTargetGroup(svc, vpcId, event, RolesMaster) + assert.NoError(t, err) + + //get metadata and ensure domain and role are correct + domain, role, err := GetMetadataForTg(out) + assert.Equal(t, domain, common.HostnameToMasterDomain(event.Hostname)) + assert.Equal(t, role, RolesMaster) + + //delete + require.NoError(t, DeleteTargetGroup(svc, *out.TargetGroupArn)) +} diff --git a/orchestration/src/sapsailing.com/orchestrator/commands.go b/orchestration/src/sapsailing.com/orchestrator/commands.go new file mode 100644 index 00000000000..ea83268dfc2 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/commands.go @@ -0,0 +1,278 @@ +package main + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "path" + "sapsailing.com/common" + "sapsailing.com/common/cmd" + "sapsailing.com/common/sailing" + "sapsailing.com/orchestrator/agent" + "sapsailing.com/orchestrator/aws" + "sapsailing.com/orchestrator/paths" + "time" +) + +type InstanceCommand struct { + Events []string `cmd:"event" cmd_help:"Name of the event to create and a hostname for it (format: hostname:eventname)"` + AdminPassword string `cmd:"admin-password" cmd_help:"New admin password to set"` + Username string `cmd:"user-name" cmd_help:"New user to create"` + Password string `cmd:"user-password" cmd_help:"Password for the new user"` + Template string `cmd:"template-file" cmd_help:"template" cmd_help:"Path to Template" cmd_optional:"!"` + Release string `cmd:"release" cmd_help:"Version (e.g.: build-201806181113)" cmd_optional:"!"` + Environment string `cmd:"env" cmd_help:"Environment (e.g.: live-master-server)" cmd_optional:"!"` + Notify string `cmd:"notify" cmd_help:"Email to notify on startup" cmd_optional:"!"` +} + +func NewInstanceCommand() cmd.Command { + return cmd.Command(&InstanceCommand{ + Template: "master", + Environment: "live-master-server", + }) +} + +func (c *InstanceCommand) Execute() uint8 { + release := c.Release + if release == "" { + latest, err := sailing.FindLatestBuild() + if err != nil { + log.Errorf("Failed to get latest release: %s", err) + return 1 + } + release = latest + } + + events := common.ParseEvents(c.Events) + primaryHostname := events[0].Hostname + + userData := map[string]string{ + "INSTALL_FROM_RELEASE": c.Release, + "USE_ENVIRONMENT": c.Environment, + "SERVER_NAME": primaryHostname, + } + + if c.Notify != "" { + userData["SERVER_STARTUP_NOTIFY"] = c.Notify + } + + instance, err := aws.TemplateFromFile(path.Join(paths.GetInstanceTemplate(c.Template)), userData) + if err != nil { + log.Errorf("Failed to launch instance: %s", err) + return 1 + } + + descriptions, err := aws.LaunchTemplate(instance, events, aws.RoleSingleMaster, false) + if err != nil { + log.Errorf("There is an error during the instance launch: %s", err) + return 1 + } + + description := descriptions[0] + + log.Info("Waiting for the instance to come up...") + err = aws.WaitUntilInstanceUsable(description.Id, false) + if err != nil { + log.Errorf("Failed to wait for instance", err) + return 1 + } + + desc, err := aws.DescribeInstance(description.Id, false) + if err != nil { + log.Errorf("Failed to describe instance: %s", err) + return 1 + } + description = *desc + + privateKeyPath, _ := paths.GetKeyPair() + keyPair, err := agent.KeyPairAuthFromPrivateKey(privateKeyPath) + if err != nil { + log.Errorf("Failed to load keypair: %s", err) + return 1 + } + sshConnection := &agent.AgentConnection{ + Ip: description.PublicIp, + Auth: keyPair, + User: common.SshUser, + } + + err = sshConnection.WaitForConnectivity(5 * time.Second) + if err != nil { + log.Errorf("Unable to connect via SSH: %s", err) + return 1 + } + + err = agent.DeployAgent(sshConnection) + if err != nil { + log.Errorf("Failed to deploy the agent binary: %s", err) + return 1 + } + + agentInput := &common.AgentInput{ + AnalyticsRelease: release, + AnalyticsEnvironment: c.Environment, + ServerName: primaryHostname, + AwsInstanceName: description.Id, + InternalHostname: description.PublicHostname, + ExternalIp: description.PublicIp, + InternalIp: description.PrivateIp, + Events: events, + Notify: c.Notify, + Scenario: "master", + } + + err = agent.InvokeAgent(sshConnection, agentInput) + if err != nil { + log.Errorf("Failed to execute agent stage master: %s", err) + return 1 + } + log.Info("Agent completed without error.") + + var masterDomain string + var roles []aws.TargetGroupRole + roles = append(roles, aws.RolesMaster) + roles = append(roles, aws.RolesDefault) + + tgs := aws.CreateTargetGroupsWithAlarm(events, roles, description) + for _, tg := range tgs { + err = aws.WaitUntilTargetGroupUsable(tg, 6) + if err != nil { + log.Errorf("Failed to wait for succeeding health check: %s", err) + return 1 + } + + domain, role, err := aws.GetMetadataForTg(tg) + if err != nil { + log.Warningf("Could not get metadata for target group %s: %s", string(*tg.TargetGroupArn), err) + } else { + if role == aws.RolesMaster { + masterDomain = domain + } + log.Infof("Target group with role %s can be reached via https://%s", role, domain) + } + } + + //configure app against target group with master role + + appConfig := &sailing.ApplicationConfig{ + Host: masterDomain, + Port: aws.LbListenerPort, + AdminUsername: "admin", + AdminPassword: "admin", + ApiVersion: 1, + } + + appClient, err := sailing.CreateAuthenticatedClient(appConfig) + if err != nil { + log.Warningf("Failed connect to application for post init steps: %s", err) + log.Warningf("This might indicate a startup problem!") + } else { + err := appClient.ChangePassword("admin", c.AdminPassword) + if err != nil { + log.Warningf("Failed to change admin password: %s", err) + } + err = appClient.CreateUser(c.Username, c.Password) + if err != nil { + log.Warningf("Failed to create new user %s: %s", c.Username, err) + } + log.Infof("You can log-in to the AdminConsole with the new user %s", c.Username) + } + + log.Infof("Scenario completed without error.") + return 0 +} + +// will be overwritten by compiler flags +var ( + BuildRevision = "unknown" + BuildVersion = "unknown" + BuildBranch = "unknown" +) + +type VersionCommand struct{} + +func NewVersionCommand() cmd.Command { + return cmd.Command(&VersionCommand{}) +} + +func (c *VersionCommand) Execute() uint8 { + fmt.Printf("Build version: %s\n", BuildVersion) + fmt.Printf("Build revision: %s\n", BuildRevision) + fmt.Printf("Build branch: %s\n", BuildBranch) + return 0 +} + +type AgentOnlyCommand struct { + BasePath string `cmd_optional:"!"` + AnalyticsRelease string `cmd_optional:"!"` + AnalyticsEnvironment string + ServerName string + InternalHostname string + ExternalIp string + InternalIp string + Event []string + Scenario string + DryRun bool `cmd_optional:"!"` +} + +func NewAgentOnlyCommand() cmd.Command { + return cmd.Command(&AgentOnlyCommand{ + DryRun: false, + }) +} + +func (c *AgentOnlyCommand) Execute() uint8 { + + if c.AnalyticsRelease == "" { + build, err := sailing.FindLatestBuild() + if err == nil { + c.AnalyticsRelease = build + } else { + log.Errorf("Failed to get latest build: %s\n", err) + return 1 + } + } + + privateKeyPath, _ := paths.GetKeyPair() + keyPair, err := agent.KeyPairAuthFromPrivateKey(privateKeyPath) + if err != nil { + log.Errorf("Failed to load keypair: %s", err) + return 1 + } + + sshConnection := &agent.AgentConnection{ + Ip: c.ExternalIp, + Auth: keyPair, + User: common.SshUser, + } + + log.Info("Deploying agent...") + err = agent.DeployAgent(sshConnection) + if err != nil { + log.Errorf("Failed to deploy the agent binary: %s", err) + return 1 + } + + agentInput := &common.AgentInput{ + BasePath: c.BasePath, + AnalyticsRelease: c.AnalyticsRelease, + AnalyticsEnvironment: c.AnalyticsEnvironment, + ServerName: c.ServerName, + InternalHostname: c.InternalHostname, + ExternalIp: c.ExternalIp, + InternalIp: c.InternalIp, + Events: common.ParseEvents(c.Event), + Scenario: c.Scenario, + DryRun: c.DryRun, + } + + log.Infof("Invoking agent with scenario %s...", agentInput.Scenario) + err = agent.InvokeAgent(sshConnection, agentInput) + if err != nil { + log.Errorf("Failed to execute agent: %s", err) + return 1 + } + + log.Info("Agent execution completed.") + + return 0 +} diff --git a/orchestration/src/sapsailing.com/orchestrator/configuration/aws.go b/orchestration/src/sapsailing.com/orchestrator/configuration/aws.go new file mode 100644 index 00000000000..9304f335916 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/configuration/aws.go @@ -0,0 +1,33 @@ +package configuration + +import ( + log "github.com/sirupsen/logrus" + "os" + "sapsailing.com/common" + "sapsailing.com/orchestrator/paths" +) + +type ConfigAws struct { + KeyId string `json:"AWS_ACCESS_KEY_ID"` + AccessKey string `json:"AWS_SECRET_ACCESS_KEY"` + DefaultRegion string `json:"AWS_DEFAULT_REGION"` + ElbName string `json:"AWS_ELB_NAME"` + SnsTopicARN string `json:"AWS_SNS_TOPIC_ARN"` +} + +func LoadAWSConfig() (*ConfigAws, error) { + + config := &ConfigAws{} + err := common.LoadFromFile(paths.GetAwsConfig(), config) + + if err != nil { + log.Infof("error loading conf_aws.json file, %s", err) + return nil, err + } + + //make sure AWS keys are set + os.Setenv("AWS_ACCESS_KEY_ID", config.KeyId) + os.Setenv("AWS_SECRET_ACCESS_KEY", config.AccessKey) + + return config, nil +} diff --git a/orchestration/src/sapsailing.com/orchestrator/configuration/aws_test.go b/orchestration/src/sapsailing.com/orchestrator/configuration/aws_test.go new file mode 100644 index 00000000000..d108e04ac03 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/configuration/aws_test.go @@ -0,0 +1,15 @@ +package configuration + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoadAWSConfig(t *testing.T) { + config, err := LoadAWSConfig() + + require.NoError(t, err, "Loading the AWS config should not fail.") + require.NotNil(t, config, "Config may not be nil") + require.NotEqual(t, "", config.KeyId) +} diff --git a/orchestration/src/sapsailing.com/orchestrator/configuration/paths_test.go b/orchestration/src/sapsailing.com/orchestrator/configuration/paths_test.go new file mode 100644 index 00000000000..c2b34594daf --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/configuration/paths_test.go @@ -0,0 +1,27 @@ +package configuration + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "sapsailing.com/orchestrator/paths" +) + +func TestGetPrefix(t *testing.T) { + workDir, err := os.Getwd() + if err != nil { + workDir = "." + } + oldPrefix, found := os.LookupEnv(paths.PrefixEnvName) + prefix := "test" + os.Setenv(paths.PrefixEnvName, prefix) + assert.Equal(t, prefix, paths.GetPrefix()) + os.Unsetenv(paths.PrefixEnvName) + assert.Equal(t, workDir, paths.GetPrefix()) + if found { + os.Setenv(paths.PrefixEnvName, oldPrefix) + } else { + os.Unsetenv(paths.PrefixEnvName) + } +} diff --git a/orchestration/src/sapsailing.com/orchestrator/orchestrator.go b/orchestration/src/sapsailing.com/orchestrator/orchestrator.go new file mode 100644 index 00000000000..f5bf5f53f43 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/orchestrator.go @@ -0,0 +1,27 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + "os" + "sapsailing.com/common" + "sapsailing.com/common/cmd" +) + +func main() { + log.SetFormatter(&common.SimpleFormatter{Source: "Orchestrator"}) + commands := cmd.CommandMap{ + "master": cmd.CommandSpec{ + Help: "Creates a master instance", + Factory: NewInstanceCommand, + }, + "agent-only": cmd.CommandSpec{ + Help: "Executes agent on system", + Factory: NewAgentOnlyCommand, + }, + "version": cmd.CommandSpec{ + Help: "Displays the version", + Factory: NewVersionCommand, + }, + } + os.Exit(int(cmd.DefaultMain(commands))) +} diff --git a/orchestration/src/sapsailing.com/orchestrator/paths/paths.go b/orchestration/src/sapsailing.com/orchestrator/paths/paths.go new file mode 100644 index 00000000000..8d74d2e0f16 --- /dev/null +++ b/orchestration/src/sapsailing.com/orchestrator/paths/paths.go @@ -0,0 +1,54 @@ +package paths + +import ( + "os" + "path" +) + +const PrefixEnvName = "PREFIX" + +func GetPrefix() string { + prefix, found := os.LookupEnv(PrefixEnvName) + if found { + return prefix + } + // prefer pwd as its easier to debug when things to wrong + workDir, err := os.Getwd() + if err == nil { + return workDir + } + return "." +} + +func GetConfigs() string { + return path.Join(GetPrefix(), "configs") +} + +func GetInstanceTemplates() string { + return path.Join(GetConfigs(), "instance_template") +} + +func GetInstanceTemplate(name string) string { + return path.Join(GetInstanceTemplates(), name+".json") +} + +func GetSecrets() string { + return path.Join(GetConfigs(), "secrets") +} + +func GetAwsConfig() string { + return path.Join(GetSecrets(), "conf_aws.json") +} + +func GetKeyPair() (string, string) { + private := path.Join(GetSecrets(), "id_rsa") + return private, private + ".pub" +} + +func GetBin() string { + return path.Join(GetPrefix(), "bin") +} + +func GetAgent() string { + return path.Join(GetBin(), "agent-linux-amd64") +} diff --git a/orchestration/testdata/test.sh b/orchestration/testdata/test.sh new file mode 100644 index 00000000000..03083c9d535 --- /dev/null +++ b/orchestration/testdata/test.sh @@ -0,0 +1,27 @@ +# Environment (live-master-server): START (Sun Jul 1 22:21:04 UTC 2018) +REPLICATION_HOST=rabbit.internal.sapsailing.com +TELNET_PORT=14888 +SERVER_PORT=8888 +MONGODB_HOST=dbserver.internal.sapsailing.com +MONGODB_PORT=10202 +EXPEDITION_PORT=2010 +REPLICATE_ON_START= +REPLICATE_MASTER_SERVLET_HOST= +REPLICATE_MASTER_SERVLET_PORT= +REPLICATE_MASTER_QUEUE_HOST= +REPLICATE_MASTER_QUEUE_PORT= +ADDITIONAL_JAVA_ARGS="$ADDITIONAL_JAVA_ARGS -Xms$MEMORY -Dorg.eclipse.jetty.LEVEL=OFF -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -Drestore.tracked.races=true -Dpolardata.source.url=https://www.sapsailing.com -XX:ThreadPriorityPolicy=2" +# Environment: END +# User-Data: START (Sun Jul 1 22:21:04 UTC 2018) +INSTANCE_NAME=i-0e1db8380e0afa1d4 +INSTANCE_IP4=34.244.12.197 +INSTANCE_INTERNAL_IP4=172.31.11.188 +INSTANCE_DNS=ec2-34-244-12-197.eu-west-1.compute.amazonaws.com +INSTALL_FROM_RELEASE=build-201806181113 +USE_ENVIRONMENT=live-master-server +SERVER_NAME=test-steffen +REPLICATION_CHANNEL=test-steffen +MONGODB_NAME=test-steffen +SERVER_STARTUP_NOTIFY= +INSTANCE_ID="i-0e1db8380e0afa1d4 (34.244.12.197)" +# User-Data: END diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md new file mode 100644 index 00000000000..d03b70e75b0 --- /dev/null +++ b/wiki/howto/development/orchestration.md @@ -0,0 +1,127 @@ +## Architecture + +### Overview +The orchestration project consists out of two components. The orchestrator on the one hand and the agent on the other. +The orchestrator takes care on all actions in regards of AWS and to execute the agent. The agent will be copied via ssh to a target system and then called with a subset of stages to execute. The actual implementation of e.g. handling apache2, is done inside the agent. Once the agent execution of a stage is finished the return will be transferred back to the orchestrator, who continues with the workflow. + +![architecture](https://wiki.sapsailing.com/wiki/images/orchestration/architecture.png) + +### Build and Test +Currently we provide a Makefile, which implements all required steps to build and test the software. Optionally you can of course also use `go test` directly. Just make sure that the `PREFIX` enviroment is set and points to the project directory (contains `configs` and `bin` subdirectories). You can also use `source env.sh` to do this, which on top sets the `GOPATH` correctly. By default, all go dependencies should be downloaded to `$HOME/.go`. They should not be stored in the git project location. + +To build the software, just call `make build`. Make sure that all dependencies are downloaded before via `make dep`. +To test the software, just call `make test` and to check the tests, call `make checktest`. There are also make steps for `fmt` and others. Just check out the Makefile in the project root. +The verbose output of the test logs is written to a folder called `testlogs` (which initially needs to be created). + +### Develop +As the only Plugin for Eclipse is not really maintained anymore, we do not recommend to use it (goclipse). There is actually also no reason for it, as we are using Go and not Java or any OSGI features. +As go is pretty simple and straight forward, you can use any Go supported IDE as stated on https://golang.org/doc/editors.html. + +Anyways, we recommend either using Goland (from IntelliJ), VS Code or Atom with respective go-plus Plugin. +All you need to do after [installing go](https://golang.org/doc/install) and the IDE, is to make sure to set the above mentioned environment variable `PREFIX` to the code directory inside your IDE, so that tests can be successfully executed. Furthermore make sure to set the `GOPATH` correctly, create a directory (outside of the git workspace) and point the `GOPATH` to it. Then, add the code directory from git also to the `GOPATH`. This will result in the fact, that go will download all external dependencies with e.g. `make dep` or `go get` to the first folder and not put everything into the code directories. You will find all dependencies needed for the project inside the top lines for the `makefile`. + +An example for a correct `GOPATH` could be: `GOPATH=/home/steffen/.godep:/home/steffen/sailing-automation/` + +Happy Coding! + +## Get and prepare the software +You can get the current release of the orchestration software currently from [here](http://static.sapsailing.com/orchestration-0.0.1_7f7e4e40d30f21ffeb8495a370a540c23c496745.tgz) until a release model is decided. The releases of each build can be downloaded directly on hudson via archived artifacts. The currently set up hudson job is called `orchestration-master` and can be started manually (runs on the archive failover, where go is installed). + +![hudson artifacts](https://wiki.sapsailing.com/wiki/images/orchestration/hudson-artifacts.png) + +Once downloaded, you just need to extract the archive locally to your machine. You will find the orchestrator and agent binaries in `bin` and all configurations in `configs` (like the instance templates or secrets). +To get going, place your private key inside the secrets folder and name it `id_rsa`. This key must be pre-deployed in the used AWS AMI image. Furthermore, create a file called `conf_aws.json` (copy `conf_aws.json.sample` and fill in the values). A good example is + +``` +{ + "AWS_ACCESS_KEY_ID": ", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_DEFAULT_REGION": "eu-west-1", + "AWS_ELB_NAME": "Sailing-eu-west-1", + "AWS_SNS_TOPIC_ARN": "arn:aws:sns:eu-west-1:017363970217:SAPSailing-General-Alerting" +} +``` + +Inside the `config` folder you will find a folder called `instance_templates`. You can pre-define variables for launching EC2 instances. A valid example is + +``` +{ + "image": "ami-839f8769", + "instance-type": "t2.medium", + "vpc": "vpc-14b3477f", + "subnet": "subnet-eab34781", + "count": 1, + "security-groups": [ + "sg-eaf31e85" + ], + "tags": { + } +} + +``` + +You are ready to use the orchestration tool! Please note, that currently only linux-amd64 versions are built. Windows versions can be easily added, as go can also run under Windows and the project currently does not have any dependencies to linux. + +## Run the software +You can run the orchestrator using + +``` +./bin/orchestrator-linux-amd64 +``` + +As we use log levels, you can increase logging verbosity to a different level by specifying a ENV variable in front of the orchestrator command, like: + +``` +LOG=DEBUG ./bin/orchestrator-linux-amd64 +``` +We offer the following log levels: `DEBUG`, `INFO` (default), `WARNING`, `PANIC`, `FATAL`. If execution fails, you may re-execute the orchestrator using a higher log level to get more insights, what has happened. + +### General +In general, the orchestrator accepts a set of arguments. The first level of parameters describe the type of action to be executed (basically a use-case). The following arguments are used to specify the use-case specific parameters. + +To show all available commands just call the orchestrator without any parameters. + +Non use-case specific commands are example commands like the `version` of the orchestrator. To do so, just use + +``` +./bin/orchestrator-linux-amd64 version +``` + +It will show the `version`, `build revision` (git commit) and the `branch`, from where the software was built. The agent should have the same version as the orchestrator and will be transferred to a target at each execution to make sure the target has the correct version. + +If you want to skip some steps done by the orchestrator on AWS level, you can use a command called `agent-only` as parameter. This will require a empty EC2 instance, where only agent actions (like bootstrapping,...) are executed. This step helps do debug actions of the agent, as you do not need to fire up a new EC2 instance all the time, but can use a already existing one. A sample call of this command looks like + +``` +./bin/orchestrator-linux-amd64 agent-only event="test-steffen123-1:1. Steffen Liga" servername=test-steffen123-1 internalhostname=ec2-34-242-13-180.eu-west-1.compute.amazonaws.com externalip=34.242.13.180 internalip=172.31.22.105 scenario=master analyticsenvironment=live-master-server analyticsrelease=build-201807231413 +``` + +### Use case: Create Master Server +This is the first ready to use and implemented use-case. It will spawn a new EC2 instance (you need to use an AMI in your instance template from the "NEXTGEN" base using Ubuntu 16 with systemd) and spawn/configure a new java instance on it. Furthermore it makes sure, that apache2 is configured correctly and if so the workflow continues to create target groups (default and master), attach the just created EC2 instance, create according rules in the load balancer and finally create an Cloudwatch alarm for all target groups to get notified, if a failure occurs inside a target group. + +If a step during execution fails, the orchestrator decides if it can continue or not. If not the execution is stopped and actions done via the agent are rolled back. Rollback of AWS features is not yet implemented. + +The workflow can create multiple events on a java instance and configure apache2, EC2 load balancing and the java application itself (event creation, user creation, password changes, ...). + +To use the use-case you simply use the orchestrator command `master`. An example could look like: + +``` +./bin/orchestrator-linux-amd64 master event="steffen2018:1. Steffen Liga" admin-password=supersicher123 user-name=steffen user-password=supersicher123 env=live-master-server release=build-201807231413 +``` + +The following parameters are required for this use-case: + +``` +- event=: (the command can be repeated to create multiple events on the java server) +- admin-password= +- user-name= +- user-password= +- env= +``` + +Optionally you can specify the following parameters: + +``` +- template-file= +- release= +- notify= +``` diff --git a/wiki/howto/development/selenium-ui-tests.md b/wiki/howto/development/selenium-ui-tests.md index 2282925de26..bc6d58f4b92 100644 --- a/wiki/howto/development/selenium-ui-tests.md +++ b/wiki/howto/development/selenium-ui-tests.md @@ -4,19 +4,19 @@ ## Quick Start: How to run tests locally in Eclipse -There are two ways to run the Selenium tests locally on your computer. Either, you compile the GWT UI using our build script and run the tests based on the compiled UI, or you run the tests using the GWT hosted mode. +There are two ways to run the Selenium tests locally on your computer. Either, you compile the GWT UI using our build script and run the tests based on the compiled UI, or you run the tests using the GWT super dev mode. ### Firefox Prerequisites -!Since old Firefox version do not work with WindowScaling, ensure that in windows the Font Scaling is set to 100%, else Firefox will not be able to click buttons. You can find this setting at "Settings>Display Settings>Change the size of text, apps and other items". Also make sure that Firefox is running maximized (at least at Windows) as per default it is not running maximized! +Running Selenium tests with FireFox requires to use GeckoDriver. You need to download a current version of GeckoDriver from [the download page](https://github.com/mozilla/geckodriver/releases) and unzip it on your system. At the time this was written, the current version was 0.21.0 and it worked best with FireFox versions 57-61 but other versions of both, GeckoDriver and FireFox should also work. In the file "/com.sap.sailing.selenium.test/local-test-environment.xml" you need to adjust the property "webdriver.gecko.driver" to point to the unzipped GeckoDriver executable. If you have a version of FixeFox globally installed on your system, GeckoDriver will use this one. If you do not have an installed version of FireFox or need to use a different version, you can set the property "webdriver.firefox.bin" in "/com.sap.sailing.selenium.test/local-test-environment.xml" to point to the specific firefox executable. When using a portable version, it is not the "FirefoxPortable.exe" but "App/Firefox/firefox.exe". -You have to ensure that your Firefox browser has a profile called "Selenium" and that in this profile the latest version of the GWT plugin is installed. To ensure this, launch the server by choosing the "Sailing Server (Proxy)" or "Sailing Server (No Proxy)" launch config. Then, run the "SailingGWT" launch to start the GWT UI in hosted / development mode. Afterwards you can launch Firefox from the command line with the -p option. On Windows machines, you can do this by pressing the Windows key, then typing "firefox.exe -p". In the profile manager create a profile called Selenium and start Firefow with that profile. Hit the entry page of the AdminConsole by entering `http://127.0.0.1:8888/gwt/AdminConsole.html?gwt.codesvr=127.0.0.1:9997` into the address bar. This will ask you to install the GWT plugin into your Selenium profile. When done, exit the browser. You may use the profile manager again to set your default profile to your original profile. +In older versions of Selenium, you needed to configure a special user profile in FireFox. With GeckoDriver, this isn't needed anymore. -### Running the tests with GWT hosted mode +### Running the tests with GWT super dev mode -Launch the server by choosing the "Sailing Server (Proxy, winddbTest)" or "Sailing Server (No Proxy, winddbTest)" launch config. Then, run the "SailingGWT" launch to start the GWT UI in hosted / development mode. +Launch the server by choosing the "Sailing Server (Proxy, winddbTest)" or "Sailing Server (No Proxy, winddbTest)" launch config. Then, run the "GWT Sailing SDM" launch to start the GWT UI in super dev mode (SDM). -When the GWT development mode has finished its initialization as indicated by the "Development Mode" view showing the entry point URLs, launch the "com.sap.sailing.senelium.test (Proxy)" or "com.sap.sailing.senelium.test (No Proxy)" launch. This will then pop up Firefox windows using the "Selenium" profile and run the tests. +When the GWT SDM has finished its initialization as indicated by the "Development Mode" view showing the entry point URLs, launch the "com.sap.sailing.senelium.test (Proxy)" or "com.sap.sailing.senelium.test (No Proxy)" launch. This will then pop up Firefox and run the tests. ### Running the tests after a successful local GWT compile @@ -111,9 +111,11 @@ For a more practical example of how to write page objects and test you should ta While our build environment is stable regarding to the used browser version, this may not be the case in your development environment, where you have the newest browser version installed probably. The short release cycles of the browsers often bring changes in the implementation, which are incompatible with Selenium. Therefore you have to update the used Selenium version by performing the following steps to be able to run the tests local. -* Download the latest _Client & WebDriver Bindings_ for Java from the official [Selenium](http://docs.seleniumhq.org/download/) website -* Delete the _selenium-java-.jar_ in the root directory of the project _org.openqa.selenium.osgi_ as well as all libraries in the _lib_ directory and copy the new versions from the downloaded file in the appropriate folders +* Download the latest _Client_ for Java from the official [Selenium](http://docs.seleniumhq.org/download/) website +* Delete the _client-combined-.jar_ (plus the _client-combined--sources.jar_) in the root directory of the project _org.openqa.selenium.osgi_ as well as all libraries in the _lib_ directory and copy the new versions from the downloaded file in the appropriate folders * Open the _MANIFEST.MF_ with the Plug-in Manifest Editor and switch to the _Runtime_ tab * Remove all the old libraries from the _Classpath_ section and add all new versions * Add all new packages that start with _org.openqa.selenium_ to the _Exported Packages_ section in the case there were packages added -* Updated the version number in the _MANIFEST.MF_ and in the _pom.xml_ \ No newline at end of file +* Updated the version number in the _MANIFEST.MF_ and in the _pom.xml_ +* update build.properties to reflect the changed *.jar files +* Update the version number in _MANIFEST.MF_ files referencing the _org.openqa.selenium_ bundle (e.g in _com.sap.sailing.selenium.test_) diff --git a/wiki/images/orchestration/architecture.png b/wiki/images/orchestration/architecture.png new file mode 100644 index 00000000000..d7ed0581f9c Binary files /dev/null and b/wiki/images/orchestration/architecture.png differ diff --git a/wiki/images/orchestration/hudson-artifacts.png b/wiki/images/orchestration/hudson-artifacts.png new file mode 100644 index 00000000000..5de63cd1450 Binary files /dev/null and b/wiki/images/orchestration/hudson-artifacts.png differ diff --git a/wiki/info/landscape/amazon-ec2.md b/wiki/info/landscape/amazon-ec2.md index 033b8f29024..1f2668dec92 100644 --- a/wiki/info/landscape/amazon-ec2.md +++ b/wiki/info/landscape/amazon-ec2.md @@ -514,6 +514,7 @@ Follow these steps to upgrade the AMI: * Check the sizes of the mounted partitions by doing `df; swapon -s`. These will come in handy after creating the new AMI in order to tag the new volume snapshots accordingly * Update any keys in `/root/.ssh/authorized_keys` and `/home/sailing/.ssh/authorized_keys` * Remove created http rewrite entries in `/etc/httpd/conf.d/001-events.conf` +* Edit /etc/update-motd.d/30-banner to set the current version * In the EC2 administration console go to the "Instances" tab, select your running instance and from the "Actions" drop-down select "Create Image". Give the image the name "SAP Sailing Analytics App x.y" where "x.y" is the updated version number of the image. Just make sure it's greater than the previous one. If you feel like it, you may provide a short description telling the most important features of the image. * Once the image creation has completed, go to the Snapshots list in the "Elastic Block Store" category and name the new snapshots appropriately. Now the information about the device sizes obtained earlier from the `df` and `swapon` commands will help you to identify which snapshot is which. Usually, the three snapshots would be something like AMI Analytics Home x.y, AMI Analytics System x.y and AMI Analytics Swap x.y with "x.y" being the version number matching that of your image. * Now you can remove any earlier Sailing Server AMI version and the corresponding snapshots.