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.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/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.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/RaceCourseReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java index 8f6a279789b..bb816ebb05c 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." + } }; } 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/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.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/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 12ab58c2f4f..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 @@ -180,18 +180,18 @@ text-overflow: ellipsis; } -.regattacompetition_phase_fleetfullwidth, .regattacompetition_phase_fleet_race_title { +.regattacompetition_phase_fleet_race_title_big { width: 27%; } @media (min-width: 375px) { - .regattacompetition_phase_fleetfullwidth, .regattacompetition_phase_fleet_race_title { + .regattacompetition_phase_fleet_race_title_big { width: 33%; } } @media (min-width: 700px) { - .regattacompetition_phase_fleetfullwidth, .regattacompetition_phase_fleet_race_title { + .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/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/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 ccf04de6168..2e4bfb1dd1c 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 @@ -50,6 +50,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Triple; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; 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.FlushableCellTable; @@ -282,7 +284,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); @@ -431,7 +433,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 old mode 100644 new mode 100755 index 2da8358f301..588f3e8c6ea --- 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.ImagesBarColumn; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; @@ -283,7 +284,7 @@ public class BoatTableWrapper> exte @Override public void onSuccess(Void result) { - Window.alert(getStringMessages().successfullyAllowedBoatReset(boats.toString())); + Notification.notify(getStringMessages().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 a2cec6e458a..6b898852beb 100755 --- 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.ImagesBarColumn; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; @@ -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 73db3adfd40..5e16aeb45dc 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.sse.common.util.NaturalComparator; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; 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.ImagesBarCell; import com.sap.sse.gwt.client.celltable.ImagesBarColumn; @@ -258,10 +260,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); } } }); @@ -328,7 +330,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 f6b08449715..aa188aed227 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 @@ -71,6 +71,8 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; 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.FlushableCellTable; import com.sap.sse.gwt.client.celltable.ImagesBarColumn; @@ -382,7 +384,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) { @@ -439,7 +441,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { leaderboardDTO.scoringScheme)); break; default: - Window.alert(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name())); + Notification.notify(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name()), NotificationType.ERROR); } } } @@ -533,7 +535,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(); } @@ -655,7 +657,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onSuccess(Boolean result) { if (!result) { - Window.alert(stringMessages.failedToSetNewStartTime()); + Notification.notify(stringMessages.failedToSetNewStartTime(), NotificationType.ERROR); } } }); @@ -682,7 +684,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 560042703b3..4757d6f3120 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 @@ -53,6 +53,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; 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; @@ -988,7 +990,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 79d6b163c9f..3c85da56884 100755 --- 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.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; 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; @@ -141,7 +143,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 48793252f1b..a4980fd043e 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 8d87211a827..326704fa801 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 @@ -66,6 +66,8 @@ import com.sap.sse.common.Util.Triple; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; 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.FlushableCellTable; import com.sap.sse.gwt.client.celltable.ImagesBarColumn; @@ -235,7 +237,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, @@ -757,7 +759,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Void result) { - Window.alert(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks()); + Notification.notify(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks(), NotificationType.SUCCESS); } })); } @@ -847,7 +849,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(); } @@ -874,7 +876,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(); } @@ -944,12 +946,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 5080572a7a7..8de6d119d37 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/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/StringMessages_es.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties index 50155bb02e9..e28ec429798 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 @@ -1163,7 +1163,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" @@ -1275,11 +1276,13 @@ uploadSuccessful=Carga correcta fileUploadResult=Carga de archivos: {0} {1}. En caso de error, compruebe que su servicio de alojamiento de archivos está configurado correctamente. removeUploadedFile=Eliminar archivo showCompetitorBoatColumn=Embarcación del competidor +showCompetitorSailIdColumn=ID de vela +showCompetitorSailIdColumnTooltip=Muestra el ID de vela más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorShortNameColumn=Denominación breve de competidor showCompetitorShortNameColumnTooltip=Muestra el nombre breve más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorFullNameColumn=Nombre completo del competidor -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. +alwaysShowCompetitorNationalityColumn=Siempre mostrar la nacionalidad del competidor +alwaysShowCompetitorNationalityColumnTooltip=Muestra ambos, las banderas de país así como las imágenes de competidor, si están disponibles. loadingDimensionValues=Cargando valores de dimensión inviteBuoyTenders=Invitar a balizadores orMultipleEmails=o varios correos electrónicos separados por una coma @@ -1970,7 +1973,7 @@ windFinderWindSourceTypeName=Windfinder windFinderWindSourceTypeTooltip=Viento medido de uno o más puntos de www.windfinder.com 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" +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} 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 cd84fe961fd..be10af53726 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 @@ -1163,7 +1163,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" @@ -1275,11 +1276,13 @@ uploadSuccessful=Chargement réussi fileUploadResult=Chargement du fichier : {0} {1}.\nEn cas d''erreur, vérifiez que votre service de stockage de fichier est correctement configuré. removeUploadedFile=Supprimer fichier chargé showCompetitorBoatColumn=Bateau du concurrent +showCompetitorSailIdColumn=ID de voile +showCompetitorSailIdColumnTooltip=Affiche l''ID de voile ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorShortNameColumn=Nom court du concurrent showCompetitorShortNameColumnTooltip=Affiche le nom court ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorFullNameColumn=Nom complet du concurrent -showCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent -showCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que l''image du concurrent +alwaysShowCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent +alwaysShowCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que 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 @@ -1970,7 +1973,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Vent mesuré d''un ou plusieurs endroits de www.windfinder.com 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". +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} 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 fa4ad030dc2..18e9f91ca5a 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 @@ -1163,7 +1163,8 @@ racesScoredTooltip=このリーダーボードにおいて競技者が完了し averageNumberOfOperationsPerMessage=メッセージごとの操作の平均数 showUncorrectedTotalPoints=未修正総得点表示 setStartTimeReceived=設定済スタート時刻を受信 -setStartTimeReceivedDescription=これにより、永続的でない選択した TrackedRace の startTimeReceived が設定されます。したがって、サーバが再起動された後は新規の値は忘れられます。 +setStartTimeReceivedDescription=選択した TrackedRace の startTimeReceived が指定した値に設定されます。値を空白のままにすると、現在設定されている startTimeReceived は削除されます。 +setStartTimeReceivedNotice=この変更は永続的なものではありません。つまり、新しい値はサーバが再起動したときに失われます。 lastScoreCorrectionsTime=最終得点修正時刻 lastScoreCorrectionsComment=最終得点修正コメント setTimeToNow=時刻を ''現在'' に設定 @@ -1275,11 +1276,13 @@ uploadSuccessful=正常にアップロード済 fileUploadResult=ファイルアップロード: {0} {1}\nエラー発生の場合は、ファイルストレージサービスが適切に設定されていることを確認してください。 removeUploadedFile=アップロード済ファイルの削除 showCompetitorBoatColumn=競技者の艇 +showCompetitorSailIdColumn=セール ID +showCompetitorSailIdColumnTooltip=セール ID に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorShortNameColumn=競技者の名前 (短) showCompetitorShortNameColumnTooltip=名前 (短) に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorFullNameColumn=競技者氏名 -showCompetitorNationalityColumn=競技者国籍を常に表示 -showCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示 +alwaysShowCompetitorNationalityColumn=競技者国籍を常に表示 +alwaysShowCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示 loadingDimensionValues=次元値ロード中 inviteBuoyTenders=ブイ入札の招待 orMultipleEmails=または複数の電子メールをカンマで区切り @@ -1970,7 +1973,7 @@ windFinderWindSourceTypeName=Windfinder windFinderWindSourceTypeTooltip=www.windfinder.com の 1 つ以上のスポットから風を測定しました windFinder=Windfinder enterTagsForTheVideo=動画のタグを入力 -enterIdOfWindfinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee") +enterIdOfWindFinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee") windFinderSpotCollectionsList=WindFinder スポットコレクション enterTagsForTheImage=画像のタグを入力 unableToResolveWindFinderSpotId=ID {0} で Windfinder スポットを決定できませんでした: {1} 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 5bad9ffc42c..78d242a2e15 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 @@ -1163,7 +1163,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'' @@ -1275,11 +1276,13 @@ uploadSuccessful=Carregamento efetuado com êxito fileUploadResult=Carregamento de arquivo: {0} {1}.\nEm caso de erro, verifique se seu serviço de armazenamento de arquivos está configurado de modo apropriado. removeUploadedFile=Remover arquivo carregado showCompetitorBoatColumn=Barco do competidor +showCompetitorSailIdColumn=ID da vela +showCompetitorSailIdColumnTooltip=Exibe o ID da vela mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorShortNameColumn=Nome abreviado do competidor showCompetitorShortNameColumnTooltip=Exibe o nome abreviado mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorFullNameColumn=Nome completo do competidor -showCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor -showCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis +alwaysShowCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor +alwaysShowCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis loadingDimensionValues=Carregando valores de dimensão inviteBuoyTenders=Convidar navios-balizadores orMultipleEmails=ou vários e-mails separados por vírgula @@ -1970,7 +1973,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Vento medido de um ou mais locais de www.windfinder.com windFinder=WindFinder enterTagsForTheVideo=Inserir etiquetas para o vídeo -enterIdOfWindfinderReviewedSpotCollection=Inserir ID de uma coleção revisada de locais do WindFinder, por exemplo, "schilksee" +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} 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 c2e3bbc1ab1..e9612321f5a 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 @@ -1163,7 +1163,8 @@ racesScoredTooltip=Число гонок, которые участник зав averageNumberOfOperationsPerMessage=Среднее число операций на сообщение showUncorrectedTotalPoints=Показать неисправленный общий балл setStartTimeReceived=Установить полученное время старта -setStartTimeReceivedDescription=Устанавливает полученное время старта для выбранной отслеживаемой гонки, которое не хранится постоянно. Это значит, что после перезагрузки сервера новое значение будет забыто. +setStartTimeReceivedDescription=Устанавливает введенное значение для полученного времени старта выбранной отслеживаемой гонки. Оставьте значение пустым, чтобы удалить текущее установленное полученное время старта. +setStartTimeReceivedNotice=Это изменение не хранится, т.е. новое значение будет забыто после перезагрузки сервера. lastScoreCorrectionsTime=Время последнего исправления оценки lastScoreCorrectionsComment=Комментарий последнего исправления оценки setTimeToNow=Установить время ''сейчас'' @@ -1275,11 +1276,13 @@ uploadSuccessful=Отправлено успешно fileUploadResult=Отправка файла: {0} {1}.\nВ случае ошибки проверьте правильность конфигурации службы хранилища файлов. removeUploadedFile=Удалить отправленный файл showCompetitorBoatColumn=Лодка участника +showCompetitorSailIdColumn=Ид. паруса +showCompetitorSailIdColumnTooltip=Показывает ид. паруса плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorShortNameColumn=Краткое имя участника showCompetitorShortNameColumnTooltip=Показывает краткое имя плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorFullNameColumn=Полное имя участника -showCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника -showCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии) +alwaysShowCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника +alwaysShowCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии) loadingDimensionValues=Загрузка значений измерения inviteBuoyTenders=Пригласить лоцманов orMultipleEmails=или несколько адресов эл. почты через запятую @@ -1970,7 +1973,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Измерения ветра из одного или нескольких мест www.windfinder.com windFinder=WindFinder enterTagsForTheVideo=Введите теги для видео -enterIdOfWindfinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee") +enterIdOfWindFinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee") windFinderSpotCollectionsList=Коллекции мест WindFinder enterTagsForTheImage=Введите теги для изображения unableToResolveWindFinderSpotId=Не удалось разрешить место WindFinder по ид. {0}: {1} 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 0c2450d869e..16c5f949a2f 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 @@ -1163,7 +1163,8 @@ racesScoredTooltip=参赛队完成并记录在此积分榜上的比赛轮次数 averageNumberOfOperationsPerMessage=各消息操作的平均数 showUncorrectedTotalPoints=显示未更正的总分 setStartTimeReceived=设置接收的开始时间 -setStartTimeReceivedDescription=设置非持久的所选 TrackedRace 的 startTimeReceived。这意味着,重启服务器后,将忘记新值。 +setStartTimeReceivedDescription=将所选 TrackedRace 的 startTimeReceived 设置为提供的值。将此值留空,以移除当前设置的 startTimeReceived。 +setStartTimeReceivedNotice=此更改并不持久,即重启服务器后,新值将被忘记。 lastScoreCorrectionsTime=上次得分更正时间 lastScoreCorrectionsComment=上次得分更正评论 setTimeToNow=将时间设为“现在” @@ -1275,11 +1276,13 @@ uploadSuccessful=上传成功 fileUploadResult=文件上传: {0} {1}。\n如果出错,请检查文件存储服务配置是否正确。 removeUploadedFile=移除上传的文件 showCompetitorBoatColumn=参赛队的船只 +showCompetitorSailIdColumn=帆号 +showCompetitorSailIdColumnTooltip=显示帆号和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorShortNameColumn=参赛队短名称 showCompetitorShortNameColumnTooltip=显示短名称和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorFullNameColumn=参赛队全名 -showCompetitorNationalityColumn=始终显示参赛队国籍 -showCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像 +alwaysShowCompetitorNationalityColumn=始终显示参赛队国籍 +alwaysShowCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像 loadingDimensionValues=正在加载尺寸值 inviteBuoyTenders=邀请浮标供应船 orMultipleEmails=或使用逗号隔开的多个电子邮件 @@ -1970,7 +1973,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Www.windfinder.com 中一个或多个地点的测量风力 windFinder=WindFinder enterTagsForTheVideo=为视频输入标记 -enterIdOfWindfinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee" +enterIdOfWindFinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee" windFinderSpotCollectionsList=WindFinder 地点集合 enterTagsForTheImage=为图像输入标记 unableToResolveWindFinderSpotId=无法解析编号为{0}的 WindFinder 地点:{1} 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 5613da1818b..8f64df8e6d8 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 6f34647f818..83fc272a062 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 @@ -34,12 +34,13 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.security.Permission; 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.leaderboard.LeaderboardPanel.LeaderBoardStyle; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sse.common.Color; import com.sap.sse.common.InvertibleComparator; import com.sap.sse.common.SortingOrder; import com.sap.sse.common.TimeRange; @@ -74,7 +75,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) { @@ -104,10 +106,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; @@ -280,8 +292,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)); } } } @@ -427,15 +440,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 da72ccf8fb1..df0f5cc9023 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 430fd4b7e49..3e0f1ee4e74 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 acd7b91b0c2..9b94c78e5de 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 @@ -1514,7 +1514,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; } @@ -1853,7 +1853,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 @@ -1866,10 +1865,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); @@ -1880,18 +1881,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; 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.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/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..92430adbf17 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 @@ -18,9 +18,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 +64,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 { 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 130258175d1..41a0c2b85df 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 @@ -132,15 +132,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 +168,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(timeoutInSeconds, TimeUnit.SECONDS); + wait.pollingEvery(pollingEverySoManySeconds, TimeUnit.SECONDS); wait.ignoreAll(exceptions); return wait; @@ -223,7 +223,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 +431,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 +449,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)); } @@ -580,9 +580,9 @@ 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); + 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. */ @@ -600,25 +600,31 @@ public class PageObject { } throw new NoAlertPresentException(); } - + /** - * Waits for an alert box to appear and dismisses the alert. If no alert shows up, an Exception is thrown. + * 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 waitForAlertAndDismiss() throws InterruptedException { - waitForAlertAndDismiss(DEFAULT_WAIT_TIMEOUT_SECONDS); + protected void waitForNotificationAndDismiss() throws InterruptedException { + waitForNotificationAndDismiss(DEFAULT_WAIT_TIMEOUT_SECONDS); } - + /** - * Waits for an alert box to appear and dismisses the alert. If no alert shows up, an Exception is thrown. + * 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 waitForAlertAndDismiss(int timeoutInSeconds) throws InterruptedException { + protected void waitForNotificationAndDismiss(int timeoutInSeconds) throws InterruptedException { int i = 0; while (i < timeoutInSeconds) { i++; try { - Alert alert = driver.switchTo().alert(); - alert.accept(); - return; + List notifications = driver.findElements(By.id("notificationBar")); + if (notifications.size() > 0) { + notifications.get(0).findElements(By.cssSelector("*")) + .forEach(notification -> notification.click()); + ; + return; + } else { + throw new NoAlertPresentException(); + } } catch (NoAlertPresentException e) { Thread.sleep(1000); } 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..f1080d617b1 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(10); } } 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 4b2680b08b4..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,10 +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.google.common.base.Predicate; import com.sap.sailing.selenium.pages.PageArea; import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO; @@ -15,32 +12,8 @@ public class AutoPlayLeaderboardView extends PageArea { } public LeaderboardTablePO getLeaderBoardWithData() { - WebElement leaderboardCellTable = new WebDriverWait(driver, 30).until(new Function() { - @Override - public WebElement apply(WebDriver driver) { - WebElement leaderboardCellTable = findElementOrNullBySeleniumId("LeaderboardCellTable"); - if (leaderboardCellTable != null) { - if (isElementEntirelyVisible(leaderboardCellTable)) { - return leaderboardCellTable; - } else { - return null; - } - } else { - return null; - } - } - }); - LeaderboardTablePO leaderboardTablePO = new LeaderboardTablePO(this.driver, leaderboardCellTable); + LeaderboardTablePO leaderboardTablePO = new LeaderboardTablePO(this.driver, findElementBySeleniumId("LeaderboardCellTable")); leaderboardTablePO.waitForTableToShowData(); - - // After the data is loaded it could be possible that the table isn't completely visible anymore - new WebDriverWait(driver, 30).until(new Predicate() { - @Override - public boolean apply(WebDriver arg0) { - return isElementEntirelyVisible(leaderboardCellTable); - } - }); 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/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java index 76f8cadf204..b68892e0ba1 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 @@ -3,11 +3,14 @@ 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 static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.Alert; +import org.openqa.selenium.By; import org.openqa.selenium.WebDriver.TargetLocator; +import org.openqa.selenium.WebElement; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; @@ -88,6 +91,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT 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(); @@ -109,12 +113,16 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT 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)); + + 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); + for (WebElement element : getWebDriver().findElement(By.id("notificationBar")).findElements(By.cssSelector("*"))) { + if (element.getText().contains(message)) { + element.click(); + assertTrue(true); + return; + } + } + assertTrue("Could not find error notification.", false); } } 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 f79a99f3baf..7ed39aa5877 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 @@ -12,7 +12,10 @@ 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.google.common.base.Function; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.event.EventConfigurationPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; @@ -118,7 +121,26 @@ public class TestAutoPlay extends AbstractSeleniumTest { assertTrue("Url does not contain proper name " + url, url.contains("name=BMW+Cup+(J80)")); AutoPlayLeaderboardView autoplayPage = page.goToAutoPlayClassicUrl(getWebDriver(), url); LeaderboardTablePO leaderBoard = autoplayPage.getLeaderBoardWithData(); - List races = leaderBoard.getRaceNames(); + 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/raceboard/SettingsTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java index 79070cb3237..76b884e193e 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 @@ -10,6 +10,7 @@ import javax.xml.bind.DatatypeConverter; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; @@ -83,6 +84,7 @@ public class SettingsTest extends AbstractSeleniumTest { * settings and global settings. */ @Test + @Ignore public void testRaceBoardPageSettingsStorage() throws InterruptedException, UnsupportedEncodingException { AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); EventConfigurationPanelPO events = adminConsole.goToEvents(); @@ -257,6 +259,7 @@ public class SettingsTest extends AbstractSeleniumTest { * Verifies that settings for different modes are distinct and do not have an effect on other modes. */ @Test + @Ignore public void testThatUserDefaultsForOneModeDoNotHaveAnEffectOnAnotherMode() throws InterruptedException, UnsupportedEncodingException { AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); EventConfigurationPanelPO events = adminConsole.goToEvents(); @@ -337,6 +340,7 @@ public class SettingsTest extends AbstractSeleniumTest { * Verifies that settings are stored for raceboard. */ @Test + @Ignore public void testThatModeDependentSettingsAreStoredForOneMode() throws InterruptedException, UnsupportedEncodingException { AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); EventConfigurationPanelPO events = adminConsole.goToEvents(); 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 a2148acf2fe..e301bf29faf 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 @@ -60,7 +60,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); @@ -69,8 +71,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.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index 23ab8d659ab..d6bc7b0e350 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -22,6 +22,11 @@

Release Notes - Administration Console

+

July 2018

+
    +
  • The mark passing editor now uses a full date/time format for the mark passing time points.
  • +
+

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 +37,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.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/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 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..0924a7d189f --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/NotificationPanel.java @@ -0,0 +1,130 @@ +package com.sap.sse.gwt.client; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.dom.client.Style.Cursor; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +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; + + + 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); + panel.addDomHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + animation.cancel(); + } + }, ClickEvent.getType()); + + 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().setOpacity(1); + } + } + + @Override + protected void onComplete() { + remove(); + } + + @Override + protected void onCancel() { + remove(); + } + }; + } + + /** + * Displays notification at UI. + */ + public void show() { + if (!animation.isRunning()) { + parent.add(panel); + animation.run(NOTIFICATION_TIME); + } + } + + /** + * 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() { + 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/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/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 e68ab23d3f6..ff57ea8cfb8 100755 --- 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; @@ -279,7 +281,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 a08a2cd27b6..41662b54d47 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 220453132b2..b34cbd618c0 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.i18n.StringMessages; @@ -39,7 +40,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 aaea386b9f9..dad28982484 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/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 64eccdf75f2..16e00f2bcec 100755 --- 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 99220e95fbe..4a45b2000fc 100755 --- 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 old mode 100644 new mode 100755 index 1b73e6035b6..2dbcaf9f4fc --- 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 @@ -15,6 +15,8 @@ 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.security.ui.shared.UserDTO; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * Default Presenter implementation for {@link UserDetailsView}. @@ -51,13 +53,14 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { authenticationManager.updateUserProperties(fullName, company, locale, new AsyncCallback() { @Override public void onSuccess(UserDTO 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); } }); } @@ -71,12 +74,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); } }); } @@ -86,7 +89,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, @@ -96,20 +99,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.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/target/sailing@.service b/java/target/sailing@.service index 25d2dda08f7..499c9a0c651 100644 --- a/java/target/sailing@.service +++ b/java/target/sailing@.service @@ -4,10 +4,10 @@ Wants=apache2.service After=network.target apache2.service [Service] -EnvironmentFile=-/home/sailing/environment -EnvironmentFile=-/home/sailing/servers/%I/environment -WorkingDirectory=/home/sailing/servers/%I 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 old mode 100644 new mode 100755 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..26e5286e545 --- /dev/null +++ b/wiki/howto/development/orchestration.md @@ -0,0 +1,116 @@ +## 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). + +## 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/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.