From 7b4298243ace13178b0d57f70fe64d771732d423 Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Wed, 4 Jul 2018 13:58:14 +0200 Subject: [PATCH 01/82] Bug 4655: Using notifications instead of window.alert() and introduced new NotificationType SUCCESS for successfull operations. --- .../client/configs/AutoPlayConfiguration.java | 5 ++-- .../preferences/UserPreferencesPresenter.java | 5 ++-- .../client/RegattaOverviewEntryPoint.java | 5 ++-- .../AbstractEventManagementPanel.java | 4 ++- .../AbstractFileImportWidget.java | 5 ++-- .../AbstractLeaderboardConfigPanel.java | 7 +++-- .../gwt/ui/adminconsole/BoatTableWrapper.java | 6 +++-- .../CompetitorInvitationHelper.java | 12 +++++---- .../adminconsole/CompetitorTableWrapper.java | 6 +++-- .../CompetitorToBoatMappingsDialog.java | 7 ++--- ...DeviceConfigurationQRIdentifierDialog.java | 5 ++-- ...eviceConfigurationUserDetailComposite.java | 5 ++-- .../ExpeditionAllInOneAfterImportHandler.java | 7 ++--- .../ExpeditionAllInOneImportPanel.java | 5 ++-- .../ui/adminconsole/IgtimiAccountsPanel.java | 9 ++++--- .../adminconsole/LeaderboardConfigPanel.java | 12 +++++---- .../LeaderboardGroupConfigPanel.java | 4 ++- .../gwt/ui/adminconsole/MediaPanel.java | 4 ++- .../PairingListCreationDialog.java | 5 +++- .../gwt/ui/adminconsole/SeriesEditDialog.java | 5 ++-- ...martphoneTrackingEventManagementPanel.java | 16 +++++++----- .../StructureImportManagementPanel.java | 6 +++-- .../multivideo/MultiURLChangeDialog.java | 7 ++--- .../multivideo/MultiVideoDialog.java | 5 ++-- .../gwt/ui/client/media/NewMediaDialog.java | 5 ++-- .../shared/charts/EditMarkPassingsPanel.java | 5 ++-- .../shared/charts/EditMarkPositionPanel.java | 6 ++--- .../EditableLeaderboardPanel.java | 4 +-- .../MasterDataImportPanel.java | 8 +++--- .../gwt/ui/simulator/SimulatorMap.java | 4 ++- .../developer/QueryDefinitionViewer.java | 7 ++--- .../adminconsole/URLFieldWithFileUpload.java | 14 +++++----- .../com/sap/sse/gwt/client/Notification.java | 5 ++-- .../gwt/client/mvp/AbstractActivityProxy.java | 5 ++-- .../shared/components/SettingsDialog.java | 14 +++++----- .../AuthenticationManagerImpl.java | 4 ++- .../sse/security/ui/client/UserService.java | 7 ++--- .../ui/client/component/CreateUserDialog.java | 5 ++-- .../EditEmailDialogWithDefaultCallback.java | 14 +++++----- .../component/ForgotPasswordDialog.java | 11 ++++---- .../ui/client/component/SettingsPanel.java | 16 +++++++----- .../ui/client/component/UserDetailsView.java | 22 +++++++++------- .../component/UserListDataProvider.java | 5 ++-- .../client/component/UserManagementPanel.java | 7 +++-- .../ui/editprofile/EditProfileEntryPoint.java | 13 ++++++---- .../sap/sse/security/ui/login/LoginView.java | 8 +++--- .../security/ui/loginpanel/LoginPanel.java | 11 ++++---- .../ui/oauth/client/OAuthLoginEntryPoint.java | 5 ++-- .../ui/oauth/client/util/ClientUtils.java | 10 ++++--- .../ui/registration/RegisterView.java | 12 +++++---- .../userdetails/UserDetailsPresenter.java | 26 +++++++++++-------- 51 files changed, 248 insertions(+), 162 deletions(-) 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/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/adminconsole/AbstractEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java index d02f523e933..df4261fa983 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java @@ -22,6 +22,8 @@ import com.sap.sailing.gwt.ui.shared.RaceRecordDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel { protected final TrackedRacesListComposite trackedRacesListComposite; @@ -121,7 +123,7 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel builder.append(")"); builder.append("\n"); } - Window.alert(builder.toString()); + Notification.notify(builder.toString(), NotificationType.ERROR); result = false; } else { result = true; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java index 88a48717b39..31862a1dbe1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java @@ -8,7 +8,6 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; @@ -25,6 +24,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; public abstract class AbstractFileImportWidget extends Composite { @@ -89,7 +90,7 @@ public abstract class AbstractFileImportWidget extends Composite { void onFileImportComplete(SubmitCompleteEvent event) { SensorDataImportResponse importResponse = SensorDataImportResponse.parse(event.getResults()); if (importResponse == null) { - Window.alert(StringMessages.INSTANCE.unexpectedErrorDuringFileImport()); + Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else { SensorDataImportResultsDialog.showResults(importResponse); if (importResponse.didSucceedImportingAnyFile()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java index 8a1047d9d48..a4df792e067 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -51,6 +51,8 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Triple; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; @@ -281,7 +283,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement refreshRaceLog(column, fleet, false); } } - Window.alert(stringMessages.raceLogReloaded()); + Notification.notify(stringMessages.raceLogReloaded(), NotificationType.ERROR); } }); vPanel.add(reloadAllRaceLogs); @@ -430,7 +432,8 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement @Override public void onSuccess(Void result) { if (showAlerts) { - Window.alert(stringMessages.raceLogReloaded()); + // TODO: Success-Type + Notification.notify(stringMessages.raceLogReloaded(), NotificationType.INFO); } } })); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java index 808ebdc3e2a..b30a4da4719 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java @@ -13,7 +13,6 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Label; import com.sap.sailing.domain.common.dto.BoatDTO; @@ -24,6 +23,8 @@ import com.sap.sse.common.Color; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -282,7 +283,8 @@ public class BoatTableWrapper> exte @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyAllowedBoatReset(boats.toString())); + // TODO: Success-Type + Notification.notify(stringMessages.successfullyAllowedBoatReset(boats.toString()), NotificationType.INFO); } }); } 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..5e23fd3e360 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,13 @@ 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()); + // TODO: Success-Type + Notification.notify(stringMessages.sendingMailsSuccessful(), NotificationType.INFO); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 36d8d1ee111..dbeb99068c9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -14,7 +14,6 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Label; import com.google.gwt.view.client.CellPreviewEvent; @@ -32,6 +31,8 @@ import com.sap.sse.common.Color; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -479,7 +480,8 @@ public class CompetitorTableWrapper competitors) { if (competitors.isEmpty()) { - Window.alert(stringMessages.importCanceledNoCompetitorAdded()); + Notification.notify(stringMessages.importCanceledNoCompetitorAdded(), NotificationType.ERROR); } else { sailingService.setCompetitorRegistrationsInRegattaLog(leaderboard.getName(), competitors, new AsyncCallback() { @@ -337,7 +338,7 @@ public class ExpeditionAllInOneAfterImportHandler { @Override public final void cancel() { - Window.alert(stringMessages.importCanceledByUser()); + Notification.notify(stringMessages.importCanceledByUser(), NotificationType.WARNING); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java index eeee4a424a6..cc04ef0440b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java @@ -7,7 +7,6 @@ import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; @@ -36,6 +35,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; @@ -153,7 +154,7 @@ public class ExpeditionAllInOneImportPanel extends Composite implements Regattas busyIndicator.setBusy(false); final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(event.getResults()); if (response == null) { - Window.alert(StringMessages.INSTANCE.unexpectedErrorDuringFileImport()); + Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else if (response.hasEventId()) { new ExpeditionAllInOneAfterImportHandler(response.getEventId(), response.getRegattaName(), response.getLeaderboardName(), response.getLeaderboardGroupName(), response.getRaceEntries(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java index 57448b3ee20..1b1d7863e27 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java @@ -40,6 +40,8 @@ import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; @@ -256,10 +258,11 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override public void onSuccess(Boolean result) { if (result) { - Window.alert(stringMessages.successfullyAuthorizedAccessToIgtimiUser(editedObject.geteMail())); + // TODO: Success-Type + Notification.notify(stringMessages.successfullyAuthorizedAccessToIgtimiUser(editedObject.geteMail()), NotificationType.INFO); updateAllAccounts(sailingService, filterAccountsPanel, stringMessages, errorReporter); } else { - Window.alert(stringMessages.couldNotAuthorizedAccessToIgtimiUser(editedObject.geteMail())); + Notification.notify(stringMessages.couldNotAuthorizedAccessToIgtimiUser(editedObject.geteMail()), NotificationType.ERROR); } } }); @@ -326,7 +329,7 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyRemoveIgtimiAccount(eMailOfAccountToRemove)); + Notification.notify(stringMessages.successfullyRemoveIgtimiAccount(eMailOfAccountToRemove), NotificationType.INFO); removeFrom.getList().remove(eMailOfAccountToRemove); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index f215666b1b3..5d71a179735 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -72,6 +72,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.shared.components.LinkWithSettingsGenerator; @@ -380,7 +382,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { otherExistingLeaderboard.addAll(availableLeaderboardList); otherExistingLeaderboard.remove(leaderboardDTO); if (leaderboardDTO.type.isMetaLeaderboard()) { - Window.alert(stringMessages.metaLeaderboardCannotBeChanged()); + Notification.notify(stringMessages.metaLeaderboardCannotBeChanged(), NotificationType.ERROR); } else { AbstractLeaderboardDialog dialog; switch (leaderboardDTO.type) { @@ -437,7 +439,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { leaderboardDTO.scoringScheme)); break; default: - Window.alert(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name())); + Notification.notify(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name()), NotificationType.ERROR); } } } @@ -531,7 +533,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onClick(ClickEvent event) { if (getSelectedLeaderboard().type.isRegattaLeaderboard()) { - Window.alert(stringMessages.cannotAddRacesToRegattaLeaderboardButOnlyToRegatta()); + Notification.notify(stringMessages.cannotAddRacesToRegattaLeaderboardButOnlyToRegatta(), NotificationType.ERROR); } else { addRaceColumnsToLeaderboard(); } @@ -653,7 +655,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onSuccess(Boolean result) { if (!result) { - Window.alert(stringMessages.failedToSetNewStartTime()); + Notification.notify(stringMessages.failedToSetNewStartTime(), NotificationType.ERROR); } } }); @@ -680,7 +682,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override public void onSuccess(Pair result) { if (!result.getA() || !result.getB()) { - Window.alert(stringMessages.failedToSetNewFinishingAndFinishTime()); + Notification.notify(stringMessages.failedToSetNewFinishingAndFinishTime(), NotificationType.ERROR); } } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index fad9c27a9a8..bd427552d62 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -54,6 +54,8 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.URLEncoder; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; @@ -986,7 +988,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements setDescriptionEditable(false); updateGroup(selectedGroup); } else { - Window.alert(stringMessages.pleaseEnterNonEmptyDescription() + "."); + Notification.notify(stringMessages.pleaseEnterNonEmptyDescription() + ".", NotificationType.ERROR); descriptionTextArea.setText(selectedGroup.description); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java index c960cb2ffc9..1cb7c2cc2db 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java @@ -59,6 +59,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; @@ -139,7 +141,7 @@ public class MediaPanel extends FlowPanel implements MediaTracksRefresher { public void onClick(ClickEvent event) { Set selected = refreshableSelectionModel.getSelectedSet(); if (selected.isEmpty()) { - Window.alert(stringMessages.noSelection()); + Notification.notify(stringMessages.noSelection(), NotificationType.ERROR); } else { new MultiURLChangeDialog(mediaService, stringMessages, selected, errorReporter, new Runnable() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationDialog.java index 50d8c0e7c2e..fbe98215202 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,8 @@ public class PairingListCreationDialog extends DataEntryDialog() { @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyFilledRaceLogsFromPairingList()); + // TODO: Success-Type + Notification.notify(stringMessages.successfullyFilledRaceLogsFromPairingList(), NotificationType.INFO); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 46b143863fc..2023058b4e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -13,7 +13,6 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.HasVerticalAlignment; @@ -31,6 +30,8 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.IntegerBox; import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; @@ -418,7 +419,7 @@ public class SeriesEditDialog extends DataEntryDialog { } validateAndUpdate(); } else { - Window.alert(getStringMessages().pleaseSelectASeriesFirst()); + Notification.notify(getStringMessages().pleaseSelectASeriesFirst(), NotificationType.ERROR); } } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java index d6f2baa6b49..4a7d7a2d89d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java @@ -67,6 +67,8 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -233,7 +235,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC } }).show(); } else { - Window.alert(stringMessages.canNotRegisterBoats()); + Notification.notify(stringMessages.canNotRegisterBoats(), NotificationType.ERROR); } } else if (RaceLogTrackingEventManagementImagesBarCell.ACTION_MAP_DEVICES.equals(value)) { new RegattaLogTrackingDeviceMappingsDialog(sailingService, stringMessages, errorReporter, @@ -755,7 +757,8 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Void result) { - Window.alert(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks()); + // TODO: Success-Type + Notification.notify(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks(), NotificationType.INFO); } })); } @@ -845,7 +848,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Boolean result) { if (!result) { - Window.alert(stringMessages.failedToSetNewStartTime()); + Notification.notify(stringMessages.failedToSetNewStartTime(), NotificationType.ERROR); } else { trackedRacesListComposite.regattaRefresher.fillRegattas(); } @@ -872,7 +875,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Pair result) { if (!result.getA() || !result.getB()) { - Window.alert(stringMessages.failedToSetNewFinishingAndFinishTime()); + Notification.notify(stringMessages.failedToSetNewFinishingAndFinishTime(), NotificationType.ERROR); } else { trackedRacesListComposite.regattaRefresher.fillRegattas(); } @@ -942,12 +945,13 @@ 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()); + // TODO: Success-Type + Notification.notify(stringMessages.sendingMailsSuccessful(), NotificationType.INFO); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java index 99d79268374..a4f0428cb18 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,8 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat @Override public void onSuccess(Void result) { regattaRefresher.fillRegattas(); - Window.alert(stringMessages.successfullyCreatedRegattas()); + // TODO: Success-Type + Notification.notify(stringMessages.successfullyCreatedRegattas(), NotificationType.INFO); } }); } 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/media/NewMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java index f635328e165..638e1e5369d 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; @@ -432,7 +433,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..949a4d2a4a4 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 @@ -15,7 +15,6 @@ 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 +42,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; @@ -216,7 +217,7 @@ public class EditMarkPassingsPanel extends AbstractCompositeComponent 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(); 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 9dda3f72fde..e9802ad2962 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; @@ -634,7 +633,8 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel { @Override public void onSuccess(Void noarg) { updateScoreCorrectionInformation(lastScoreCorrectionComment, lastScoreCorrectionTime); - Window.alert(stringMessages.successfullyUpdatedScores()); + // TODO: Success-Type + Notification.notify(stringMessages.successfullyUpdatedScores(), NotificationType.INFO); } @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..75a9dccda53 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,8 @@ public class MasterDataImportPanel extends VerticalPanel { buffer.append(regattaName + "\n"); } } - Window.alert(buffer.toString()); + // TODO: Success-Type + Notification.notify(buffer.toString(), NotificationType.INFO); } @@ -319,7 +321,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/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.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.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..da5253c3f83 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 extends AbstractSetti @Override public void onSuccess() { setButtonSavingState(false, stringMessages); - Window.alert(stringMessages.settingsSavedMessage()); + // TODO: Success-Type + Notification.notify(stringMessages.settingsSavedMessage(), NotificationType.INFO); } @Override public void onError(Throwable caught) { setButtonSavingState(false, stringMessages); - Window.alert(stringMessages.settingsSaveErrorMessage()); + Notification.notify(stringMessages.settingsSaveErrorMessage(), NotificationType.ERROR); } }); @@ -111,15 +113,15 @@ 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()); + // TODO: Success-Type + Notification.notify(stringMessages.settingsRemoved(), NotificationType.INFO); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java index 4c34ac2fbf0..2e10b85dac9 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/authentication/AuthenticationManagerImpl.java @@ -10,6 +10,8 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.web.bindery.event.shared.EventBus; import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.authentication.app.AuthenticationContext; import com.sap.sse.security.ui.authentication.app.AuthenticationContextImpl; @@ -281,7 +283,7 @@ public class AuthenticationManagerImpl implements AuthenticationManager { private class ErrorMessageViewImpl implements ErrorMessageView { @Override public void setErrorMessage(String errorMessage) { - Window.alert(errorMessage); + Notification.notify(errorMessage, NotificationType.ERROR); } } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java index 1588d4d9563..d5e55cb11c8 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java @@ -8,13 +8,14 @@ import java.util.logging.Logger; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Random; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.settings.generic.AbstractGenericSerializableSettings; import com.sap.sse.common.settings.generic.GenericSerializableSettings; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.Storage; import com.sap.sse.gwt.client.StorageEvent; import com.sap.sse.gwt.client.StorageEvent.Handler; @@ -129,7 +130,7 @@ public class UserService { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } })); } @@ -180,7 +181,7 @@ public class UserService { userManagementService.logout(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.couldNotSignOut(caught.getMessage())); + Notification.notify(stringMessages.couldNotSignOut(caught.getMessage()), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java index bcb01c0739f..c775f217323 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/CreateUserDialog.java @@ -2,8 +2,9 @@ package com.sap.sse.security.ui.client.component; import java.util.HashMap; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.EntryPointLinkFactory; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.component.UserManagementPanel.UserCreatedEventHandler; @@ -38,7 +39,7 @@ public class CreateUserDialog extends AbstractUserDialog { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.couldNotCreateUser(caught.getMessage())); + Notification.notify(stringMessages.couldNotCreateUser(caught.getMessage()), NotificationType.ERROR); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditEmailDialogWithDefaultCallback.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditEmailDialogWithDefaultCallback.java index d327751a18b..eeb529d6205 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())); + // TODO: Success-Type + Notification.notify(stringMessages.successfullyUpdatedEmail(userData.getUsername(), userData.getEmail()), NotificationType.ERROR); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java index 1050c38e0a2..d796da15cc9 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/ForgotPasswordDialog.java @@ -2,11 +2,12 @@ package com.sap.sse.security.ui.client.component; import java.util.HashMap; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.client.EntryPointLinkFactory; @@ -36,12 +37,12 @@ public class ForgotPasswordDialog extends AbstractUserDialog { public void onFailure(Throwable caught) { if (caught instanceof UserManagementException) { if (UserManagementException.CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL.equals(caught.getMessage())) { - Window.alert(StringMessages.INSTANCE.cannotResetPasswordWithoutValidatedEmail(userData.getUsername())); + Notification.notify(StringMessages.INSTANCE.cannotResetPasswordWithoutValidatedEmail(userData.getUsername()), NotificationType.ERROR); } else { - Window.alert(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage())); + Notification.notify(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage()), NotificationType.ERROR); } } else { - Window.alert(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage())); + Notification.notify(StringMessages.INSTANCE.errorDuringPasswordReset(caught.getMessage()), NotificationType.ERROR); } } @@ -57,7 +58,7 @@ public class ForgotPasswordDialog extends AbstractUserDialog { } nameOrEmail.append(userData.getEmail()); } - Window.alert(StringMessages.INSTANCE.passwordResetLinkSent(nameOrEmail.toString())); + Notification.notify(StringMessages.INSTANCE.passwordResetLinkSent(nameOrEmail.toString()), NotificationType.INFO); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java index ca05964b91d..669394b5837 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,25 @@ 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!"); + // TODO: Success-Type + Notification.notify("Added url!", NotificationType.INFO); } }); 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!"); + // TODO: Success-Type + Notification.notify("Added url filter!", NotificationType.INFO); } }); } @@ -182,7 +186,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 +296,7 @@ public class SettingsPanel extends LayoutPanel { boolean deleteS = Window.confirm("Are you sure you want to delete this setting?"); if (deleteS){ - Window.alert("Not implemented yet"); + Notification.notify("Not implemented yet", NotificationType.ERROR); } } }); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index 6528d99f4af..2fd62246f89 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -12,7 +12,6 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DecoratorPanel; @@ -24,6 +23,8 @@ import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; @@ -93,13 +94,13 @@ public class UserDetailsView extends FlowPanel { new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorUpdatingRoles(selectedUser.getName(), caught.getMessage())); + Notification.notify(stringMessages.errorUpdatingRoles(selectedUser.getName(), caught.getMessage()), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) { if (!result.isSuccessful()) { - Window.alert(stringMessages.errorUpdatingRoles(selectedUser.getName(), result.getMessage())); + Notification.notify(stringMessages.errorUpdatingRoles(selectedUser.getName(), result.getMessage()), NotificationType.ERROR); } else { userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { @@ -123,13 +124,13 @@ public class UserDetailsView extends FlowPanel { new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorUpdatingPermissions(selectedUser.getName(), caught.getMessage())); + Notification.notify(stringMessages.errorUpdatingPermissions(selectedUser.getName(), caught.getMessage()), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) { if (!result.isSuccessful()) { - Window.alert(stringMessages.errorUpdatingPermissions(selectedUser.getName(), result.getMessage())); + Notification.notify(stringMessages.errorUpdatingPermissions(selectedUser.getName(), result.getMessage()), NotificationType.ERROR); } else { userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { @@ -225,20 +226,21 @@ public class UserDetailsView extends FlowPanel { if (caught instanceof UserManagementException) { String message = ((UserManagementException) caught).getMessage(); if (UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS.equals(message)) { - Window.alert(stringMessages.passwordDoesNotMeetRequirements()); + Notification.notify(stringMessages.passwordDoesNotMeetRequirements(), NotificationType.ERROR); } else if (UserManagementException.INVALID_CREDENTIALS.equals(message)) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } else { - Window.alert(stringMessages.errorChangingPassword(caught.getMessage())); + Notification.notify(stringMessages.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } else { - Window.alert(stringMessages.errorChangingPassword(caught.getMessage())); + Notification.notify(stringMessages.errorChangingPassword(caught.getMessage()), NotificationType.ERROR); } } @Override public void onSuccess(Void result) { - Window.alert(stringMessages.passwordSuccessfullyChanged()); + // TODO: Success-Type + Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.INFO); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java index 1b610f25013..818ec82bd4f 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java @@ -8,13 +8,14 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.view.client.AbstractDataProvider; import com.google.gwt.view.client.HasData; import com.google.gwt.view.client.Range; import com.google.gwt.view.client.SelectionModel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.shared.UserDTO; @@ -34,7 +35,7 @@ public class UserListDataProvider extends AbstractDataProvider { userManagementService.getFilteredSortedUserList(filterBox.getText(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java index 8641b84e823..15f595f810f 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java @@ -24,6 +24,8 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.shared.Permission; import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.Role; @@ -80,12 +82,13 @@ public class UserManagementPanel extends DockPanel { for (UserDeletedEventHandler userDeletedHandler : userDeletedHandlers) { userDeletedHandler.onUserDeleted(userToDelete); } - Window.alert(result.getMessage()); + // TODO: Success-Type + Notification.notify(result.getMessage(), NotificationType.INFO); } @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorDeletingUser()); + Notification.notify(stringMessages.errorDeletingUser(), NotificationType.ERROR); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/editprofile/EditProfileEntryPoint.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/editprofile/EditProfileEntryPoint.java index e0fe774b1d1..59e7ac6c94d 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,21 @@ 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()); + // TODO: Success-Type + Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.ERROR); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java index 6759b402553..67321e5f36d 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/login/LoginView.java @@ -17,6 +17,8 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.PasswordTextBoxWithWatermark; import com.sap.sse.gwt.client.controls.TextBoxWithWatermark; import com.sap.sse.gwt.client.dialog.DialogUtils; @@ -83,13 +85,13 @@ public class LoginView extends Composite { if (!result.getRedirectURL().equals("")) { Window.Location.replace(result.getRedirectURL()); } else { - Window.alert(stringMessages.loggedIn(result.getUserDTO().getName())); + Notification.notify(stringMessages.loggedIn(result.getUserDTO().getName()), NotificationType.INFO); } } else { if (SuccessInfo.FAILED_TO_LOGIN.equals(result.getMessage())) { - Window.alert(stringMessages.failedToSignIn()); + Notification.notify(stringMessages.failedToSignIn(), NotificationType.ERROR); } else { - Window.alert(result.getMessage()); + Notification.notify(result.getMessage(), NotificationType.ERROR); } } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java index d440fd43f36..78bcfc1e3af 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/loginpanel/LoginPanel.java @@ -7,12 +7,13 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.security.ui.client.EntryPointLinkFactory; @@ -56,11 +57,11 @@ public class LoginPanel extends HorizontalPanel implements UserStatusEventHandle userService.login(userData.getUsername(), userData.getPassword(), new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) { if (!result.isSuccessful()) { - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } } })); @@ -83,14 +84,14 @@ public class LoginPanel extends HorizontalPanel implements UserStatusEventHandle new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.errorCreatingUser(userData.getUsername(), caught.getMessage())); + Notification.notify(stringMessages.errorCreatingUser(userData.getUsername(), caught.getMessage()), NotificationType.ERROR); } @Override public void onSuccess(UserDTO result) { userService.login(userData.getUsername(), userData.getPassword(), new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { // pretty strange; we just successfully created the user with these credentials... - Window.alert(stringMessages.invalidCredentials()); + Notification.notify(stringMessages.invalidCredentials(), NotificationType.ERROR); } @Override public void onSuccess(SuccessInfo result) {} })); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java index df3bc7d0d70..2783bc2385c 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/OAuthLoginEntryPoint.java @@ -4,7 +4,6 @@ import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.Button; @@ -13,6 +12,8 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.RemoteServiceMappingConstants; import com.sap.sse.security.ui.client.UserChangeEventHandler; import com.sap.sse.security.ui.client.UserManagementService; @@ -50,7 +51,7 @@ public class OAuthLoginEntryPoint implements EntryPoint, UserChangeEventHandler userService.verifySocialUser(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(caught.getMessage()); + Notification.notify(caught.getMessage(), NotificationType.ERROR); } @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java index e3f658c6911..d37212ed934 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java @@ -5,6 +5,8 @@ import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.oauth.client.CredentialDTO; import com.sap.sse.security.ui.shared.SuccessInfo; @@ -296,7 +298,7 @@ public class ClientUtils { String error = Location.getParameter("error"); if (error != null) { String errorMessage = Location.getParameter("error_description"); - Window.alert("Error: " + error + ":" + errorMessage); + Notification.notify(error + ":" + errorMessage, NotificationType.ERROR); reload(); return false; } @@ -383,7 +385,7 @@ public class ClientUtils { } public static void showSessionExpires() { - Window.alert("Your session seems to have expired!\n" + "You will be logged out"); + Notification.notify("Your session seems to have expired! You will be logged out.", NotificationType.ERROR); ClientUtils.reload(); } @@ -394,7 +396,7 @@ public class ClientUtils { message += "Please Logout/reload the application"; // String st = LogUtil.stackTraceToString(caught); // message += "Stack Trace:\n" + st; - Window.alert(message); + Notification.notify(message, NotificationType.ERROR); } public static void logout(UserManagementServiceAsync userManagementService) { @@ -403,7 +405,7 @@ public class ClientUtils { @Override public void onFailure(Throwable caught) { - Window.alert("Could not log out! " + caught); + Notification.notify("Could not log out! " + caught, NotificationType.ERROR); ClientUtils.reload(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java index fa49d95226a..c4c3e6e595e 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java @@ -10,12 +10,13 @@ import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.PasswordTextBoxWithWatermark; import com.sap.sse.gwt.client.controls.TextBoxWithWatermark; import com.sap.sse.gwt.client.dialog.DialogUtils; @@ -85,20 +86,21 @@ 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())); + // TODO: Success-Type + Notification.notify(stringMessages.signedUpSuccessfully(result.getName()), NotificationType.ERROR); closeWindow(); } else { - Window.alert(stringMessages.unknownErrorCreatingUser(usernameTextBox.getText())); + Notification.notify(stringMessages.unknownErrorCreatingUser(usernameTextBox.getText()), NotificationType.ERROR); } } }); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java index 0ae2c0a4356..a0282ed5d87 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java @@ -8,6 +8,8 @@ import com.sap.sse.security.ui.authentication.app.AuthenticationContext; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.component.NewAccountValidator; import com.sap.sse.security.ui.client.i18n.StringMessages; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * Default Presenter implementation for {@link UserDetailsView}. @@ -47,13 +49,14 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { new AsyncCallback() { @Override public void onSuccess(Void result) { - Window.alert(i18n_sec.successfullyUpdatedUserProperties( - authenticationManager.getAuthenticationContext().getCurrentUser().getName())); + Notification.notify(i18n_sec.successfullyUpdatedUserProperties( + authenticationManager.getAuthenticationContext().getCurrentUser().getName()), + NotificationType.INFO); } @Override public void onFailure(Throwable caught) { - Window.alert(i18n_sec.errorUpdatingUserProperties(caught.getMessage())); + Notification.notify(i18n_sec.errorUpdatingUserProperties(caught.getMessage()), NotificationType.ERROR); } }); } @@ -67,12 +70,12 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { new AsyncCallback() { @Override public void onSuccess(Void result) { - Window.alert(i18n_sec.successfullyUpdatedEmail(username, email)); + Notification.notify(i18n_sec.successfullyUpdatedEmail(username, email), NotificationType.INFO); } @Override public void onFailure(Throwable caught) { - Window.alert(i18n_sec.errorUpdatingEmail(caught.getMessage())); + Notification.notify(i18n_sec.errorUpdatingEmail(caught.getMessage()), NotificationType.ERROR); } }); } @@ -82,7 +85,7 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { final String username = authenticationManager.getAuthenticationContext().getCurrentUser().getName(); String errorMessage = validator.validateUsernameAndPassword(username, newPassword, newPasswordConfirmation); if (errorMessage != null && !errorMessage.isEmpty()) { - Window.alert(errorMessage); + Notification.notify(errorMessage, NotificationType.ERROR); return; } userManagementService.updateSimpleUserPassword(username, oldPassword, null, newPassword, @@ -92,20 +95,21 @@ 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()); + // TODO: Success-Type + Notification.notify(i18n_sec.passwordSuccessfullyChanged(), NotificationType.SUCCESS); view.clearPasswordFields(); } }); From 1dc23c6000ba826e699e321e90dde156f316b988 Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Wed, 4 Jul 2018 15:23:10 +0200 Subject: [PATCH 02/82] Bug 4655: Changed type of "successful" messages from INFO to SUCCESS. --- .../gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java | 3 +-- .../sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java | 3 +-- .../gwt/ui/adminconsole/CompetitorInvitationHelper.java | 3 +-- .../sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java | 3 +-- .../sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java | 3 +-- .../gwt/ui/adminconsole/PairingListCreationDialog.java | 3 +-- .../SmartphoneTrackingEventManagementPanel.java | 6 ++---- .../gwt/ui/adminconsole/StructureImportManagementPanel.java | 3 +-- .../gwt/ui/leaderboardedit/EditableLeaderboardPanel.java | 3 +-- .../gwt/ui/masterdataimport/MasterDataImportPanel.java | 3 +-- .../sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java | 3 +-- .../sse/gwt/client/shared/components/SettingsDialog.java | 6 ++---- .../component/EditEmailDialogWithDefaultCallback.java | 4 ++-- .../sap/sse/security/ui/client/component/SettingsPanel.java | 6 ++---- .../sse/security/ui/client/component/UserDetailsView.java | 3 +-- .../security/ui/client/component/UserManagementPanel.java | 3 +-- .../sse/security/ui/editprofile/EditProfileEntryPoint.java | 3 +-- .../com/sap/sse/security/ui/registration/RegisterView.java | 3 +-- .../shared/userdetails/UserDetailsPresenter.java | 1 - 19 files changed, 22 insertions(+), 43 deletions(-) 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 a4df792e067..7af822f63b5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -432,8 +432,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement @Override public void onSuccess(Void result) { if (showAlerts) { - // TODO: Success-Type - Notification.notify(stringMessages.raceLogReloaded(), NotificationType.INFO); + Notification.notify(stringMessages.raceLogReloaded(), NotificationType.SUCCESS); } } })); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java index b30a4da4719..0cc890bac1d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BoatTableWrapper.java @@ -283,8 +283,7 @@ public class BoatTableWrapper> exte @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.successfullyAllowedBoatReset(boats.toString()), NotificationType.INFO); + Notification.notify(stringMessages.successfullyAllowedBoatReset(boats.toString()), NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorInvitationHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorInvitationHelper.java index 5e23fd3e360..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 @@ -67,8 +67,7 @@ public class CompetitorInvitationHelper { @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.sendingMailsSuccessful(), NotificationType.INFO); + 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 dbeb99068c9..af5ce2fc3cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -480,8 +480,7 @@ public class CompetitorTableWrapper() { @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.successfullyFilledRaceLogsFromPairingList(), NotificationType.INFO); + 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/SmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java index 4a7d7a2d89d..20e494bd823 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java @@ -757,8 +757,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks(), NotificationType.INFO); + Notification.notify(stringMessages.notificationRegattaConfigurationUpdatedUsingByMarks(), NotificationType.SUCCESS); } })); } @@ -950,8 +949,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.sendingMailsSuccessful(), NotificationType.INFO); + 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 a4f0428cb18..e657ca630db 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java @@ -476,8 +476,7 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat @Override public void onSuccess(Void result) { regattaRefresher.fillRegattas(); - // TODO: Success-Type - Notification.notify(stringMessages.successfullyCreatedRegattas(), NotificationType.INFO); + Notification.notify(stringMessages.successfullyCreatedRegattas(), NotificationType.SUCCESS); } }); } 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 e9802ad2962..22e03d0395f 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 @@ -633,8 +633,7 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel { @Override public void onSuccess(Void noarg) { updateScoreCorrectionInformation(lastScoreCorrectionComment, lastScoreCorrectionTime); - // TODO: Success-Type - Notification.notify(stringMessages.successfullyUpdatedScores(), NotificationType.INFO); + 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 75a9dccda53..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 @@ -269,8 +269,7 @@ public class MasterDataImportPanel extends VerticalPanel { buffer.append(regattaName + "\n"); } } - // TODO: Success-Type - Notification.notify(buffer.toString(), NotificationType.INFO); + Notification.notify(buffer.toString(), NotificationType.SUCCESS); } 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 da5253c3f83..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 @@ -119,8 +119,7 @@ public class URLFieldWithFileUpload extends Composite implements HasValue extends AbstractSetti @Override public void onSuccess() { setButtonSavingState(false, stringMessages); - // TODO: Success-Type - Notification.notify(stringMessages.settingsSavedMessage(), NotificationType.INFO); + Notification.notify(stringMessages.settingsSavedMessage(), NotificationType.SUCCESS); } @Override @@ -120,8 +119,7 @@ public class SettingsDialog extends AbstractSetti public void onSuccess(SettingsType settings) { setButtonSavingState(false, stringMessages); setDialogComponent(component.getSettingsDialogComponent(settings)); - // TODO: Success-Type - Notification.notify(stringMessages.settingsRemoved(), NotificationType.INFO); + Notification.notify(stringMessages.settingsRemoved(), NotificationType.SUCCESS); } }); } 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 eeb529d6205..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 @@ -48,8 +48,8 @@ public class EditEmailDialogWithDefaultCallback extends EditEmailDialog { if (callback != null) { callback.onSuccess(userData); } - // TODO: Success-Type - Notification.notify(stringMessages.successfullyUpdatedEmail(userData.getUsername(), userData.getEmail()), NotificationType.ERROR); + 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/SettingsPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java index 669394b5837..a14c683d578 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SettingsPanel.java @@ -134,8 +134,7 @@ public class SettingsPanel extends LayoutPanel { @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify("Added url!", NotificationType.INFO); + Notification.notify("Added url!", NotificationType.SUCCESS); } }); userManagementService.addSetting("URLS_AUTH_" + key.getText(), String.class.getName(), filter.getText(), new AsyncCallback() { @@ -146,8 +145,7 @@ public class SettingsPanel extends LayoutPanel { @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify("Added url filter!", NotificationType.INFO); + Notification.notify("Added url filter!", NotificationType.SUCCESS); } }); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index 2fd62246f89..c6956fc2dac 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -239,8 +239,7 @@ public class UserDetailsView extends FlowPanel { @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.INFO); + Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.SUCCESS); } })); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java index 15f595f810f..7976a54d364 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java @@ -82,8 +82,7 @@ public class UserManagementPanel extends DockPanel { for (UserDeletedEventHandler userDeletedHandler : userDeletedHandlers) { userDeletedHandler.onUserDeleted(userToDelete); } - // TODO: Success-Type - Notification.notify(result.getMessage(), NotificationType.INFO); + Notification.notify(result.getMessage(), NotificationType.SUCCESS); } @Override 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 59e7ac6c94d..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 @@ -178,8 +178,7 @@ public class EditProfileEntryPoint implements EntryPoint { @Override public void onSuccess(Void result) { - // TODO: Success-Type - Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.ERROR); + Notification.notify(stringMessages.passwordSuccessfullyChanged(), NotificationType.SUCCESS); } }); } 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 c4c3e6e595e..c3ff8201619 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/registration/RegisterView.java @@ -96,8 +96,7 @@ public class RegisterView extends Composite { @Override public void onSuccess(UserDTO result) { if (result != null) { - // TODO: Success-Type - Notification.notify(stringMessages.signedUpSuccessfully(result.getName()), NotificationType.ERROR); + Notification.notify(stringMessages.signedUpSuccessfully(result.getName()), NotificationType.SUCCESS); closeWindow(); } else { Notification.notify(stringMessages.unknownErrorCreatingUser(usernameTextBox.getText()), NotificationType.ERROR); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java index a0282ed5d87..fc43e4b23a3 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/userprofile/shared/userdetails/UserDetailsPresenter.java @@ -108,7 +108,6 @@ public class UserDetailsPresenter implements AbstractUserDetails.Presenter { @Override public void onSuccess(Void result) { - // TODO: Success-Type Notification.notify(i18n_sec.passwordSuccessfullyChanged(), NotificationType.SUCCESS); view.clearPasswordFields(); } From 2e6d9259f3f6f17f9386a61f0822af1e394f000c Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Thu, 5 Jul 2018 15:41:03 +0200 Subject: [PATCH 03/82] Bug 6455: Added functionality to show multiple notifications (up to 5) at the same time. Other messages get queued in notification queue until free notification slot is available. --- .../com/sap/sailing/gwt/home/HomeBase.gwt.xml | 3 + .../com/sap/sse/gwt/client/Notification.java | 175 +++++++----------- .../sap/sse/gwt/client/NotificationPanel.java | 157 ++++++++++++++++ .../com/sap/sse/gwt/client/notification.gss | 16 +- 4 files changed, 242 insertions(+), 109 deletions(-) create mode 100644 java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/NotificationPanel.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml index 222dead5057..0dcfe1f11bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml @@ -45,6 +45,9 @@ + + + diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java index 56ec38b71dd..22d37f66df5 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -1,90 +1,25 @@ package com.sap.sse.gwt.client; import java.util.LinkedList; +import java.util.List; -import com.google.gwt.animation.client.Animation; import com.google.gwt.core.shared.GWT; -import com.google.gwt.dom.client.Style.Cursor; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootPanel; -import com.sap.sse.common.Util.Pair; /** - * Utility class to show non obstructive warning / info messages using a small notification at the bottom of the page. Clicking the notification will hide it. It will autohide after some time. + * Utility class to show multiple non obstructive warning / info messages using a small notification at the bottom of the page. */ public class Notification { - private static final int NOTIFICATION_TIME = 10000; - private static final double FADE_OUT_PERCENT = 0.98; - private static final double FADE_IN_PERCENT = 0.005; - - interface NotificationResources extends ClientBundle { - @Source("notification.gss") - NotificationCSS css(); - } - - interface NotificationCSS extends CssResource { - String snackbar(); - } - - private final static FlowPanel snackBar = new FlowPanel(); - private final static NotificationResources ress = GWT.create(NotificationResources.class); - private static Animation notificationAnimation; - - static { - ress.css().ensureInjected(); - snackBar.addStyleName(ress.css().snackbar()); - snackBar.getElement().getStyle().setCursor(Cursor.POINTER); - - notificationAnimation = new Animation() { - @Override - protected void onStart() { - super.onStart(); - snackBar.getElement().getStyle().setOpacity(0); - snackBar.getElement().getStyle().setVisibility(Visibility.VISIBLE); - } - - @Override - protected void onUpdate(double progress) { - if (progress < FADE_IN_PERCENT) { - double relPr = progress / FADE_IN_PERCENT; - double bottom = 60 * relPr - 30; - snackBar.getElement().getStyle().setOpacity(relPr); - snackBar.getElement().getStyle().setBottom(bottom, Unit.PX); - } else if (progress > FADE_OUT_PERCENT) { - double relPr = (progress - FADE_OUT_PERCENT) / (1 - FADE_OUT_PERCENT); - snackBar.getElement().getStyle().setOpacity(1 - relPr); - double bottom = 60 * (1 - relPr) - 30; - snackBar.getElement().getStyle().setBottom(bottom, Unit.PX); - } else { - snackBar.getElement().getStyle().setOpacity(1); - snackBar.getElement().getStyle().setBottom(30, Unit.PX); - } - } - - @Override - protected void onComplete() { - snackBar.getElement().getStyle().setVisibility(Visibility.HIDDEN); - checkQueue(true); - } - - @Override - protected void onCancel() { - snackBar.getElement().getStyle().setVisibility(Visibility.HIDDEN); - checkQueue(true); - } - }; - } - - private final static LinkedList> QUEUE = new LinkedList<>(); - - private Notification() { - } + + private static int currentlyShown = 0; // currently displayed messages on the screen. + 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<>(); + private static final NotificationResources ress = GWT.create(NotificationResources.class); public enum NotificationType { ERROR("\u2716", "RED", "WHITE"), @@ -101,37 +36,71 @@ public class Notification { this.bgColor = bgColor; this.color = color; } - } - public static void notify(String message, NotificationType type) { - QUEUE.add(new Pair<>(message, type)); - checkQueue(false); - } + public String getDecorator() { + return decorator; + } - /** - * Displays next message if non is displayed currently, else does nothing and will by called by currently displaying - * message TTL timer - */ - 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 String getColor() { + return color; + } + + public String getBackgroundColor() { + return bgColor; } } + + interface NotificationResources extends ClientBundle { + @Source("notification.gss") + NotificationCSS css(); + } + + interface NotificationCSS extends CssResource { + String notification(); + String notification_bar(); + } + + static { + ress.css().ensureInjected(); + RootPanel.get().add(notifications); + notifications.addStyleName(ress.css().notification_bar()); + } + + private Notification() { + } + + /** + * Creates new notification and adds it to the notification queue. + * @param message message formatted as string + * @param type type of notification, see {@link com.sap.sse.gwt.client.Notification.NotificationType} + */ + public static void notify(String message, NotificationType type) { + NotificationPanel notification = new NotificationPanel(message, type, notifications); + if (!QUEUE.contains(notification)) { + QUEUE.add(notification); + } + checkQueue(false, 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 onAnimationFinish should only be {@code true} if an already shown message leads to this method call, otherwise {@code false}. Only works in combination with parameter {@code notification}. + * @param notification should be an instance of NotificationPanel which has finished displaying, otherwise {@code null}. + */ + protected static void checkQueue(boolean onAnimationFinish, NotificationPanel notification) { + if (onAnimationFinish && notification != null && notification.alreadyShown()) { + QUEUE.remove(notification); + } + + currentlyShown = QUEUE.stream().filter(value -> value.alreadyShown() == true).toArray().length; + QUEUE.forEach(panel -> { + if (!panel.alreadyShown() && currentlyShown < MAX_NOTIFICATIONS) { + panel.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..d02bba856e3 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/NotificationPanel.java @@ -0,0 +1,157 @@ +package com.sap.sse.gwt.client; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.Style.Cursor; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +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 static final NotificationResources ress = GWT.create(NotificationResources.class); + + private final String message; + private final Panel panel; + private final Panel parent; + private final Animation animation; + + private boolean alreadyShown = false; + + interface NotificationResources extends ClientBundle { + @Source("notification.gss") + NotificationCSS css(); + } + + interface NotificationCSS extends CssResource { + String notification(); + + String notification_bar(); + } + + static { + ress.css().ensureInjected(); + } + + public NotificationPanel(String message, NotificationType type, Panel parent) { + this.message = message; + this.parent = parent; + + panel = new FlowPanel(); + panel.addStyleName(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 + " " + type.getDecorator()); + panel.addDomHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + GWT.log("Clicked"); + 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); + checkQueue(true); + } + + /** + * Embeds reference to this notification panel instance to allow access at {@link com.sap.sse.gwt.client.Notification}. + */ + private void checkQueue(boolean onAnimationFinish) { + Notification.checkQueue(onAnimationFinish, 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/notification.gss b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss index 0e1f3b5a5e0..0b05f5c75b6 100644 --- 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 @@ -1,10 +1,14 @@ -.snackbar { - visibility: hidden; - min-width: 250px; - margin-left: -125px; +.notification { + min-width: 250px; text-align: center; border-radius: 2px; padding: 16px; - position: fixed; - left: 50%; + margin-bottom: 10px; +} + +.notification_bar { + position: fixed; + bottom: 0; + left: 50%; + transform: translateX(-50%); } \ No newline at end of file From eafeaa1d4a4a098c65a8ccb9b0d033d73b864301 Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Fri, 6 Jul 2018 13:19:57 +0200 Subject: [PATCH 04/82] Bug 4655: Added z-index to notifications to prevent overlapping by other components. --- java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss | 1 + 1 file changed, 1 insertion(+) 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 index 0b05f5c75b6..752ccfaeb1b 100644 --- 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 @@ -4,6 +4,7 @@ border-radius: 2px; padding: 16px; margin-bottom: 10px; + z-index: 1000; } .notification_bar { From a00b2778fa2cc2c27591c905bfddceeb43c3b3f6 Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Mon, 9 Jul 2018 09:03:36 +0200 Subject: [PATCH 05/82] Bug 4655: Used CSS instead of GSS as GSS is not required. --- .../src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml | 3 --- .../src/com/sap/sse/gwt/client/Notification.java | 2 +- .../src/com/sap/sse/gwt/client/NotificationPanel.java | 2 +- .../sap/sse/gwt/client/{notification.gss => notification.css} | 0 4 files changed, 2 insertions(+), 5 deletions(-) rename java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/{notification.gss => notification.css} (100%) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml index 0dcfe1f11bc..222dead5057 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml @@ -45,9 +45,6 @@ - - - diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java index 22d37f66df5..54f786f8dd5 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -51,7 +51,7 @@ public class Notification { } interface NotificationResources extends ClientBundle { - @Source("notification.gss") + @Source("notification.css") NotificationCSS css(); } 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 index d02bba856e3..da70eea2bef 100644 --- 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 @@ -31,7 +31,7 @@ public class NotificationPanel { private boolean alreadyShown = false; interface NotificationResources extends ClientBundle { - @Source("notification.gss") + @Source("notification.css") NotificationCSS css(); } 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.css similarity index 100% rename from java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss rename to java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.css From 2b426fc004cfdd24a4b499d44824100d7a28494d Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Mon, 9 Jul 2018 17:37:05 +0200 Subject: [PATCH 06/82] Bug 4655: Fixed z-index for notifications. --- .../com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 752ccfaeb1b..7a28589d22a 100644 --- 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 @@ -4,7 +4,6 @@ border-radius: 2px; padding: 16px; margin-bottom: 10px; - z-index: 1000; } .notification_bar { @@ -12,4 +11,5 @@ bottom: 0; left: 50%; transform: translateX(-50%); + z-index: 100; } \ No newline at end of file From 1b1c8335303ac4f5062cbf4e1a08101ec41cecec Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Tue, 10 Jul 2018 15:15:15 +0200 Subject: [PATCH 07/82] Bug 4655: Adapt selenium tests which required alerts to pop up & removed unneccessary console outputs. --- .../sailing/selenium/pages/PageObject.java | 45 ++++++++++++------- .../advanced/MasterDataImportPO.java | 2 +- .../IgtimiAccountsManagementPanelPO.java | 2 +- .../pages/adminconsole/wind/WindPanelPO.java | 2 +- ...StartAndStopTrackingForTracTracEvents.java | 38 +++++++++------- .../com/sap/sse/gwt/client/Notification.java | 1 + .../sap/sse/gwt/client/NotificationPanel.java | 1 - 7 files changed, 55 insertions(+), 36 deletions(-) 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 50828202f50..b395b3c557d 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 @@ -10,7 +10,6 @@ import java.util.function.BooleanSupplier; import java.util.logging.Level; import java.util.logging.Logger; -import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.NoAlertPresentException; import org.openqa.selenium.SearchContext; @@ -538,23 +537,29 @@ public class PageObject { } /** - * Waits for an alert box to appear and accepts the alert. If no alert shows up, an Exception is thrown. + * Waits for an notification to appear and accepts the notification by clicking on it. If no notification shows up, an Exception is thrown. */ - protected void waitForAlertAndAccept() throws InterruptedException { - waitForAlertAndAccept(DEFAULT_WAIT_TIMEOUT_SECONDS); + protected void waitForNotificationAndAccept() throws InterruptedException { + waitForNotificationAndAccept(DEFAULT_WAIT_TIMEOUT_SECONDS); } /** - * Waits for an alert box to appear and accepts the alert. If no alert shows up, an Exception is thrown. + * Waits for an notification to appear and accepts the notification by clicking on it. If no notification shows up, an Exception is thrown. */ - protected void waitForAlertAndAccept(int timeoutInSeconds) throws InterruptedException { + protected void waitForNotificationAndAccept(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); } @@ -563,23 +568,29 @@ public class PageObject { } /** - * 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..5a88707bd6a 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); + waitForNotificationAndAccept(10); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java index 030bc232771..fc8501f96c9 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java @@ -25,7 +25,7 @@ public class WindPanelPO extends PageArea { public void importWindFromIgtimi(int timeoutInSeconds) throws InterruptedException { importWindFromIgtimi.click(); - waitForAlertAndAccept(10); + waitForNotificationAndAccept(10); FluentWait wait = createFluentWait("The following number of fixes were imported for the listed races:", timeoutInSeconds, DEFAULT_POLLING_INTERVAL); wait.until(new Function() { 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..7d18878053a 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestStartAndStopTrackingForTracTracEvents.java @@ -1,13 +1,13 @@ 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.WebDriver.TargetLocator; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; @@ -88,12 +88,16 @@ 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(); - alert.dismiss(); + String message = "There is at least one regatta for the selected boat classes."; - assertThat(text, containsString(message)); + 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); } @Test @@ -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.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java index 54f786f8dd5..40ff1951f2d 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -63,6 +63,7 @@ public class Notification { static { ress.css().ensureInjected(); RootPanel.get().add(notifications); + notifications.getElement().setId("notificationBar"); notifications.addStyleName(ress.css().notification_bar()); } 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 index da70eea2bef..c497e950044 100644 --- 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 @@ -59,7 +59,6 @@ public class NotificationPanel { panel.addDomHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - GWT.log("Clicked"); animation.cancel(); } }, ClickEvent.getType()); From d88c981145a8b75faced9dc2f5061d2039e6a37b Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Wed, 11 Jul 2018 08:21:02 +0200 Subject: [PATCH 08/82] Bug 4655: Reverted selenium test to old version which requires Window.confirm() --- ...estStartAndStopTrackingForTracTracEvents.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 7d18878053a..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 @@ -1,12 +1,15 @@ 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; @@ -89,15 +92,12 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT tracTracEvents.setTrackSettings(false, false, false); tracTracEvents.startTrackingForRace(this.trackableRace); + TargetLocator locator = getWebDriver().switchTo(); + Alert alert = locator.alert(); + String text = alert.getText(); + alert.dismiss(); String message = "There is at least one regatta for the selected boat classes."; - 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); + assertThat(text, containsString(message)); } @Test From f781970822df579e90a64f9817cefcacb856d8e1 Mon Sep 17 00:00:00 2001 From: Henri Kohlberg Date: Wed, 11 Jul 2018 11:25:01 +0200 Subject: [PATCH 09/82] Bug 4655: Fixed Selenium-test which tests scenario with window.confirm() --- .../sailing/selenium/pages/PageObject.java | 29 ++++++++----------- .../IgtimiAccountsManagementPanelPO.java | 2 +- .../pages/adminconsole/wind/WindPanelPO.java | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) 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 b395b3c557d..ad122ed4f53 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 @@ -10,6 +10,7 @@ import java.util.function.BooleanSupplier; import java.util.logging.Level; import java.util.logging.Logger; +import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.NoAlertPresentException; import org.openqa.selenium.SearchContext; @@ -537,43 +538,37 @@ public class PageObject { } /** - * Waits for an notification to appear and accepts the notification by clicking on it. If no notification shows up, an Exception is thrown. + * Waits for an alert box to appear and accepts the alert. If no alert shows up, an Exception is thrown. */ - protected void waitForNotificationAndAccept() throws InterruptedException { - waitForNotificationAndAccept(DEFAULT_WAIT_TIMEOUT_SECONDS); + protected void waitForAlertAndAccept() throws InterruptedException { + waitForAlertAndAccept(DEFAULT_WAIT_TIMEOUT_SECONDS); } - + /** - * Waits for an notification to appear and accepts the notification by clicking on it. If no notification shows up, an Exception is thrown. + * Waits for an alert box to appear and accepts the alert. If no alert shows up, an Exception is thrown. */ - protected void waitForNotificationAndAccept(int timeoutInSeconds) throws InterruptedException { + protected void waitForAlertAndAccept(int timeoutInSeconds) throws InterruptedException { int i = 0; while (i < timeoutInSeconds) { i++; try { - 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(); - } + Alert alert = driver.switchTo().alert(); + alert.accept(); + return; } catch (NoAlertPresentException e) { Thread.sleep(1000); } } throw new NoAlertPresentException(); } - + /** * Waits for an notification to appear and dismisses the notification by clicking on it. If no notification shows up, an Exception is thrown. */ protected void waitForNotificationAndDismiss() throws InterruptedException { waitForNotificationAndDismiss(DEFAULT_WAIT_TIMEOUT_SECONDS); } - + /** * Waits for an notification to appear and dismisses the notification by clicking on it. If no notification shows up, an Exception is thrown. */ 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 5a88707bd6a..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(); - waitForNotificationAndAccept(10); + waitForNotificationAndDismiss(10); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java index fc8501f96c9..030bc232771 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/wind/WindPanelPO.java @@ -25,7 +25,7 @@ public class WindPanelPO extends PageArea { public void importWindFromIgtimi(int timeoutInSeconds) throws InterruptedException { importWindFromIgtimi.click(); - waitForNotificationAndAccept(10); + waitForAlertAndAccept(10); FluentWait wait = createFluentWait("The following number of fixes were imported for the listed races:", timeoutInSeconds, DEFAULT_POLLING_INTERVAL); wait.until(new Function() { From a42fab2c41a80effa5b9617735078474a453d1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 17 Jul 2018 11:17:01 +0200 Subject: [PATCH 10/82] bug4655 some code cleanups/ state simplifications --- .../com/sap/sse/gwt/client/Notification.java | 26 +++++++++---------- .../sap/sse/gwt/client/NotificationPanel.java | 12 +-------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java index 40ff1951f2d..a3643a48cf7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -14,8 +14,6 @@ import com.google.gwt.user.client.ui.RootPanel; * Utility class to show multiple non obstructive warning / info messages using a small notification at the bottom of the page. */ public class Notification { - - private static int currentlyShown = 0; // currently displayed messages on the screen. 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<>(); @@ -56,7 +54,6 @@ public class Notification { } interface NotificationCSS extends CssResource { - String notification(); String notification_bar(); } @@ -71,7 +68,7 @@ public class Notification { } /** - * Creates new notification and adds it to the notification queue. + * 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} */ @@ -80,7 +77,7 @@ public class Notification { if (!QUEUE.contains(notification)) { QUEUE.add(notification); } - checkQueue(false, notification); + checkQueue(notification); } /** @@ -88,20 +85,21 @@ public class Notification { * 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 onAnimationFinish should only be {@code true} if an already shown message leads to this method call, otherwise {@code false}. Only works in combination with parameter {@code notification}. - * @param notification should be an instance of NotificationPanel which has finished displaying, otherwise {@code null}. + * @param notification should be an instance of NotificationPanel which is newly added or just removed */ - protected static void checkQueue(boolean onAnimationFinish, NotificationPanel notification) { - if (onAnimationFinish && notification != null && notification.alreadyShown()) { + protected static void checkQueue(NotificationPanel notification) { + //clean old notification from queue + if (notification.alreadyShown()) { QUEUE.remove(notification); } - currentlyShown = QUEUE.stream().filter(value -> value.alreadyShown() == true).toArray().length; - QUEUE.forEach(panel -> { - if (!panel.alreadyShown() && currentlyShown < MAX_NOTIFICATIONS) { - panel.show(); + 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 index c497e950044..6b7a41c7ae9 100644 --- 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 @@ -17,7 +17,6 @@ import com.sap.sse.gwt.client.Notification.NotificationType; * {@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; @@ -37,8 +36,6 @@ public class NotificationPanel { interface NotificationCSS extends CssResource { String notification(); - - String notification_bar(); } static { @@ -119,16 +116,9 @@ public class NotificationPanel { */ public void remove() { parent.remove(panel); - checkQueue(true); + Notification.checkQueue(this); } - /** - * Embeds reference to this notification panel instance to allow access at {@link com.sap.sse.gwt.client.Notification}. - */ - private void checkQueue(boolean onAnimationFinish) { - Notification.checkQueue(onAnimationFinish, this); - } - @Override public int hashCode() { final int prime = 31; From 9a04624b2e4569bf48bbd597a80e7c502aabdfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 17 Jul 2018 11:50:21 +0200 Subject: [PATCH 11/82] bug4655 simplified css handling --- .../com/sap/sse/gwt/client/Notification.java | 4 +++- .../sap/sse/gwt/client/NotificationPanel.java | 18 +----------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java index a3643a48cf7..9006a22a6f3 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -17,7 +17,7 @@ public class 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<>(); - private static final NotificationResources ress = GWT.create(NotificationResources.class); + static final NotificationResources ress = GWT.create(NotificationResources.class); public enum NotificationType { ERROR("\u2716", "RED", "WHITE"), @@ -55,6 +55,8 @@ public class Notification { interface NotificationCSS extends CssResource { String notification_bar(); + + String notification(); } static { 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 index 6b7a41c7ae9..190fefdd2fd 100644 --- 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 @@ -1,12 +1,9 @@ package com.sap.sse.gwt.client; import com.google.gwt.animation.client.Animation; -import com.google.gwt.core.shared.GWT; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Panel; import com.sap.sse.gwt.client.Notification.NotificationType; @@ -20,7 +17,6 @@ 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 static final NotificationResources ress = GWT.create(NotificationResources.class); private final String message; private final Panel panel; @@ -29,25 +25,13 @@ public class NotificationPanel { private boolean alreadyShown = false; - interface NotificationResources extends ClientBundle { - @Source("notification.css") - NotificationCSS css(); - } - - interface NotificationCSS extends CssResource { - String notification(); - } - - static { - ress.css().ensureInjected(); - } public NotificationPanel(String message, NotificationType type, Panel parent) { this.message = message; this.parent = parent; panel = new FlowPanel(); - panel.addStyleName(ress.css().notification()); + panel.addStyleName(Notification.ress.css().notification()); panel.getElement().getStyle().setCursor(Cursor.POINTER); panel.getElement().getStyle().setColor(type.getColor()); From a827b76e62969cb11ddbdf38311734651025e0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 17 Jul 2018 12:09:13 +0200 Subject: [PATCH 12/82] bug4655 some styling improvement --- .../src/com/sap/sse/gwt/client/NotificationPanel.java | 2 +- .../src/com/sap/sse/gwt/client/notification.css | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) 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 index 190fefdd2fd..0924a7d189f 100644 --- 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 @@ -36,7 +36,7 @@ public class NotificationPanel { panel.getElement().getStyle().setColor(type.getColor()); panel.getElement().getStyle().setBackgroundColor(type.getBackgroundColor()); - panel.getElement().setInnerText(type.getDecorator() + " " + message + " " + type.getDecorator()); + panel.getElement().setInnerText(type.getDecorator() + " " + message); panel.addDomHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { 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 index 7a28589d22a..846404c22d5 100644 --- 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 @@ -1,6 +1,5 @@ .notification { - min-width: 250px; - text-align: center; + min-width: 400px; border-radius: 2px; padding: 16px; margin-bottom: 10px; From ea453abd42469878c1579a712af60b28b4c180d9 Mon Sep 17 00:00:00 2001 From: Steffen Schaefer Date: Fri, 20 Jul 2018 16:27:53 +0200 Subject: [PATCH 13/82] Bug 4675: Waiting for the leaderboard in AutoPlay does not work in all cases because the auto-zoom can break this too. With this change we do not wait for the leaderboard table anymore but the LeaderboardView to get visible. With this change we can be sure that the leaderboard is visible even if the auto zoom changes the size of the leaderboard table while reading the view contents. --- .../autoplay/AutoPlayLeaderboardView.java | 29 +------------------ .../selenium/pages/autoplay/AutoPlayPage.java | 22 +++++++++++--- 2 files changed, 19 insertions(+), 32 deletions(-) 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) { From 4cf7d27c8447dda08d62f4cdad82310aa27e46d1 Mon Sep 17 00:00:00 2001 From: Steffen Schaefer Date: Fri, 20 Jul 2018 16:35:03 +0200 Subject: [PATCH 14/82] Bug 4675: Changes to make the AutoPlay test robust regarding auto update of the leaderboard table --- .../selenium/test/autoplay/TestAutoPlay.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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")); From 155de3537bb07c4d6f45fe826bd17fc4c4e0490e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Fri, 20 Jul 2018 17:23:09 +0200 Subject: [PATCH 15/82] bug4671 fixed issue with temporary files not properly deleted, upon uploading socket connection loss --- .../gwt/ui/server/MediaServiceImpl.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java index 4ac3bbd8d54..5e8e010cc25 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,23 @@ 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, ""); + File tmp = null; + try { + tmp = File.createTempFile("upload", "metadataCheck"); + 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(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, ""); + } + } + } finally { + Files.delete(tmp.toPath()); } } @@ -276,8 +287,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); } } From 56a4960f2419ae3eb5a0e599978c33e0627cd75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Fri, 20 Jul 2018 17:23:33 +0200 Subject: [PATCH 16/82] bug4671 fixed a bug that can cause lost connections on the clientside --- .../gwt/ui/client/media/JSDownloadUtils.java | 128 ++++++++++-------- 1 file changed, 69 insertions(+), 59 deletions(-) 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..1b875229a2a 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_USEAGE_FOR_DL = 1024.0 * 1024 * 1024 * 2; 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_USEAGE_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 maxMemoryUseage) /*-{ 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 > maxMemoryUseage || evt.loaded > maxMemoryUseage) { + 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"); } } }; From 811d1a630fea53bf87bac1ba152f0f4bef097554 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Sun, 22 Jul 2018 19:05:56 +0200 Subject: [PATCH 17/82] Bug 4662: Improved the maneuver counting of RaceOfCompetitorWithContext --- .../data/RaceOfCompetitorWithContext.java | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) 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..2227b229f0e 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; @@ -27,6 +31,7 @@ import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sse.common.Distance; 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.MillisecondsTimePoint; import com.sap.sse.datamining.data.Cluster; @@ -240,50 +245,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++; + } } } } From bdecac1b1351fc8af627a3ee2345b995fc3efc52 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Sun, 22 Jul 2018 19:37:52 +0200 Subject: [PATCH 18/82] Improved the ManeuverRetrievalProcessor so that maneuvers with a time point equal to the start of a leg aren't retrieved twice --- .../impl/components/ManeuverRetrievalProcessor.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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..1670f3a7083 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) { @@ -66,6 +70,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(); From f317c7cb857faa996b4856dea4af54871960001b Mon Sep 17 00:00:00 2001 From: Steffen Wagner Date: Mon, 23 Jul 2018 00:04:52 +0200 Subject: [PATCH 19/82] changed chmod 755 of start_foreground script --- java/target/start_foreground | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 java/target/start_foreground diff --git a/java/target/start_foreground b/java/target/start_foreground old mode 100644 new mode 100755 From 6b7ac2973122dc96374ded9e28507d76160de755 Mon Sep 17 00:00:00 2001 From: Steffen Wagner Date: Mon, 23 Jul 2018 00:37:26 +0200 Subject: [PATCH 20/82] adjusted systemd service for nextgen sailing ami --- java/target/sailing@.service | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From be775edba9d3b9182a41750ce25a3153fe8a8808 Mon Sep 17 00:00:00 2001 From: Steffen Schaefer Date: Mon, 23 Jul 2018 09:01:10 +0200 Subject: [PATCH 21/82] Bug 4661: Added release notes --- java/com.sap.sailing.www/release_notes_admin.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index daafe6c3709..c775e33f0bb 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -32,6 +32,19 @@ 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.
    • +
    +
  • June 2018

    From ff9ebaaef2d723eeee66e6cc655a1f3040be95ba Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 23 Jul 2018 11:25:49 +0200 Subject: [PATCH 22/82] aligned synchronous and asynchronous code paths when adding new fixes Bug 4551 --- .../shared/charts/EditMarkPositionPanel.java | 41 +++++++++++++++---- .../ui/client/shared/charts/MarksPanel.java | 35 +++++++++------- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java index 5fb4d1b13c6..f6fc1d8bac9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java @@ -5,9 +5,11 @@ import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -132,6 +134,8 @@ public class EditMarkPositionPanel extends AbstractRaceChart 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, @@ -859,11 +863,13 @@ public class EditMarkPositionPanel extends AbstractRaceChart i } private void selectMark(MarkDTO mark) { - selectedMark = mark; - if (currentFixPositionChooser != null) { - currentFixPositionChooser.cancel(); - currentFixPositionChooser = null; + if (selectedMark != mark) { + if (currentFixPositionChooser != null) { + currentFixPositionChooser.cancel(); + currentFixPositionChooser = null; + } } + 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 +911,43 @@ public class EditMarkPositionPanel extends AbstractRaceChart i @Override public void onSelectionChange(SelectionChangeEvent event) { final MarkDTO mark = marksPanel.getSelectedMark(); + retrieveAndSelectMarkIfNecessary(mark, null); + } + + protected void retrieveAndSelectMarkIfNecessary(final MarkDTO mark, final Runnable callback) { if (mark != null && (marks.get(mark) == null || marks.get(mark).isEmpty())) { - if (mark != null) { - markPositionService.getMarkTrack(raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier), + + if (marksCurrentlyRequestedViaRemoteCall.add(mark)) { + markPositionService.getMarkTrack( + raceIdentifierToLeaderboardRaceColumnAndFleetMapper + .getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier), mark.getIdAsString(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { - errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage()); + marksCurrentlyRequestedViaRemoteCall.remove(mark); + errorReporter.reportError( + stringMessages.errorCommunicatingWithServer() + ": " + caught.getMessage()); } - + @Override public void onSuccess(MarkTrackDTO result) { + marksCurrentlyRequestedViaRemoteCall.remove(mark); createMarkTrackUi(mark, result.getFixes()); selectMark(mark); + if (callback != null) { + callback.run(); + } } }); } + else { + // remote call of the same mark already in progress -> ignore this request + } } else { selectMark(mark); + if (callback != null) { + callback.run(); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java index cae1ca24db7..219e12485f8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java @@ -85,22 +85,27 @@ public class MarksPanel extends AbstractCompositeComponent { @Override public void update(int index, final MarkDTO mark, String value) { final Date timePoint = parent.timer.getTime(); - select(mark); - if (parent.hasFixAtTimePoint(mark, timePoint)) { - parent.showNotification(stringMessages.pleaseSelectOtherTimepoint(), NotificationType.ERROR); - } else { - parent.createFixPositionChooserToAddFixToMark(mark, new Callback() { - @Override - public void onFailure(Exception reason) { - parent.resetCurrentFixPositionChooser(); + parent.retrieveAndSelectMarkIfNecessary(mark, new Runnable() { + + @Override + public void run() { + if (parent.hasFixAtTimePoint(mark, timePoint)) { + parent.showNotification(stringMessages.pleaseSelectOtherTimepoint(), NotificationType.ERROR); + } else { + parent.createFixPositionChooserToAddFixToMark(mark, new Callback() { + @Override + public void onFailure(final Exception reason) { + parent.resetCurrentFixPositionChooser(); + } + @Override + public void onSuccess(Position result) { + parent.addMarkFix(mark, timePoint, result); + parent.resetCurrentFixPositionChooser(); + } + }); } - @Override - public void onSuccess(Position result) { - parent.addMarkFix(mark, timePoint, result); - parent.resetCurrentFixPositionChooser(); - } - }); - } + } + }); } }); markTable.addColumn(addFixColumn); From 49a94fd50589aa382b47cb066889de5f68d0dcde Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 23 Jul 2018 13:48:42 +0200 Subject: [PATCH 23/82] Bug 4551: extracted method to cancel the current fix position chooser + cancel the associated notification, if it is still visible. --- .../shared/charts/EditMarkPositionPanel.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java index f6fc1d8bac9..2150b14a4d7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java @@ -739,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) { @@ -758,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(); @@ -864,10 +872,7 @@ public class EditMarkPositionPanel extends AbstractRaceChart i private void selectMark(MarkDTO mark) { if (selectedMark != mark) { - if (currentFixPositionChooser != null) { - currentFixPositionChooser.cancel(); - currentFixPositionChooser = null; - } + cancelFixPositionChooserAndNotification(); } selectedMark = mark; if (selectedMark != null) { From dc759b88a978f855e37047bf11c4237c91a3355a Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 23 Jul 2018 15:58:17 +0200 Subject: [PATCH 24/82] Bug 4661: moved CSS with display-scaling logic into separate CSS class to make it possible to add and remove it specifically --- .../partials/regattacompetition/RegattaCompetition.gss | 6 +++--- .../regattacompetition/RegattaCompetitionFleetRace.ui.xml | 2 +- .../regattacompetition/RegattaCompetitionResources.java | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) 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/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..a6a1cff0550 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 @@ -5,7 +5,7 @@
    -
    +
    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(); } } From 9abc7abd6a2596d47add0ec0149b906070309596 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 23 Jul 2018 16:17:12 +0200 Subject: [PATCH 25/82] Bug 4661: added functionality to remove the CSS class with the display scaling logic from the RaceName-Element if there is more than one fleet --- .../RegattaCompetitionFleet.java | 15 +++++++++++++-- .../RegattaCompetitionFleetRace.java | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) 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()); + } } From 678508d4649b9b315e6b1a9a1e044c8399e8a6f5 Mon Sep 17 00:00:00 2001 From: wagnst Date: Mon, 23 Jul 2018 17:00:05 +0200 Subject: [PATCH 26/82] Orchestration delivery: base and create master scenario --- orchestration/.editorconfig | 17 + orchestration/.gitignore | 65 ++ orchestration/.gitlab-ci.yml | 77 ++ orchestration/Makefile | 94 +++ orchestration/bash/aws-setup.sh | 266 +++++++ orchestration/bash/lib/aws-setup.sh | 0 orchestration/bash/lib/build-config.sh | 251 +++++++ orchestration/bash/lib/functions_app.sh | 189 +++++ orchestration/bash/lib/functions_ec2.sh | 176 +++++ orchestration/bash/lib/functions_elb.sh | 101 +++ orchestration/bash/lib/functions_io.sh | 218 ++++++ orchestration/bash/lib/functions_wrapper.sh | 128 ++++ orchestration/bash/lib/require_variables.sh | 148 ++++ .../bash/lib/scenario_associate_alb.sh | 55 ++ orchestration/bash/lib/scenario_instance.sh | 92 +++ .../bash/lib/scenario_master_instance.sh | 68 ++ .../bash/lib/scenario_replica_instance.sh | 42 ++ .../bash/lib/scenario_shared_instance.sh | 176 +++++ orchestration/bash/lib/util_functions.sh | 423 +++++++++++ orchestration/bash/lib/utils.sh | 110 +++ orchestration/bash/lib/validation.sh | 37 + orchestration/bash/lib/variables.sh | 11 + orchestration/calculateCodeCoverage.sh | 13 + .../configs/instance_template/master.json | 12 + .../configs/secrets/conf_aws.json.sample | 7 + orchestration/createDistro.sh | 22 + orchestration/env.sh | 4 + orchestration/includeFilesDistro | 5 + .../src/sapsailing.com/agent/agent.go | 36 + .../src/sapsailing.com/agent/paths/paths.go | 42 ++ .../sapsailing.com/agent/primitive/apache.go | 76 ++ .../sapsailing.com/agent/primitive/common.go | 38 + .../agent/primitive/common_test.go | 17 + .../agent/primitive/instance.go | 179 +++++ .../agent/primitive/patch_bash.go | 106 +++ .../agent/primitive/patch_bash_test.go | 32 + .../sapsailing.com/agent/primitive/system.go | 47 ++ .../sapsailing.com/agent/scenario/invoke.go | 17 + .../sapsailing.com/agent/scenario/master.go | 139 ++++ .../src/sapsailing.com/common/agent.go | 32 + .../src/sapsailing.com/common/cmd/cmd.go | 136 ++++ .../src/sapsailing.com/common/cmd/cmd_test.go | 49 ++ .../src/sapsailing.com/common/common.go | 176 +++++ .../src/sapsailing.com/common/common_test.go | 11 + .../src/sapsailing.com/common/logging.go | 54 ++ .../src/sapsailing.com/common/sailing/api.go | 246 +++++++ .../sapsailing.com/common/sailing/api_test.go | 37 + .../orchestrator/agent/agent.go | 48 ++ .../orchestrator/agent/client.go | 184 +++++ .../orchestrator/agent/client_test.go | 35 + .../orchestrator/aws/cloudwatch.go | 149 ++++ .../orchestrator/aws/cloudwatch_test.go | 67 ++ .../orchestrator/aws/convert.go | 130 ++++ .../sapsailing.com/orchestrator/aws/data.go | 129 ++++ .../orchestrator/aws/instance.go | 221 ++++++ .../orchestrator/aws/instance_test.go | 89 +++ .../src/sapsailing.com/orchestrator/aws/lb.go | 662 ++++++++++++++++++ .../orchestrator/aws/lb_test.go | 249 +++++++ .../sapsailing.com/orchestrator/commands.go | 278 ++++++++ .../orchestrator/configuration/aws.go | 33 + .../orchestrator/configuration/aws_test.go | 15 + .../orchestrator/configuration/paths_test.go | 27 + .../orchestrator/orchestrator.go | 27 + .../orchestrator/paths/paths.go | 54 ++ orchestration/testdata/test.sh | 27 + 65 files changed, 6701 insertions(+) create mode 100644 orchestration/.editorconfig create mode 100644 orchestration/.gitignore create mode 100644 orchestration/.gitlab-ci.yml create mode 100644 orchestration/Makefile create mode 100755 orchestration/bash/aws-setup.sh create mode 100644 orchestration/bash/lib/aws-setup.sh create mode 100644 orchestration/bash/lib/build-config.sh create mode 100644 orchestration/bash/lib/functions_app.sh create mode 100644 orchestration/bash/lib/functions_ec2.sh create mode 100644 orchestration/bash/lib/functions_elb.sh create mode 100644 orchestration/bash/lib/functions_io.sh create mode 100644 orchestration/bash/lib/functions_wrapper.sh create mode 100644 orchestration/bash/lib/require_variables.sh create mode 100644 orchestration/bash/lib/scenario_associate_alb.sh create mode 100644 orchestration/bash/lib/scenario_instance.sh create mode 100644 orchestration/bash/lib/scenario_master_instance.sh create mode 100644 orchestration/bash/lib/scenario_replica_instance.sh create mode 100644 orchestration/bash/lib/scenario_shared_instance.sh create mode 100644 orchestration/bash/lib/util_functions.sh create mode 100644 orchestration/bash/lib/utils.sh create mode 100644 orchestration/bash/lib/validation.sh create mode 100644 orchestration/bash/lib/variables.sh create mode 100644 orchestration/calculateCodeCoverage.sh create mode 100644 orchestration/configs/instance_template/master.json create mode 100644 orchestration/configs/secrets/conf_aws.json.sample create mode 100644 orchestration/createDistro.sh create mode 100755 orchestration/env.sh create mode 100644 orchestration/includeFilesDistro create mode 100644 orchestration/src/sapsailing.com/agent/agent.go create mode 100644 orchestration/src/sapsailing.com/agent/paths/paths.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/apache.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/common.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/common_test.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/instance.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/patch_bash.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/patch_bash_test.go create mode 100644 orchestration/src/sapsailing.com/agent/primitive/system.go create mode 100644 orchestration/src/sapsailing.com/agent/scenario/invoke.go create mode 100644 orchestration/src/sapsailing.com/agent/scenario/master.go create mode 100644 orchestration/src/sapsailing.com/common/agent.go create mode 100644 orchestration/src/sapsailing.com/common/cmd/cmd.go create mode 100644 orchestration/src/sapsailing.com/common/cmd/cmd_test.go create mode 100644 orchestration/src/sapsailing.com/common/common.go create mode 100644 orchestration/src/sapsailing.com/common/common_test.go create mode 100644 orchestration/src/sapsailing.com/common/logging.go create mode 100644 orchestration/src/sapsailing.com/common/sailing/api.go create mode 100644 orchestration/src/sapsailing.com/common/sailing/api_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/agent/agent.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/agent/client.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/agent/client_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/cloudwatch_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/convert.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/data.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/instance.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/instance_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/lb.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/aws/lb_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/commands.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/configuration/aws.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/configuration/aws_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/configuration/paths_test.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/orchestrator.go create mode 100644 orchestration/src/sapsailing.com/orchestrator/paths/paths.go create mode 100644 orchestration/testdata/test.sh 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..45af72384ff --- /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))) + @/usr/bin/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 From a1951bd4b319be591c6b963fb1c0a47404afc016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 24 Jul 2018 08:23:37 +0200 Subject: [PATCH 27/82] bug4679 small formating fix --- .../RegattaCompetitionFleetRace.ui.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 a6a1cff0550..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 From 7f7e4e40d30f21ffeb8495a370a540c23c496745 Mon Sep 17 00:00:00 2001 From: wagnst Date: Tue, 24 Jul 2018 13:13:56 +0200 Subject: [PATCH 28/82] remove absolute path of chmod in Makefile --- orchestration/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestration/Makefile b/orchestration/Makefile index 45af72384ff..e6ca4b43bb8 100644 --- a/orchestration/Makefile +++ b/orchestration/Makefile @@ -72,7 +72,7 @@ build: $(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))) - @/usr/bin/chmod +x $${PREFIX}/bin/* + @chmod +x $${PREFIX}/bin/* @echo "done." clean: From 642dec6059cf2c08c26171d03405138614d24239 Mon Sep 17 00:00:00 2001 From: wagnst Date: Tue, 24 Jul 2018 14:51:16 +0200 Subject: [PATCH 29/82] initial wiki page for orchestration --- wiki/howto/development/orchestration.md | 96 ++++++++++++++++++ wiki/images/orchestration/architecture.png | Bin 0 -> 44610 bytes .../images/orchestration/hudson-artifacts.png | Bin 0 -> 22130 bytes 3 files changed, 96 insertions(+) create mode 100644 wiki/howto/development/orchestration.md create mode 100644 wiki/images/orchestration/architecture.png create mode 100644 wiki/images/orchestration/hudson-artifacts.png diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md new file mode 100644 index 00000000000..aaebcd6563d --- /dev/null +++ b/wiki/howto/development/orchestration.md @@ -0,0 +1,96 @@ +## 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 couse also use go test directly. Just make sure the PREFIX enviroment is set and points to the working directory of the orchestrator. You can also source env.sh to do this and also set the GOPATH correctly. By default, all go dependencies should be downloaded to `$HOME/.go`. + +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`. The verbose output of the 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 from [here](http://static.sapsailing.com/orchestration-0.0.1_7f7e4e40d30f21ffeb8495a370a540c23c496745.tgz). The releases of each build process 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`. Furthermore, please create an according `conf_aws.json` file (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" +} +``` + +You are ready to use the orchestration tool! + +## Run the software +All you need is to source the environment file env.sh, which you can find in the root folder. You will need to source it via + +``` +source env.sh +``` + +and then you can run the orchestrator using + +``` +./bin/orchestrator-linux-amd64 +``` + +As we use loglevels, you can increase logging messages to another level by specifying a ENV variable in front of the orchestrator command, like: + +``` +LOG=DEBUG ./bin/orchestrator-linux-amd64 +``` + +### General +In general, the orchestrator accepts a set of sub parameters. The first level describes the type of action to execute (like a use-case). The following parameters behind, are used to specify the use-case parameter with details like servername. + +To show all available just call the orchestrator without any parameters. + +Non use-case specific can be, to get the version of the orchestrator. To do so, just use + +``` +./bin/orchestrator-linux-amd64 version +``` + +It will show you the version, build revision (git commit) and the branch, from where the software was built (ideally you are using a version from master). + +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 +``` + +All those parameters are parsed by the orchestrator, when using the full EC2 bootstrap process. Some of the parameters are also optional (like `analyticsrelease`. + +### Use case: Create Master Event Server +This is the first ready to use and implemented use-case. It will spawn a new EC2 instance (on the AMI base called "NEXTGEN" (using Ubuntu 16 with systemd as base) and configure a new java instance on it. Furthermore it makes sure, that apache2 is configured correctly and if so the workflow continues to create according target groups (default and master), attach the just created EC2 instance, create according rules in the load balancer and finally creating an Cloudwatch alarm for all target groups to get notified, if a failure occurs inside a target group. + +The workflow can create multiple events on a java instance and configure apache2, EC2 load balancing and the java application itself (event creating, user creation, password change, ...). + +To use the workflow you simply use the orchestrator command `master`. A sample 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 the master 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 0000000000000000000000000000000000000000..d7ed0581f9cefe51f6132ade6ea0eaf8bbe5842b GIT binary patch literal 44610 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 Date: Tue, 24 Jul 2018 15:06:07 +0200 Subject: [PATCH 30/82] some more changes to the orchestration wiki page --- wiki/howto/development/orchestration.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md index aaebcd6563d..bdce47dd2a3 100644 --- a/wiki/howto/development/orchestration.md +++ b/wiki/howto/development/orchestration.md @@ -4,10 +4,10 @@ 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)) +![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 couse also use go test directly. Just make sure the PREFIX enviroment is set and points to the working directory of the orchestrator. You can also source env.sh to do this and also set the GOPATH correctly. By default, all go dependencies should be downloaded to `$HOME/.go`. +Currently we provide a Makefile, which implements all required steps to build and test the software. Optionally you can of couse also use go test directly. Just make sure the `PREFIX` enviroment is set and points to the working directory of the orchestrator. You can also `source env.sh` to do this and also set the `GOPATH` correctly. By default, all go dependencies should be downloaded to `$HOME/.go`. 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`. The verbose output of the logs is written to a folder called testlogs (which initially needs to be created). @@ -15,7 +15,7 @@ To test the software, just call `make test` and to check the tests, call `make c ## Get and prepare the software You can get the current release of the orchestration software from [here](http://static.sapsailing.com/orchestration-0.0.1_7f7e4e40d30f21ffeb8495a370a540c23c496745.tgz). The releases of each build process 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)) +![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`. Furthermore, please create an according `conf_aws.json` file (copy `conf_aws.json.sample` and fill in the values). A good example is @@ -30,7 +30,7 @@ To get going, place your private key inside the secrets folder and name it `id_r } ``` -You are ready to use the orchestration tool! +You are ready to use the orchestration tool! Please note, that currently only linux-adm64 versions are built. Windows versions could be easily added, as go can also run under windows and the project currently does not have any hard dependencies to linux. ## Run the software All you need is to source the environment file env.sh, which you can find in the root folder. You will need to source it via @@ -56,7 +56,7 @@ In general, the orchestrator accepts a set of sub parameters. The first level de To show all available just call the orchestrator without any parameters. -Non use-case specific can be, to get the version of the orchestrator. To do so, just use +Non use-case specific can be, to get the `version` of the orchestrator. To do so, just use ``` ./bin/orchestrator-linux-amd64 version @@ -70,7 +70,7 @@ If you want to skip some steps done by the orchestrator on AWS level, you can us ./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 ``` -All those parameters are parsed by the orchestrator, when using the full EC2 bootstrap process. Some of the parameters are also optional (like `analyticsrelease`. +All those parameters are parsed by the orchestrator, when using the full EC2 bootstrap process. Some of the parameters are also optional (like `analyticsrelease`). ### Use case: Create Master Event Server This is the first ready to use and implemented use-case. It will spawn a new EC2 instance (on the AMI base called "NEXTGEN" (using Ubuntu 16 with systemd as base) and configure a new java instance on it. Furthermore it makes sure, that apache2 is configured correctly and if so the workflow continues to create according target groups (default and master), attach the just created EC2 instance, create according rules in the load balancer and finally creating an Cloudwatch alarm for all target groups to get notified, if a failure occurs inside a target group. @@ -84,13 +84,19 @@ To use the workflow you simply use the orchestrator command `master`. A sample c ``` The following parameters are required for the master 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= +``` From 0d2598e298c000ff8430d1a50602e2f63a90c5eb Mon Sep 17 00:00:00 2001 From: Steffen Wagner Date: Tue, 24 Jul 2018 17:05:54 +0200 Subject: [PATCH 31/82] modified orchestrator wiki page --- wiki/howto/development/orchestration.md | 61 ++++++++++++++++--------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md index bdce47dd2a3..d898f1defca 100644 --- a/wiki/howto/development/orchestration.md +++ b/wiki/howto/development/orchestration.md @@ -7,18 +7,19 @@ The orchestrator takes care on all actions in regards of AWS and to execute the ![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 couse also use go test directly. Just make sure the `PREFIX` enviroment is set and points to the working directory of the orchestrator. You can also `source env.sh` to do this and also set the `GOPATH` correctly. By default, all go dependencies should be downloaded to `$HOME/.go`. +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 working directory of the orchestrator. You can also use `source env.sh` to do this, which also set 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`. The verbose output of the logs is written to a folder called testlogs (which initially needs to be created). +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 from [here](http://static.sapsailing.com/orchestration-0.0.1_7f7e4e40d30f21ffeb8495a370a540c23c496745.tgz). The releases of each build process 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). +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`. Furthermore, please create an according `conf_aws.json` file (copy `conf_aws.json.sample` and fill in the values). A good example is +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, please create an according `conf_aws.json` file (copy `conf_aws.json.sample` and fill in the values). A good example is ``` { @@ -30,60 +31,78 @@ To get going, place your private key inside the secrets folder and name it `id_r } ``` -You are ready to use the orchestration tool! Please note, that currently only linux-adm64 versions are built. Windows versions could be easily added, as go can also run under windows and the project currently does not have any hard dependencies to linux. +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 -All you need is to source the environment file env.sh, which you can find in the root folder. You will need to source it via +All you need is to source the environment file `env.sh`, which you can find in the root folder. You will need to source it via ``` source env.sh ``` -and then you can run the orchestrator using +Then you can run the orchestrator using ``` ./bin/orchestrator-linux-amd64 ``` -As we use loglevels, you can increase logging messages to another level by specifying a ENV variable in front of the orchestrator command, like: +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`. ### General -In general, the orchestrator accepts a set of sub parameters. The first level describes the type of action to execute (like a use-case). The following parameters behind, are used to specify the use-case parameter with details like servername. +In general, the orchestrator accepts a set of sub parameters. The first level of parameters describe the type of action to be executed (basically a use-case). The parameters behind, are used to specify the use-case specific parameters. To show all available just call the orchestrator without any parameters. -Non use-case specific can be, to get the `version` of the orchestrator. To do so, just use +Non use-case specific are example commands like the `version` of the orchestrator. To do so, just use ``` ./bin/orchestrator-linux-amd64 version ``` -It will show you the version, build revision (git commit) and the branch, from where the software was built (ideally you are using a version from master). +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 +If you want to skip some steps done by the orchestrator on AWS level, you can use a 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 according 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. -All those parameters are parsed by the orchestrator, when using the full EC2 bootstrap process. Some of the parameters are also optional (like `analyticsrelease`). +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. -### Use case: Create Master Event Server -This is the first ready to use and implemented use-case. It will spawn a new EC2 instance (on the AMI base called "NEXTGEN" (using Ubuntu 16 with systemd as base) and configure a new java instance on it. Furthermore it makes sure, that apache2 is configured correctly and if so the workflow continues to create according target groups (default and master), attach the just created EC2 instance, create according rules in the load balancer and finally creating an Cloudwatch alarm for all target groups to get notified, if a failure occurs inside a target group. +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, ...). -The workflow can create multiple events on a java instance and configure apache2, EC2 load balancing and the java application itself (event creating, user creation, password change, ...). - -To use the workflow you simply use the orchestrator command `master`. A sample could look like: +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 the master use-case: +The following parameters are required for this use-case: ``` - event=: (the command can be repeated to create multiple events on the java server) @@ -97,6 +116,6 @@ Optionally you can specify the following parameters: ``` - template-file= -- release= +- release= - notify= ``` From a143d7e421b31a5f4d975371a64caf6627acedf2 Mon Sep 17 00:00:00 2001 From: Steffen Wagner Date: Tue, 24 Jul 2018 17:14:20 +0200 Subject: [PATCH 32/82] more changes in the orchestration wiki --- wiki/howto/development/orchestration.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md index d898f1defca..7d151062109 100644 --- a/wiki/howto/development/orchestration.md +++ b/wiki/howto/development/orchestration.md @@ -74,9 +74,9 @@ We offer the following log levels: `DEBUG`, `INFO` (default), `WARNING`, `PANIC` ### General In general, the orchestrator accepts a set of sub parameters. The first level of parameters describe the type of action to be executed (basically a use-case). The parameters behind, are used to specify the use-case specific parameters. -To show all available just call the orchestrator without any parameters. +To show all available commands just call the orchestrator without any parameters. -Non use-case specific are example commands like the `version` of the orchestrator. To do so, just use +Non use-case specific commands are example commands like the `version` of the orchestrator. To do so, just use ``` ./bin/orchestrator-linux-amd64 version @@ -84,11 +84,12 @@ Non use-case specific are example commands like the `version` of the 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 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 +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 according 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. From 4d915c0f424e02f663a357af29ba62015e248502 Mon Sep 17 00:00:00 2001 From: Steffen Wagner Date: Tue, 24 Jul 2018 17:38:06 +0200 Subject: [PATCH 33/82] more changes to the orchestration wiki page --- wiki/howto/development/orchestration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md index 7d151062109..2c9b9d99e43 100644 --- a/wiki/howto/development/orchestration.md +++ b/wiki/howto/development/orchestration.md @@ -7,7 +7,7 @@ The orchestrator takes care on all actions in regards of AWS and to execute the ![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 working directory of the orchestrator. You can also use `source env.sh` to do this, which also set the `GOPATH` correctly. By default, all go dependencies should be downloaded to `$HOME/.go`. They should not be stored in the git project location. +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. @@ -19,7 +19,7 @@ You can get the current release of the orchestration software currently from [he ![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, please create an according `conf_aws.json` file (copy `conf_aws.json.sample` and fill in the values). A good example is +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 ``` { @@ -72,7 +72,7 @@ LOG=DEBUG ./bin/orchestrator-linux-amd64 We offer the following log levels: `DEBUG`, `INFO` (default), `WARNING`, `PANIC`, `FATAL`. ### General -In general, the orchestrator accepts a set of sub parameters. The first level of parameters describe the type of action to be executed (basically a use-case). The parameters behind, are used to specify the use-case specific parameters. +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. @@ -91,7 +91,7 @@ If you want to skip some steps done by the orchestrator on AWS level, you can us ``` ### 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 according 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. +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. From eea8060beae728d507cc5c6e6286dbb3b6879e2d Mon Sep 17 00:00:00 2001 From: Steffen Wagner Date: Tue, 24 Jul 2018 17:42:49 +0200 Subject: [PATCH 34/82] orchestration wiki removed unnecessary step --- wiki/howto/development/orchestration.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/wiki/howto/development/orchestration.md b/wiki/howto/development/orchestration.md index 2c9b9d99e43..26e5286e545 100644 --- a/wiki/howto/development/orchestration.md +++ b/wiki/howto/development/orchestration.md @@ -52,13 +52,7 @@ Inside the `config` folder you will find a folder called `instance_templates`. Y 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 -All you need is to source the environment file `env.sh`, which you can find in the root folder. You will need to source it via - -``` -source env.sh -``` - -Then you can run the orchestrator using +You can run the orchestrator using ``` ./bin/orchestrator-linux-amd64 @@ -69,7 +63,7 @@ As we use log levels, you can increase logging verbosity to a different level by ``` LOG=DEBUG ./bin/orchestrator-linux-amd64 ``` -We offer the following log levels: `DEBUG`, `INFO` (default), `WARNING`, `PANIC`, `FATAL`. +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. From 516728a4e7112e71c087b84c2b2007a1a56a21dd Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Wed, 25 Jul 2018 11:00:35 +0200 Subject: [PATCH 35/82] bug4671 Fixed typos and channel usages --- .../gwt/ui/client/media/JSDownloadUtils.java | 8 ++++---- .../sailing/gwt/ui/server/MediaServiceImpl.java | 15 +++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) 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 1b875229a2a..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,7 +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_USEAGE_FOR_DL = 1024.0 * 1024 * 1024 * 2; + protected static final Double MAX_MEMORY_USAGE_FOR_DL = 1024 * 1024 * 1024 * 2d; public interface JSDownloadCallback { void progress(Double current, Double total); @@ -44,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, MAX_MEMORY_USEAGE_FOR_DL); + getDataSlow(url, callback, REQUIRED_SIZE, MAX_MEMORY_USAGE_FOR_DL); } } }); @@ -145,14 +145,14 @@ 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, - Double maxMemoryUseage) /*-{ + 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 > maxMemoryUseage || evt.loaded > maxMemoryUseage) { + 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); 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 5e8e010cc25..16d309f6e26 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java @@ -222,17 +222,16 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi private VideoMetadataDTO checkMetadataByFullFileDownload(URL input) throws ParserConfigurationException, SAXException, IOException { - File tmp = null; + final File tmp = File.createTempFile("upload", "metadataCheck"); try { - tmp = File.createTempFile("upload", "metadataCheck"); - ReadableByteChannel rbc = Channels.newChannel(input.openStream()); + 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(Channels.newChannel(input.openStream()))) { - boolean canDownload = true; - Date recordStartedTimer = determineRecordingStart(isof); - Duration duration = determineDuration(isof); - boolean spherical = determine360(isof); + 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, ""); } From c31c07904132602be79eaa03e009edb4fe8100ad Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Wed, 25 Jul 2018 11:49:01 +0200 Subject: [PATCH 36/82] Reduced tolerance for comparison of incremental maneuver detector performance against non-incremental maneuver detector to 20% --- .../impl/IncrementalManeuverComputationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..031f22ec7fa 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 @@ -133,8 +133,8 @@ public class IncrementalManeuverComputationTest extends AbstractManeuverDetectio 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", + millisForIncrementalManeuverDetection * 1.2 < millisForNormalManeuverDetection); } } From 01fe50ff04eabad98cbce753fd15e4b5c6853aa0 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Wed, 25 Jul 2018 12:23:11 +0200 Subject: [PATCH 37/82] Fixed incorrect maneuver angle calculation from twa in getManeuverLikelihoodAndTwsTwa --- .../com/sap/sailing/polars/impl/PolarDataServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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..f83866213eb 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; From b51671237fbc4e2ece0b41c4b143532031fdc436 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Wed, 25 Jul 2018 12:53:38 +0200 Subject: [PATCH 38/82] Corrected minDiff calculation between best maneuver angle candidates --- .../src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f83866213eb..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 @@ -191,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; From 6f04dad8b4f6dffaecf19bc385ef85f034609e51 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Wed, 25 Jul 2018 14:31:45 +0200 Subject: [PATCH 39/82] Order of SuggestBoxStatisticProvider is now case independent. Fixed typo in sailing data mining properties --- .../resources/stringmessages/Sailing_StringMessages.properties | 2 +- .../ui/client/selection/SuggestBoxStatisticProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..66575e7438b 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 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..db03c0749f6 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 @@ -531,7 +531,7 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent Date: Wed, 25 Jul 2018 12:49:49 +0000 Subject: [PATCH 40/82] Updated amazon ec2 (markdown) --- wiki/info/landscape/amazon-ec2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/wiki/info/landscape/amazon-ec2.md b/wiki/info/landscape/amazon-ec2.md index 033b8f29024..dcc9dc95d36 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/motd 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. From d2b9d83cb3231a76ef9cf2286ff3afc1d3fc5a1b Mon Sep 17 00:00:00 2001 From: EC2 Server Date: Wed, 25 Jul 2018 12:58:20 +0000 Subject: [PATCH 41/82] Updated sailing init script to work execute logrotate -f on shutdown, also improved configuration handling for chkconfig --- configuration/sailing | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/configuration/sailing b/configuration/sailing index 2764e008d49..11b5baadca6 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,22 @@ 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) start_servers + touch /var/lock/subsys/sailing ;; stop) stop_servers + rm -f /var/lock/subsys/sailing ;; status) status java From 77d25eb5807a33746dd3f64eec18c3cec0794655 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Wed, 25 Jul 2018 15:16:22 +0200 Subject: [PATCH 42/82] Fixed fallback twa for jibes --- .../src/com/sap/sailing/polars/mining/PolarDataMiner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From d3a832746a2994a2ea5a4d7b2212b0ce63fb6576 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Wed, 25 Jul 2018 15:28:16 +0200 Subject: [PATCH 43/82] Added race duration statistic to HasTrackedRaceContext --- .../Sailing_StringMessages.properties | 3 ++- .../Sailing_StringMessages_de.properties | 3 ++- .../data/HasTrackedRaceContext.java | 8 +++++-- .../impl/data/TrackedRaceWithContext.java | 22 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) 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 66575e7438b..c67e1bcfb75 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties @@ -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/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/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() { From 2b6822c812524953970b40f2450e9142bf7f1f86 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Wed, 25 Jul 2018 15:58:51 +0200 Subject: [PATCH 44/82] Added race duration statistic to HasRaceOfCompetitorContext --- .../data/HasRaceOfCompetitorContext.java | 51 ++++++++++--------- .../data/RaceOfCompetitorWithContext.java | 16 ++++++ 2 files changed, 44 insertions(+), 23 deletions(-) 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..32c3e23ad15 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(); + 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/impl/data/RaceOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java index 2227b229f0e..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 @@ -29,10 +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; @@ -357,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 From 6bbbbdb3a98953378e6054d6dfc9758bebd3351a Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Wed, 25 Jul 2018 17:10:00 +0200 Subject: [PATCH 45/82] Improved performance benefit calculation and error message --- .../impl/IncrementalManeuverComputationTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 031f22ec7fa..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 20% faster in detecting maneuvers incrementally, than the full maneuver detection", - millisForIncrementalManeuverDetection * 1.2 < 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); } } From 3f8d74c3fdb633b44d23dd0a3a6a7e711438b8f9 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Wed, 25 Jul 2018 20:34:28 +0200 Subject: [PATCH 46/82] Fixed estimation data rest service --- ...TrackWithEstimationDataJsonSerializer.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) 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)); } } From b57bfd39ec047ad991fb2eb8e2313a97f1fe0742 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Jul 2018 21:24:56 +0200 Subject: [PATCH 47/82] formatting, coding conventions Change-Id: I7a819c3662b2d2d4528642897c15ed6dea451731 --- .../gwt/ui/server/SailingServiceImpl.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 03a47b7418d..00b9a06cb3f 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 @@ -1843,7 +1843,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 @@ -1856,10 +1855,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); @@ -1870,18 +1871,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; From 66405e2ccb24fcbcdab42743cf5961549bff0bd8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Jul 2018 21:25:24 +0200 Subject: [PATCH 48/82] improved parameter naming Change-Id: I4a5bbc8d27e4543f348ed0c32d795974b5dc64cf --- .../sailing/domain/tracking/impl/CrossTrackErrorCache.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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..2cde239415b 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 @@ -250,10 +250,10 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { } /** - * @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; @@ -294,7 +294,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; } From ce2813c818d6eabd5f593dda085d6110f6b9c965 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Jul 2018 21:26:14 +0200 Subject: [PATCH 49/82] removed System.out statement from GWT code Change-Id: I1eb58b591b29fc3b337fb6dc6d2e0ec2de778dd3 --- .../com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java | 1 - 1 file changed, 1 deletion(-) 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()); } From 165301ee4dd33ec4d83b35bcddc3fc7fa387584f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Jul 2018 21:26:43 +0200 Subject: [PATCH 50/82] bug4684: in WinningLanesMode ensure that competitor selection is set before extending tail length Change-Id: I855243d9ae790688e1482904d6ab6447abb67df9 --- .../sap/sailing/gwt/ui/raceboard/WinningLanesMode.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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..d7da164f951 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); } - }); } @@ -129,17 +127,17 @@ 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(); } } From 93f6ab0feb94608fc668eb35716167d626f62c38 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Jul 2018 21:51:25 +0200 Subject: [PATCH 51/82] used lambda style where appropriate; made variables final where possible Change-Id: Ifd23ddc7c2809fed59937ca356903f4801b47379 --- .../tracking/impl/CrossTrackErrorCache.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) 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 2cde239415b..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,12 +236,7 @@ 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); } @@ -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; From 0fcec3fba89e541682ed89c1e977c31c9888d532 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Jul 2018 22:10:20 +0200 Subject: [PATCH 52/82] removed extra empty lines Change-Id: Id4516ecf1173223d9f355b96fdfc7b94e47e0ff4 --- .../com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java | 4 ---- 1 file changed, 4 deletions(-) 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 d7da164f951..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 @@ -77,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); } - }); } @@ -139,5 +136,4 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors { adjustMapSettings(); } } - } From 9a6357cc2b1c41fa563d16a7776c87f55527fd7a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Jul 2018 09:58:41 +0200 Subject: [PATCH 53/82] improved Selenium parameter names Change-Id: I2e4181c4f3c105b23ca33c94d4d5fd7fb47beb0b --- .../sap/sailing/selenium/pages/HostPage.java | 11 +++-- .../sailing/selenium/pages/PageObject.java | 44 +++++++++---------- 2 files changed, 27 insertions(+), 28 deletions(-) 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 288d2c4af7e..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)); } From ba90962cb83c51470b0be9c23070fb43ba891bac Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Jul 2018 14:02:46 +0200 Subject: [PATCH 54/82] Fixed some data mining annotation typos --- .../com/sap/sailing/datamining/data/HasBravoFixContext.java | 2 +- .../sailing/datamining/data/HasRaceOfCompetitorContext.java | 2 +- .../com/sap/sailing/datamining/data/HasWindFixContext.java | 2 +- .../datamining/components/management/FunctionProvider.java | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) 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/HasRaceOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java index 32c3e23ad15..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 @@ -18,7 +18,7 @@ public interface HasRaceOfCompetitorContext { HasTrackedRaceContext getTrackedRaceContext(); @Connector(messageKey="Competitor") - @Statistic(messageKey="") + @Statistic(messageKey="Competitor") Competitor getCompetitor(); @Dimension(messageKey="TackAtStart", ordinal=12) 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.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); From 832156bd79850c826c6e589d7d63db424ea8a412 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Thu, 26 Jul 2018 12:28:26 +0000 Subject: [PATCH 55/82] Updated amazon ec2 (markdown) --- wiki/info/landscape/amazon-ec2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/info/landscape/amazon-ec2.md b/wiki/info/landscape/amazon-ec2.md index dcc9dc95d36..1f2668dec92 100644 --- a/wiki/info/landscape/amazon-ec2.md +++ b/wiki/info/landscape/amazon-ec2.md @@ -514,7 +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/motd to set the current version +* 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. From e30595cd795622839a93cee1011674d673931e13 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Jul 2018 15:53:03 +0200 Subject: [PATCH 56/82] The SuggestBoxStatisticProvider now shows the retriever chain by default --- .../SuggestBoxStatisticProvider.java | 54 +++---------------- .../suggestion/AbstractListSuggestOracle.java | 5 +- .../suggestion/AbstractSuggestOracle.java | 10 ++-- 3 files changed, 17 insertions(+), 52 deletions(-) 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 db03c0749f6..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 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) { From e8c43db1142b387e431054267a47f44e38f01b06 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Thu, 26 Jul 2018 16:25:01 +0200 Subject: [PATCH 57/82] Changed polars based features for maneuver estimation data --- ...mpleteManeuverCurveWithEstimationData.java | 4 +-- ...teManeuverCurveWithEstimationDataImpl.java | 25 +++++++++-------- ...ithEstimationDataSupportDecoratorImpl.java | 19 +++++++------ ...nDataSerializationDeserializationTest.java | 28 ++++++++----------- ...rveWithEstimationDataJsonDeserializer.java | 11 ++++---- ...CurveWithEstimationDataJsonSerializer.java | 10 +++---- 6 files changed, 46 insertions(+), 51 deletions(-) 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.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/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; } From 9c83e29ab0051ed0d915dd5fed5837486cc7ebc8 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Jul 2018 17:07:14 +0200 Subject: [PATCH 58/82] Removed unused class and moved the filter selection UI components to the other data mining UI components --- .../META-INF/MANIFEST.MF | 1 - .../DimensionFilterSelectionProvider.java | 2 +- ...lDimensionListFilterSelectionProvider.java | 2 +- .../QueryDefinitionProviderWithControls.java | 1 - .../ControllableMultiSelectionModel.java | 166 ------------------ 5 files changed, 2 insertions(+), 170 deletions(-) rename java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/{filter => }/DimensionFilterSelectionProvider.java (99%) rename java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/{filter => }/HierarchicalDimensionListFilterSelectionProvider.java (99%) delete mode 100644 java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/ControllableMultiSelectionModel.java 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/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/filter/ControllableMultiSelectionModel.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/ControllableMultiSelectionModel.java deleted file mode 100644 index 0214c5d534e..00000000000 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/ControllableMultiSelectionModel.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.sap.sse.datamining.ui.client.selection.filter; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import com.google.gwt.view.client.MultiSelectionModel; -import com.google.gwt.view.client.ProvidesKey; -import com.google.gwt.view.client.SelectionChangeEvent; - -/** - * A multi selection model, that allows to block the selection change notifications. - * @author Lennart Hensler (D054527) - */ -public class ControllableMultiSelectionModel 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 From c0f16266ef7cff0f8a95d3f4cd6393fce6ba158e Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Jul 2018 17:48:41 +0200 Subject: [PATCH 59/82] Fixed the generic type arguments of LeaderboardGroupRetrievalProcessor fixing a dimension duplicate for leaderboard group name --- .../TestLeaderboardGroupRetrievalProcessor.java | 10 +++++----- .../SailingDataRetrievalChainDefinitions.java | 8 ++++---- .../components/LeaderboardGroupRetrievalProcessor.java | 9 +++++---- .../impl/components/LeaderboardRetrievalProcessor.java | 8 ++++---- 4 files changed, 18 insertions(+), 17 deletions(-) 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/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/impl/components/LeaderboardGroupRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupRetrievalProcessor.java index 6e4a8acecfa..4b5278e1a13 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupRetrievalProcessor.java @@ -4,21 +4,22 @@ import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; +import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.server.RacingEventService; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor; -public class LeaderboardGroupRetrievalProcessor 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) { + protected Iterable retrieveData(RacingEventService element) { final PolarDataService polarDataService = element.getPolarDataService(); return element.getLeaderboardGroups() .values() 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..2c0eb1b9d09 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,21 +5,21 @@ 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())); From 4e9744f196f5b9e1b87016ef4deb2f02d37a57a9 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Jul 2018 17:55:56 +0200 Subject: [PATCH 60/82] Improved toString of ConcatenatingCompoundFunction --- .../impl/functions/ConcatenatingCompoundFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e2a67a178e73c8b27c043339ec1c221a5dcf2fe5 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Jul 2018 17:59:39 +0200 Subject: [PATCH 61/82] Removed the polar data service from HasLeaderboardContext --- .../sailing/datamining/data/HasLeaderboardContext.java | 3 --- ...ompetitorOfRaceInLeaderboardRetrievalProcessor.java | 2 +- ...euverCurveWithEstimationDataRetrievalProcessor.java | 2 +- .../impl/components/LeaderboardRetrievalProcessor.java | 2 +- .../datamining/impl/data/LeaderboardWithContext.java | 10 +--------- 5 files changed, 4 insertions(+), 15 deletions(-) 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/impl/components/CompetitorOfRaceInLeaderboardRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompetitorOfRaceInLeaderboardRetrievalProcessor.java index 5d81bac8881..d1557a55763 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 @@ -26,7 +26,7 @@ public class CompetitorOfRaceInLeaderboardRetrievalProcessor extends for (RaceColumn raceColumn : element.getLeaderboard().getRaceColumns()) { for (Competitor competitor : element.getLeaderboard().getCompetitors()) { 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..ad335b82fd8 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,7 +48,7 @@ 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); Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); Iterable maneuversWithEstimationData = maneuverDetector 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 2c0eb1b9d09..9dd837d36b1 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 @@ -22,7 +22,7 @@ public class LeaderboardRetrievalProcessor extends AbstractRetrievalProcessor retrieveData(HasLeaderboardGroupContext element) { Collection leaderboardsWithContext = new ArrayList<>(); for (Leaderboard leaderboard : element.getLeaderboardGroup().getLeaderboards()) { - leaderboardsWithContext.add(new LeaderboardWithContext(leaderboard, element, element.getPolarDataService())); + leaderboardsWithContext.add(new LeaderboardWithContext(leaderboard, element)); } return leaderboardsWithContext; } 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(); From a90a11fdf0cf74e317c02de2188fe81da3620ec6 Mon Sep 17 00:00:00 2001 From: "service.tip.git" Date: Thu, 26 Jul 2018 16:15:49 +0000 Subject: [PATCH 62/82] [INTERNAL] Translation delivery: commit by SLS Change-Id: Ib2791b87945cea21bfd801fcf6f2cb1256ac62a8 --- .../gwt/ui/client/StringMessages_es.properties | 11 +++++++---- .../gwt/ui/client/StringMessages_fr.properties | 11 +++++++---- .../gwt/ui/client/StringMessages_ja.properties | 11 +++++++---- .../gwt/ui/client/StringMessages_pt.properties | 11 +++++++---- .../gwt/ui/client/StringMessages_ru.properties | 11 +++++++---- .../gwt/ui/client/StringMessages_zh.properties | 11 +++++++---- 6 files changed, 42 insertions(+), 24 deletions(-) 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} From 2a50bd4d2cf139896a9e6469115b56ae5c60d012 Mon Sep 17 00:00:00 2001 From: EC2 Server Date: Fri, 27 Jul 2018 12:31:25 +0000 Subject: [PATCH 63/82] Ensure that update-motd us run when server starts --- configuration/sailing | 1 + 1 file changed, 1 insertion(+) diff --git a/configuration/sailing b/configuration/sailing index 11b5baadca6..82d4c09b9a4 100755 --- a/configuration/sailing +++ b/configuration/sailing @@ -117,6 +117,7 @@ stop_httpd() { # See how we were called. case "$1" in start) + /usr/sbin/update-motd start_servers touch /var/lock/subsys/sailing ;; From ca00f7484c3392a41dc194a3e75cff7c41d2bbec Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Sat, 28 Jul 2018 18:36:32 +0200 Subject: [PATCH 64/82] Implemented a check to skip the exection of already scheduled instructions, if the responsible processor has been aborted --- .../components/ProcessorInstructionHandler.java | 8 +++++--- .../impl/components/AbstractProcessorInstruction.java | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) 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..e01461324d7 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(); } 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..ff404891ac0 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,8 +52,10 @@ public abstract class AbstractProcessorInstruction implements Proces @Override public void run() { try { - ResultType result = computeResult(); - handler.instructionSucceeded(result); + if (!handler.isAborted()) { + ResultType result = computeResult(); + handler.instructionSucceeded(result); + } } catch (Exception e) { handler.instructionFailed(e); } finally { From 3c31932f38a93f5ed037c6298fef2a2e7d36d55f Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Sun, 29 Jul 2018 13:43:05 +0200 Subject: [PATCH 65/82] Added test of AbstractParallelProcessor.processElement in different states with a simple execution context --- ...actParallelProcessorElementProcessing.java | 124 ++++++++++++++++++ .../test/util/ConcurrencyTestsUtil.java | 3 +- .../StatefulBlockingInstruction.java | 47 +++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorElementProcessing.java create mode 100644 java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulBlockingInstruction.java 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..55ff4525adf --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestAbstractParallelProcessorElementProcessing.java @@ -0,0 +1,124 @@ +package com.sap.sse.datamining.impl.components; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +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.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 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<>(); + processor = new AbstractParallelProcessor(Integer.class, Object.class, executor, Collections.emptySet()) { + @Override + protected ProcessorInstruction createInstruction(Integer sleepTime) { + StatefulBlockingInstruction instruction = new StatefulBlockingInstruction(this, sleepTime); + createdInstructions.add(instruction); + return instruction; + } + @Override + protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { } + }; + } + + @Test + public void testSimpleProcessing() throws InterruptedException { + int elementCount = executor.getMaximumPoolSize() * 2; + for (int i = 0; i < elementCount; i++) { + processor.processElement(10); + } + + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + executor.shutdown(); + assertThat("Executor couldn't terminate", executor.awaitTermination(1, TimeUnit.SECONDS), is(true)); + for (StatefulBlockingInstruction instruction : createdInstructions) { + assertThat("run wasn't called", instruction.runWasCalled(), is(true)); + assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); + assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); + } + } + + @Test + public void testProcessingAfterFinish() throws InterruptedException { + int instructionDuration = 50; + int elementCount = executor.getMaximumPoolSize() + 1; // Last instruction will be queued + for (int i = 0; i < elementCount; i++) { + processor.processElement(instructionDuration); + } + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + Thread.sleep(instructionDuration / 3); // Giving some time to ensure execution of unqueued instructions + Thread finishingThread = ConcurrencyTestsUtil.tryToFinishTheProcessorInAnotherThread(processor); + do { + Thread.sleep(1); + } while (!finishingThread.isAlive()); + assertThat("Processor is already finished", processor.isFinished(), is(false)); + // Processor not yet finished. New elements will be accepted + processor.processElement(instructionDuration); + elementCount++; + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + finishingThread.join(1000); + assertThat("Processor isn't finished", processor.isFinished(), is(true)); + processor.processElement(instructionDuration); + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + for (StatefulBlockingInstruction instruction : createdInstructions) { + assertThat("run wasn't called", instruction.runWasCalled(), is(true)); + assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); + assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); + } + } + + @Test + public void testProcessingAfterAbort() throws InterruptedException { + int instructionDuration = 50; + int elementCount = executor.getMaximumPoolSize() + 1; // Last instruction will be queued + for (int i = 0; i < elementCount; i++) { + processor.processElement(instructionDuration); + } + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + Thread.sleep(instructionDuration / 3); // Giving some time to ensure execution of unqueued instructions + processor.abort(); + processor.processElement(0); + assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); + + executor.shutdown(); + assertThat("Executor couldn't terminate", executor.awaitTermination(1, TimeUnit.SECONDS), is(true)); + for (int i = 0; i < createdInstructions.size(); i++) { + StatefulBlockingInstruction instruction = createdInstructions.get(i); + assertThat("run wasn't called", instruction.runWasCalled(), is(true)); + // Last instructions was executed after the processor has been aborted. computeResult() should not be called + if (i == createdInstructions.size() - 1) { + assertThat("computeResult of last instruction was called", instruction.computeResultWasCalled(), is(false)); + assertThat("computeResult of last instruction finished", instruction.computeResultWasFinished(), is(false)); + } else { + assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); + assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); + } + } + } + +} 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..b892e3ea813 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 @@ -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/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..3055d8cbc7b --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulBlockingInstruction.java @@ -0,0 +1,47 @@ +package com.sap.sse.datamining.test.util.components; + +import com.sap.sse.datamining.components.ProcessorInstructionHandler; +import com.sap.sse.datamining.impl.components.AbstractProcessorInstruction; + +public class StatefulBlockingInstruction extends AbstractProcessorInstruction { + + private final int duration; + + private boolean runWasCalled; + private boolean computeResultWasCalled; + private boolean computeResultWasFinished; + + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, int duration) { + super(handler); + this.duration = duration; + } + + @Override + public void run() { + runWasCalled = true; + super.run(); + } + + @Override + protected Object computeResult() throws Exception { + computeResultWasCalled = true; + if (duration > 0) { + Thread.sleep(duration); + } + computeResultWasFinished = true; + return null; + } + + public boolean runWasCalled() { + return runWasCalled; + } + + public boolean computeResultWasCalled() { + return computeResultWasCalled; + } + + public boolean computeResultWasFinished() { + return computeResultWasFinished; + } + +} \ No newline at end of file From fd69fd620e2acb5cc1b7776ebdf440cf90df2e61 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Sun, 29 Jul 2018 18:06:02 +0200 Subject: [PATCH 66/82] Corrected REST API with support for smoothed speed and course of GPSFixesWithEstimationData --- .../serialization/impl/GPSFixMovingJsonSerializer.java | 10 ++++++++-- .../impl/GpsFixesWithEstimationDataJsonSerializer.java | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) 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()); From cbc3222ad2c61fdc0a4a3ab99a6eae3fcd277c46 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Sun, 29 Jul 2018 19:52:49 +0200 Subject: [PATCH 67/82] Implemented the first version of an integration test aborting a query with heavy load instructions --- .../impl/TestAbortingHeavyLoadQuery.java | 273 ++++++++++++++++++ .../datamining/impl/TestProcessorQuery.java | 10 +- ...actParallelProcessorElementProcessing.java | 10 +- ...estAbstractParallelProcessorFinishing.java | 2 +- ...elProcessorWithManySimpleInstructions.java | 2 +- .../TestDataRetrieverChainCreation.java | 22 +- .../components/TestFilteringProcessors.java | 2 +- .../TestParallelExtractionProcessor.java | 4 +- ...llelMultiDimensionalGroupingProcessor.java | 2 +- .../TestRetrieverFilterProcessorChain.java | 6 +- ...ctStoringParallelAggregationProcessor.java | 4 +- .../TestParallelAggregationProcessors.java | 10 +- .../TestParallelAveragingProcessors.java | 2 +- ...elGroupedDataCollectingAsSetProcessor.java | 2 +- ...TestFunctionManagerAsFunctionRegistry.java | 2 +- .../test/util/ComponentTestsUtil.java | 2 +- .../test/util/ConcurrencyTestsUtil.java | 2 +- .../sse/datamining/test/util/TestsUtil.java | 7 +- .../StatefulBlockingInstruction.java | 38 ++- 19 files changed, 350 insertions(+), 52 deletions(-) create mode 100644 java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestAbortingHeavyLoadQuery.java 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..27db549e027 --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestAbortingHeavyLoadQuery.java @@ -0,0 +1,273 @@ +package com.sap.sse.datamining.impl; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +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.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; + +public class TestAbortingHeavyLoadQuery { + + private static final int ExecutorPoolSize = Math.min(3, Runtime.getRuntime().availableProcessors() - 2); + private static final int DataSourceSize = 2000; + private static final String GroupKeyPrefix = "G"; + private static final long HeavyLoadInstructionDuration = 500; + private static final long AbortQueryDelay = (long) (HeavyLoadInstructionDuration * 1.1); + + private static final boolean RecordExecution = true; + private ConcurrentLinkedQueue executionRecord; + + private ExecutorService executor; + private Query> query; + private Collection> processors; + + @Test + public void testAbortingHeavyLoadQuery() throws InterruptedException, ExecutionException { + 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); + + Thread.sleep(AbortQueryDelay); + logExecution("Aborting query"); + query.abort(); + QueryResult> result = queryTask.get(); + 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()); + } + + // TODO Ensure that no instructions are scheduled after the query has been aborted + // TODO More detailed assessment of the leftover instructions + + executor.shutdown(); + boolean terminated = executor.awaitTermination((long) (HeavyLoadInstructionDuration * 1.5), TimeUnit.MILLISECONDS); + + if (RecordExecution) { + for (String string : executionRecord) { + System.out.println(string); + } + } + assertTrue("The executor didn't terminate in the given time", terminated); + } + + private void logExecution(String message) { + if (RecordExecution) { + executionRecord.add(Thread.currentThread().getName() + ": " + message); + } + } + + @Before + @SuppressWarnings("unchecked") + public void initialize() { + executor = new DataMiningExecutorService(ExecutorPoolSize); +// executor = new ThreadPoolExecutor(ExecutorPoolSize, ExecutorPoolSize, 0, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>()); +// executor = new ThreadPoolExecutor(ExecutorPoolSize, ExecutorPoolSize, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + if (RecordExecution) { + executionRecord = new ConcurrentLinkedQueue<>(); + } + processors = new ArrayList<>(); + + 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 void storeElement(GroupedDataEntry element) { + logExecution("Storing " + element); + super.storeElement(element); + } + }; + + Processor> grouper = new AbstractParallelProcessor>(Element.class, groupedType, executor, Collections.singleton(aggregator)) { + @Override + protected ProcessorInstruction> createInstruction(Element element) { + return new AbstractProcessorInstruction>(this, ProcessorInstructionPriority.Grouping) { + @Override + protected GroupedDataEntry computeResult() throws Exception { + logExecution("Grouping " + element); + return new GroupedDataEntry<>(new GenericGroupKey<>(element.getName()), element.getValue()); + } + }; + } + @Override + protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { } + }; + + Processor heavyLoadProcessor = new AbstractParallelProcessor(Element.class, Element.class, executor, Collections.singleton(grouper)) { + @Override + protected ProcessorInstruction createInstruction(Element element) { + ProcessorInstruction instruction = new RecordingBlockingInstruction(this, + ProcessorInstructionPriority.Extraction, HeavyLoadInstructionDuration, element, + TestAbortingHeavyLoadQuery.this::logExecution); + return instruction; + } + @Override + protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { } + + }; + + Processor retriever1 = new AbstractRetrievalProcessor(String.class, Element.class, executor, Collections.singleton(heavyLoadProcessor), 1) { + @Override + protected Iterable retrieveData(String element) { + logExecution("Retrieving " + ExecutorPoolSize + " elements from " + element); + Collection data = new ArrayList<>(); + for (int i = 0; i < ExecutorPoolSize; i++) { + data.add(new Element(element, i)); + } + return data; + } + + }; + Processor, String> retriever0 = new AbstractRetrievalProcessor, String>(dataSourceType, String.class, executor, Collections.singleton(retriever1), 0) { + @Override + protected Iterable retrieveData(Iterable element) { + logExecution("Retrieving data from data source"); + return element; + } + }; + + processors.add(retriever0); + processors.add(retriever1); + processors.add(heavyLoadProcessor); + processors.add(grouper); + processors.add(aggregator); + + return retriever0; + } + }; + } + + private static class RecordingBlockingInstruction extends StatefulBlockingInstruction { + + private final Consumer recorder; + + public RecordingBlockingInstruction(ProcessorInstructionHandler handler, + ProcessorInstructionPriority priority, long blockDuration, Element result, Consumer recorder) { + super(handler, priority, blockDuration, 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 actionAfterBlock() { + recorder.accept("Finished heavy load instruction for " + result); + } + + } + + 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 index 55ff4525adf..a1d34b379d6 100644 --- 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 @@ -23,7 +23,7 @@ public class TestAbstractParallelProcessorElementProcessing { private ThreadPoolExecutor executor; private Processor processor; - private List createdInstructions; + private List> createdInstructions; @Before public void initialize() { @@ -33,7 +33,7 @@ public class TestAbstractParallelProcessorElementProcessing { processor = new AbstractParallelProcessor(Integer.class, Object.class, executor, Collections.emptySet()) { @Override protected ProcessorInstruction createInstruction(Integer sleepTime) { - StatefulBlockingInstruction instruction = new StatefulBlockingInstruction(this, sleepTime); + StatefulBlockingInstruction instruction = new StatefulBlockingInstruction<>(this, sleepTime); createdInstructions.add(instruction); return instruction; } @@ -52,7 +52,7 @@ public class TestAbstractParallelProcessorElementProcessing { assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); executor.shutdown(); assertThat("Executor couldn't terminate", executor.awaitTermination(1, TimeUnit.SECONDS), is(true)); - for (StatefulBlockingInstruction instruction : createdInstructions) { + for (StatefulBlockingInstruction instruction : createdInstructions) { assertThat("run wasn't called", instruction.runWasCalled(), is(true)); assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); @@ -84,7 +84,7 @@ public class TestAbstractParallelProcessorElementProcessing { processor.processElement(instructionDuration); assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); - for (StatefulBlockingInstruction instruction : createdInstructions) { + for (StatefulBlockingInstruction instruction : createdInstructions) { assertThat("run wasn't called", instruction.runWasCalled(), is(true)); assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); @@ -108,7 +108,7 @@ public class TestAbstractParallelProcessorElementProcessing { executor.shutdown(); assertThat("Executor couldn't terminate", executor.awaitTermination(1, TimeUnit.SECONDS), is(true)); for (int i = 0; i < createdInstructions.size(); i++) { - StatefulBlockingInstruction instruction = createdInstructions.get(i); + StatefulBlockingInstruction instruction = createdInstructions.get(i); assertThat("run wasn't called", instruction.runWasCalled(), is(true)); // Last instructions was executed after the processor has been aborted. computeResult() should not be called if (i == createdInstructions.size() - 1) { 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 b892e3ea813..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; } 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 index 3055d8cbc7b..8ad2210288e 100644 --- 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 @@ -2,18 +2,29 @@ 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 class StatefulBlockingInstruction extends AbstractProcessorInstruction { +public class StatefulBlockingInstruction extends AbstractProcessorInstruction { - private final int duration; + protected final long blockDuration; + protected final ResultType result; private boolean runWasCalled; private boolean computeResultWasCalled; private boolean computeResultWasFinished; - public StatefulBlockingInstruction(ProcessorInstructionHandler handler, int duration) { - super(handler); - this.duration = duration; + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, long blockDuration) { + this(handler, 0, blockDuration, null); + } + + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, ProcessorInstructionPriority priority, long blockDuration, ResultType result) { + this(handler, priority.asIntValue(), blockDuration, result); + } + + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, int priority, long blockDuration, ResultType result) { + super(handler, priority); + this.blockDuration = blockDuration; + this.result = result; } @Override @@ -23,13 +34,22 @@ public class StatefulBlockingInstruction extends AbstractProcessorInstruction 0) { - Thread.sleep(duration); + actionBeforeBlock(); + if (blockDuration > 0) { + Thread.sleep(blockDuration); } + actionAfterBlock(); computeResultWasFinished = true; - return null; + return result; + } + + protected void actionBeforeBlock() { } + protected void actionAfterBlock() { } + + public long getBlockDuration() { + return blockDuration; } public boolean runWasCalled() { From bac4271a76e409b1eaf063d81a6f768487f0ef2c Mon Sep 17 00:00:00 2001 From: Steffen Schaefer Date: Mon, 30 Jul 2018 08:54:00 +0200 Subject: [PATCH 68/82] Temporarily ignored non deterministic test cases (these will be active again on bug4339) --- .../com/sap/sailing/selenium/test/raceboard/SettingsTest.java | 4 ++++ 1 file changed, 4 insertions(+) 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(); From ab381ac72a4cc43de6b91ef34a266c5ca70a7fbc Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 30 Jul 2018 17:22:17 +0200 Subject: [PATCH 69/82] Finished the integration test to check the behavior when a query with heavy load instructions is aborted --- .../impl/TestAbortingHeavyLoadQuery.java | 281 ++++++++++++++++-- .../StatefulBlockingInstruction.java | 29 +- .../StatefulProcessorInstruction.java | 54 ++++ .../ProcessorInstructionHandler.java | 2 +- .../components/AbstractParallelProcessor.java | 11 +- .../AbstractProcessorInstruction.java | 6 +- .../AbstractParallelAggregationProcessor.java | 3 +- 7 files changed, 327 insertions(+), 59 deletions(-) create mode 100644 java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulProcessorInstruction.java 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 index 27db549e027..2aa3e6ada0e 100644 --- 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 @@ -1,14 +1,20 @@ 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; @@ -17,6 +23,7 @@ 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; @@ -37,25 +44,63 @@ 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 and the time to wait before the query is aborted. 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 { - private static final int ExecutorPoolSize = Math.min(3, Runtime.getRuntime().availableProcessors() - 2); + // 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 #HeavyLoadInstructionDuration}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 heavy load instruction blocks the executing thread using {@link Thread#sleep(long)}. */ private static final long HeavyLoadInstructionDuration = 500; - private static final long AbortQueryDelay = (long) (HeavyLoadInstructionDuration * 1.1); + /** The number of milliseconds to wait before {@link Query#abort()} is called. */ + private static final long AbortQueryDelay = (long) (HeavyLoadInstructionDuration * 5.45); + //------------------------------------------------------------------------------------------------------------ + // 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 { - RunnableFuture>> queryTask = new FutureTask<>(() -> { + // Executing query in separate thread + RunnableFuture>> queryTask = new FutureTask<>(() -> { logExecution("Starting query execution"); long start = System.currentTimeMillis(); QueryResult> result = query.run(); @@ -69,46 +114,133 @@ public class TestAbortingHeavyLoadQuery { 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()); } - // TODO Ensure that no instructions are scheduled after the query has been aborted - // TODO More detailed assessment of the leftover instructions - + // Execution of unfinished instructions executor.shutdown(); boolean terminated = executor.awaitTermination((long) (HeavyLoadInstructionDuration * 1.5), TimeUnit.MILLISECONDS); - - if (RecordExecution) { - for (String string : executionRecord) { - System.out.println(string); - } - } + 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()); + } + 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) { - executionRecord.add(Thread.currentThread().getName() + ": " + message); + 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 #HeavyLoadInstructionDuration}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() { - executor = new DataMiningExecutorService(ExecutorPoolSize); -// executor = new ThreadPoolExecutor(ExecutorPoolSize, ExecutorPoolSize, 0, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>()); -// executor = new ThreadPoolExecutor(ExecutorPoolSize, ExecutorPoolSize, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); 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; @@ -118,29 +250,51 @@ public class TestAbortingHeavyLoadQuery { 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) { - return new AbstractProcessorInstruction>(this, ProcessorInstructionPriority.Grouping) { + StatefulProcessorInstruction> instruction = new StatefulProcessorInstruction>(this, ProcessorInstructionPriority.Grouping) { @Override - protected GroupedDataEntry computeResult() throws Exception { + 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) { } }; @@ -148,20 +302,35 @@ public class TestAbortingHeavyLoadQuery { Processor heavyLoadProcessor = new AbstractParallelProcessor(Element.class, Element.class, executor, Collections.singleton(grouper)) { @Override protected ProcessorInstruction createInstruction(Element element) { - ProcessorInstruction instruction = new RecordingBlockingInstruction(this, + StatefulProcessorInstruction instruction = new HeavyLoadInstruction(this, ProcessorInstructionPriority.Extraction, HeavyLoadInstructionDuration, 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) { - logExecution("Retrieving " + ExecutorPoolSize + " elements from " + element); + logExecution("Retrieving " + ExecutorPoolSize + " elements for " + element); Collection data = new ArrayList<>(); for (int i = 0; i < ExecutorPoolSize; i++) { data.add(new Element(element, i)); @@ -169,13 +338,44 @@ public class TestAbortingHeavyLoadQuery { 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); @@ -189,11 +389,11 @@ public class TestAbortingHeavyLoadQuery { }; } - private static class RecordingBlockingInstruction extends StatefulBlockingInstruction { + private static class HeavyLoadInstruction extends StatefulBlockingInstruction { private final Consumer recorder; - public RecordingBlockingInstruction(ProcessorInstructionHandler handler, + public HeavyLoadInstruction(ProcessorInstructionHandler handler, ProcessorInstructionPriority priority, long blockDuration, Element result, Consumer recorder) { super(handler, priority, blockDuration, result); this.recorder = recorder; @@ -217,6 +417,37 @@ public class TestAbortingHeavyLoadQuery { } + 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; 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 index 8ad2210288e..0efe183c595 100644 --- 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 @@ -1,17 +1,12 @@ 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 class StatefulBlockingInstruction extends AbstractProcessorInstruction { +public class StatefulBlockingInstruction extends StatefulProcessorInstruction { protected final long blockDuration; protected final ResultType result; - - private boolean runWasCalled; - private boolean computeResultWasCalled; - private boolean computeResultWasFinished; public StatefulBlockingInstruction(ProcessorInstructionHandler handler, long blockDuration) { this(handler, 0, blockDuration, null); @@ -26,22 +21,14 @@ public class StatefulBlockingInstruction extends AbstractProcessorIn this.blockDuration = blockDuration; this.result = result; } - - @Override - public void run() { - runWasCalled = true; - super.run(); - } @Override - protected ResultType computeResult() throws Exception { - computeResultWasCalled = true; + protected ResultType internalComputeResult() throws Exception { actionBeforeBlock(); if (blockDuration > 0) { Thread.sleep(blockDuration); } actionAfterBlock(); - computeResultWasFinished = true; return result; } @@ -51,17 +38,5 @@ public class StatefulBlockingInstruction extends AbstractProcessorIn public long getBlockDuration() { return blockDuration; } - - public boolean runWasCalled() { - return runWasCalled; - } - - public boolean computeResultWasCalled() { - return computeResultWasCalled; - } - - public boolean computeResultWasFinished() { - return computeResultWasFinished; - } } \ 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/src/com/sap/sse/datamining/components/ProcessorInstructionHandler.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/components/ProcessorInstructionHandler.java index e01461324d7..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 @@ -7,6 +7,6 @@ public interface ProcessorInstructionHandler { void instructionSucceeded(ResultType result); void instructionFailed(Exception e); - void afterInstructionFinished(); + void afterInstructionFinished(ProcessorInstruction instruction); } 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 ff404891ac0..a785e6481de 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 @@ -59,12 +59,16 @@ public abstract class AbstractProcessorInstruction implements Proces } catch (Exception e) { handler.instructionFailed(e); } finally { - handler.afterInstructionFinished(); + handler.afterInstructionFinished(this); } } 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/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 From 622944ae5d9ca85d86a487ecd111d12e60871e73 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 30 Jul 2018 17:25:18 +0200 Subject: [PATCH 70/82] Bug 4685: bypassed issue regarding panning to West Sahara by refreshing the location on the map after google maps initialization is complete. --- .../com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 1 + 1 file changed, 1 insertion(+) 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..4a8b418348d 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 @@ -674,6 +674,7 @@ public class RaceMap extends AbstractCompositeComponent impleme map.panTo(autoZoomLatLngBounds.getCenter()); autoZoomOut = false; } + redraw(); } }); map.addBoundsChangeHandler(new BoundsChangeMapHandler() { From b4b663719c89245acb6ac3a36d3f5fe5d26b2829 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 30 Jul 2018 17:35:01 +0200 Subject: [PATCH 71/82] Forgot to deactivate the execution recording of TestAbortingHeavyLoadQuery --- .../com/sap/sse/datamining/impl/TestAbortingHeavyLoadQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 2aa3e6ada0e..e9a9c0dff83 100644 --- 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 @@ -85,7 +85,7 @@ public class TestAbortingHeavyLoadQuery { // 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 boolean RecordExecution = false; private static final SimpleDateFormat DateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); private ConcurrentLinkedQueue executionRecord; //------------------------------------------------------------------------------------------------------------ From fd1f79e80226907d269d66974e7fce65b8276fa3 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 30 Jul 2018 18:02:34 +0200 Subject: [PATCH 72/82] Made the termination timeout of TestAbortingHeavyLoadQuery configurable --- .../impl/TestAbortingHeavyLoadQuery.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 index e9a9c0dff83..30b82bb2d24 100644 --- 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 @@ -54,12 +54,13 @@ import com.sap.sse.datamining.test.util.components.StatefulProcessorInstruction; * 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 and the time to wait before the query is aborted. Note that - * the test can fail for some configurations, due to race conditions that would be very hard to check for.
    + * 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.
    + * 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. * @@ -81,6 +82,8 @@ public class TestAbortingHeavyLoadQuery { private static final long HeavyLoadInstructionDuration = 500; /** The number of milliseconds to wait before {@link Query#abort()} is called. */ private static final long AbortQueryDelay = (long) (HeavyLoadInstructionDuration * 5.45); + /** The time given to the executor to complete all unfinished instructions */ + private static final long TerminationTimeout = (long) (HeavyLoadInstructionDuration * 1.5); //------------------------------------------------------------------------------------------------------------ // Execution Recording Configuration ------------------------------------------------------------------------- @@ -150,7 +153,7 @@ public class TestAbortingHeavyLoadQuery { // Execution of unfinished instructions executor.shutdown(); - boolean terminated = executor.awaitTermination((long) (HeavyLoadInstructionDuration * 1.5), TimeUnit.MILLISECONDS); + boolean terminated = executor.awaitTermination(TerminationTimeout, TimeUnit.MILLISECONDS); printExecutionRecord(); assertTrue("The executor didn't terminate in the given time", terminated); @@ -330,9 +333,10 @@ public class TestAbortingHeavyLoadQuery { @Override protected Iterable retrieveData(String element) { - logExecution("Retrieving " + ExecutorPoolSize + " elements for " + element); + int count = ExecutorPoolSize; + logExecution("Retrieving " + count + " elements for " + element); Collection data = new ArrayList<>(); - for (int i = 0; i < ExecutorPoolSize; i++) { + for (int i = 0; i < count; i++) { data.add(new Element(element, i)); } return data; From a197c01924908f620aa771e89c77eb5779f287b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 31 Jul 2018 10:19:51 +0200 Subject: [PATCH 73/82] bug4685 fixed formating of adjacent code --- .../gwt/ui/client/shared/racemap/RaceMap.java | 220 +++++++++--------- 1 file changed, 113 insertions(+), 107 deletions(-) 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 4a8b418348d..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,117 +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.setControls(ControlPosition.LEFT_TOP, topLeftControlsWrapperPanel); + adjustLeftControlsIndent(); + + 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); - } + } + }); + 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 (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); - } + // 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); } /** From 6b94358542b5f54d9210f47ec98eb44427a826c9 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Tue, 31 Jul 2018 13:07:53 +0200 Subject: [PATCH 74/82] Adjusted the tests for instructions that can abort their execution --- .../impl/TestAbortingHeavyLoadQuery.java | 40 +++++--- ...actParallelProcessorElementProcessing.java | 96 ++++++++++++------- .../StatefulBlockingInstruction.java | 44 ++++++--- .../AbstractProcessorInstruction.java | 6 +- 4 files changed, 125 insertions(+), 61 deletions(-) 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 index 30b82bb2d24..3e6a5494596 100644 --- 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 @@ -71,24 +71,31 @@ 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 #HeavyLoadInstructionDuration}ms, + * 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 heavy load instruction blocks the executing thread using {@link Thread#sleep(long)}. */ - private static final long HeavyLoadInstructionDuration = 500; + + /** 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) (HeavyLoadInstructionDuration * 5.45); + 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 = (long) (HeavyLoadInstructionDuration * 1.5); + 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 = false; + private static final boolean RecordExecution = true; private static final SimpleDateFormat DateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); private ConcurrentLinkedQueue executionRecord; //------------------------------------------------------------------------------------------------------------ @@ -174,6 +181,10 @@ public class TestAbortingHeavyLoadQuery { 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()); @@ -216,7 +227,7 @@ public class TestAbortingHeavyLoadQuery { * *
  • * A heavy load instruction for each element is scheduled, which blocks the running thread for - * {@value #HeavyLoadInstructionDuration}ms. + * {@value #HeavyLoadInstructionTotalDuration}ms. *
  • *
  • * Each element is grouped by its name and its value is used as value for the {@link GroupedDataEntry}. @@ -306,8 +317,8 @@ public class TestAbortingHeavyLoadQuery { @Override protected ProcessorInstruction createInstruction(Element element) { StatefulProcessorInstruction instruction = new HeavyLoadInstruction(this, - ProcessorInstructionPriority.Extraction, HeavyLoadInstructionDuration, element, - TestAbortingHeavyLoadQuery.this::logExecution); + ProcessorInstructionPriority.Extraction, HeavyLoadInstructionStepDuration, + HeavyLoadInstructionNumberOfSteps, element, TestAbortingHeavyLoadQuery.this::logExecution); unfinishedInstructions.add(instruction); return instruction; } @@ -397,9 +408,9 @@ public class TestAbortingHeavyLoadQuery { private final Consumer recorder; - public HeavyLoadInstruction(ProcessorInstructionHandler handler, - ProcessorInstructionPriority priority, long blockDuration, Element result, Consumer recorder) { - super(handler, priority, blockDuration, result); + public HeavyLoadInstruction(ProcessorInstructionHandler handler, ProcessorInstructionPriority priority, + long stepDuration, int numberOfSteps, Element result, Consumer recorder) { + super(handler, priority, stepDuration, numberOfSteps, result); this.recorder = recorder; } @@ -414,6 +425,11 @@ public class TestAbortingHeavyLoadQuery { 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); 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 index a1d34b379d6..c2132833e8c 100644 --- 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 @@ -1,7 +1,9 @@ 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; @@ -13,6 +15,7 @@ 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; @@ -22,7 +25,7 @@ import com.sap.sse.datamining.test.util.components.StatefulBlockingInstruction; public class TestAbstractParallelProcessorElementProcessing { private ThreadPoolExecutor executor; - private Processor processor; + private Processor, Object> processor; private List> createdInstructions; @Before @@ -30,10 +33,13 @@ public class TestAbstractParallelProcessorElementProcessing { int corePoolSize = Runtime.getRuntime().availableProcessors(); executor = new ThreadPoolExecutor(corePoolSize, corePoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); createdInstructions = new ArrayList<>(); - processor = new AbstractParallelProcessor(Integer.class, Object.class, executor, Collections.emptySet()) { + + @SuppressWarnings("unchecked") + Class> inputType = (Class>)(Class) Pair.class; + processor = new AbstractParallelProcessor, Object>(inputType, Object.class, executor, Collections.emptySet()) { @Override - protected ProcessorInstruction createInstruction(Integer sleepTime) { - StatefulBlockingInstruction instruction = new StatefulBlockingInstruction<>(this, sleepTime); + protected ProcessorInstruction createInstruction(Pair sleepTime) { + StatefulBlockingInstruction instruction = new StatefulBlockingInstruction<>(this, sleepTime.getA(), sleepTime.getB()); createdInstructions.add(instruction); return instruction; } @@ -44,79 +50,101 @@ public class TestAbstractParallelProcessorElementProcessing { @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(10); + 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(); - assertThat("Executor couldn't terminate", executor.awaitTermination(1, TimeUnit.SECONDS), is(true)); + assertTrue("Executor couldn't terminate", executor.awaitTermination((long) (executionTime * 1.2), TimeUnit.SECONDS)); for (StatefulBlockingInstruction instruction : createdInstructions) { - assertThat("run wasn't called", instruction.runWasCalled(), is(true)); - assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); - assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); + 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 { - int instructionDuration = 50; + 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(instructionDuration); + processor.processElement(input); } assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); - Thread.sleep(instructionDuration / 3); // Giving some time to ensure execution of unqueued instructions + Thread.sleep(stepDuration); // Giving some time to ensure execution of unqueued instructions Thread finishingThread = ConcurrencyTestsUtil.tryToFinishTheProcessorInAnotherThread(processor); do { Thread.sleep(1); } while (!finishingThread.isAlive()); - assertThat("Processor is already finished", processor.isFinished(), is(false)); + assertFalse("Processor is already finished", processor.isFinished()); // Processor not yet finished. New elements will be accepted - processor.processElement(instructionDuration); + processor.processElement(input); elementCount++; assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); - finishingThread.join(1000); - assertThat("Processor isn't finished", processor.isFinished(), is(true)); - processor.processElement(instructionDuration); + 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) { - assertThat("run wasn't called", instruction.runWasCalled(), is(true)); - assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); - assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); + 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 { - int instructionDuration = 50; - int elementCount = executor.getMaximumPoolSize() + 1; // Last instruction will be queued + 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(instructionDuration); + processor.processElement(input); } assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); - - Thread.sleep(instructionDuration / 3); // Giving some time to ensure execution of unqueued instructions + + long instructionDuration = stepDuration * numberOfSteps; + Thread.sleep(instructionDuration + stepDuration); // Time to finish first set and start second set processor.abort(); - processor.processElement(0); + processor.processElement(new Pair<>(0L, 0)); assertThat("Unexpected amount of created instructions", createdInstructions.size(), is(elementCount)); executor.shutdown(); - assertThat("Executor couldn't terminate", executor.awaitTermination(1, TimeUnit.SECONDS), is(true)); + assertTrue("Executor couldn't terminate", executor.awaitTermination(2 * stepDuration, TimeUnit.MILLISECONDS)); for (int i = 0; i < createdInstructions.size(); i++) { StatefulBlockingInstruction instruction = createdInstructions.get(i); - assertThat("run wasn't called", instruction.runWasCalled(), is(true)); - // Last instructions was executed after the processor has been aborted. computeResult() should not be called - if (i == createdInstructions.size() - 1) { - assertThat("computeResult of last instruction was called", instruction.computeResultWasCalled(), is(false)); - assertThat("computeResult of last instruction finished", instruction.computeResultWasFinished(), is(false)); + // 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 { - assertThat("computeResult wasn't called", instruction.computeResultWasCalled(), is(true)); - assertThat("computeResult didn't finish", instruction.computeResultWasFinished(), is(true)); + // 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/test/util/components/StatefulBlockingInstruction.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/components/StatefulBlockingInstruction.java index 0efe183c595..bdac4dce0e4 100644 --- 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 @@ -5,38 +5,54 @@ import com.sap.sse.datamining.impl.components.ProcessorInstructionPriority; public class StatefulBlockingInstruction extends StatefulProcessorInstruction { - protected final long blockDuration; + protected final long stepDuration; + protected final int numberOfSteps; protected final ResultType result; + + private boolean computeResultWasAborted; - public StatefulBlockingInstruction(ProcessorInstructionHandler handler, long blockDuration) { - this(handler, 0, blockDuration, null); + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, long stepDuration, int numberOfSteps) { + this(handler, 0, stepDuration, numberOfSteps, null); } - public StatefulBlockingInstruction(ProcessorInstructionHandler handler, ProcessorInstructionPriority priority, long blockDuration, ResultType result) { - this(handler, priority.asIntValue(), blockDuration, result); + 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 blockDuration, ResultType result) { + public StatefulBlockingInstruction(ProcessorInstructionHandler handler, int priority, long stepDuration, int numberOfSteps, ResultType result) { super(handler, priority); - this.blockDuration = blockDuration; + this.stepDuration = stepDuration; + this.numberOfSteps = numberOfSteps; this.result = result; } @Override protected ResultType internalComputeResult() throws Exception { - actionBeforeBlock(); - if (blockDuration > 0) { - Thread.sleep(blockDuration); + if (getTotalBlockDuration() > 0) { + actionBeforeBlock(); + for (int i = 0; i < numberOfSteps; i++) { + if (isAborted()) { + actionBeforeAbort(); + computeResultWasAborted = true; + break; + } + Thread.sleep(stepDuration); + } + actionAfterBlock(); } - actionAfterBlock(); return result; } - + protected void actionBeforeBlock() { } + protected void actionBeforeAbort() { } protected void actionAfterBlock() { } - public long getBlockDuration() { - return blockDuration; + 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/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 a785e6481de..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,7 +52,7 @@ public abstract class AbstractProcessorInstruction implements Proces @Override public void run() { try { - if (!handler.isAborted()) { + if (!isAborted()) { ResultType result = computeResult(); handler.instructionSucceeded(result); } @@ -62,6 +62,10 @@ public abstract class AbstractProcessorInstruction implements Proces handler.afterInstructionFinished(this); } } + + protected boolean isAborted() { + return handler.isAborted(); + } protected abstract ResultType computeResult() throws Exception; From b3d700110a4325c8aa7de840eb9ce06dece84427 Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Tue, 31 Jul 2018 15:37:53 +0200 Subject: [PATCH 75/82] bug4436 Added underline color bar to maneuver table competitor column --- .../racemap/maneuver/ManeuverTableData.java | 8 ++++- .../racemap/maneuver/ManeuverTablePanel.java | 36 ++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java index 564f80b57c9..b4506e6d70a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableData.java @@ -12,6 +12,7 @@ import com.sap.sailing.gwt.ui.shared.ManeuverDTO; class ManeuverTableData { private final String competitorName; + private final String competitorColor; private final Date timePoint; private final Date timePointBefore; private final ManeuverType maneuverType; @@ -24,8 +25,9 @@ class ManeuverTableData { private final double directionChange; private final boolean markPassing; - ManeuverTableData(final CompetitorDTO competitor, final ManeuverDTO maneuver) { + ManeuverTableData(final CompetitorDTO competitor, final String competitorColor, final ManeuverDTO maneuver) { this.competitorName = competitor.getName(); + this.competitorColor = competitorColor; this.timePoint = maneuver.getTimePoint(); this.timePointBefore = maneuver.getTimePointBefore(); this.maneuverType = maneuver.getType(); @@ -43,6 +45,10 @@ class ManeuverTableData { return competitorName; } + public String getCompetitorColor() { + return competitorColor; + } + public Date getTimePoint() { return timePoint; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java index 5b588fb674f..dc90b3643be 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java @@ -38,8 +38,8 @@ import com.sap.sailing.domain.common.security.Permission; import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.actions.GetManeuversForCompetitorsAction; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter; +import com.sap.sailing.gwt.ui.client.RaceCompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; @@ -47,6 +47,7 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel.LeaderBoardStyle; import com.sap.sailing.gwt.ui.leaderboard.SortedCellTableWithStylableHeaders; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sse.common.Color; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util; import com.sap.sse.common.filter.Filter; @@ -75,7 +76,8 @@ public class ManeuverTablePanel extends AbstractCompositeComponent parent, ComponentContext context, final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, final RegattaAndRaceIdentifier raceIdentifier, final StringMessages stringMessages, - final CompetitorSelectionProvider competitorSelectionModel, final ErrorReporter errorReporter, + final RaceCompetitorSelectionProvider competitorSelectionModel, final ErrorReporter errorReporter, final Timer timer, final ManeuverTableSettings initialSettings, final TimeRangeWithZoomModel timeRangeWithZoomProvider, final LeaderBoardStyle style, final UserService userService) { @@ -106,10 +108,11 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createCompetitorColumn() { + private SortableColumn createCompetitorColumn() { InvertibleComparator comparator = new InvertibleComparatorAdapter() { @Override public int compare(ManeuverTableData o1, ManeuverTableData o2) { return o1.getCompetitorName().compareTo(o2.getCompetitorName()); } }; - return new SortableColumn(new TextCell(), SortingOrder.ASCENDING) { + return new SortableColumn(new AbstractCell() { + @Override + public void render(Context context, ManeuverTableData data, SafeHtmlBuilder sb) { + final String color = data.getCompetitorColor(); + final String divStyle = color == null ? "border: none;" : "border-bottom: 2px solid " + color + ";"; + sb.appendHtmlConstant("
    "); + sb.appendEscaped(data.getCompetitorName()); + sb.appendHtmlConstant("
    "); + } + }, SortingOrder.ASCENDING) { @Override public InvertibleComparator getComparator() { return comparator; @@ -282,8 +294,8 @@ public class ManeuverTablePanel extends AbstractCompositeComponent> entry : cachedData.entrySet()) { for (ManeuverDTO maneuver : entry.getValue()) { if (settings.getSelectedManeuverTypes().contains(maneuver.getType())) { - data.add(new ManeuverTableData(entry.getKey(), maneuver)); + final Color competitorColor = competitorSelectionModel.getColor(entry.getKey(), raceIdentifier); + data.add(new ManeuverTableData(entry.getKey(), competitorColor.getAsHtml(), maneuver)); } } } @@ -429,15 +442,12 @@ public class ManeuverTablePanel extends AbstractCompositeComponent { private final AsyncActionsExecutor asyncActionsExecutor; - private final RegattaAndRaceIdentifier raceIdentifier; private final SailingServiceAsync sailingService; private CachedManeuverTableDataProvider(final TimeRangeProvider timeRangeProvider, final Timer timer, - final RegattaAndRaceIdentifier raceIdentifier, final SailingServiceAsync sailingService, - final AsyncActionsExecutor asyncActionsExecutor) { + final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor) { super(timeRangeProvider, timer, m -> m.getTimePoint(), LOADING_OFFSET_TO_NEXT_MANEUVER_PROVIDER, true); this.asyncActionsExecutor = asyncActionsExecutor; - this.raceIdentifier = raceIdentifier; this.sailingService = sailingService; } From 5aacf8b904a045913f72cfb48c92fc5299131ed2 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Tue, 31 Jul 2018 16:12:36 +0200 Subject: [PATCH 76/82] Made processors more efficient, if a query is aborted --- .../BravoFixRetrievalProcessor.java | 19 +++++++++++-------- .../BravoFixTrackRetrievalProcessor.java | 8 ++------ ...OfRaceInLeaderboardRetrievalProcessor.java | 6 ++++++ ...eWithEstimationDataRetrievalProcessor.java | 14 +++++++++++++- .../FoilingSegmentRetrievalProcessor.java | 3 +++ .../components/GPSFixRetrievalProcessor.java | 3 +++ .../LeaderboardGroupRetrievalProcessor.java | 19 ++++++++++++------- .../LeaderboardRetrievalProcessor.java | 3 +++ .../ManeuverRetrievalProcessor.java | 4 ++++ ...aneuverSpeedDetailsRetrievalProcessor.java | 7 +++++++ .../MarkPassingRetrievalProcessor.java | 3 +++ .../RaceOfCompetitorRetrievalProcessor.java | 3 +++ ...ctParallelAverageAggregationProcessor.java | 3 +++ ...stractParallelSumAggregationProcessor.java | 4 ++-- ...ingAverageDegreesAggregationProcessor.java | 3 +++ ...lelDistanceMedianAggregationProcessor.java | 3 +++ ...kendPolarsBoatClassRetrievalProcessor.java | 16 +++++++++++----- .../PolarCompetitorRetrievalProcessor.java | 3 +++ .../PolarFleetRetrievalProcessor.java | 3 +++ .../PolarGPSFixRetrievalProcessor.java | 3 +++ ...larLeaderboardGroupRetrievalProcessor.java | 17 +++++++++++------ .../PolarLeaderboardRetrievalProcessor.java | 3 +++ .../PolarLegRetrievalProcessor.java | 3 +++ .../PolarRaceColumnRetrievalProcessor.java | 3 +++ .../PolarBackendDataAggregationProcessor.java | 10 ++++++++++ ...tiDimensionalNestingGroupingProcessor.java | 3 +++ .../AbstractRetrievalProcessor.java | 3 +++ ...NumberDataAverageAggregationProcessor.java | 3 +++ ...dNumberDataMedianAggregationProcessor.java | 3 +++ ...NumberPairAverageAggregationProcessor.java | 3 +++ ...lGroupedNumberPairCollectingProcessor.java | 3 +++ 31 files changed, 149 insertions(+), 35 deletions(-) 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 d1557a55763..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,7 +24,13 @@ 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.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 ad335b82fd8..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 @@ -49,14 +49,26 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( new ManeuverDetectorImpl(trackedRace, competitor), 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 retrieveData(RacingEventService element) { - final PolarDataService polarDataService = element.getPolarDataService(); - return element.getLeaderboardGroups() - .values() - .stream() - .map(lg -> new LeaderboardGroupWithContext(lg, polarDataService)) - .collect(Collectors.toSet()); + 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 9dd837d36b1..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 @@ -22,6 +22,9 @@ public class LeaderboardRetrievalProcessor extends AbstractRetrievalProcessor retrieveData(HasLeaderboardGroupContext element) { Collection leaderboardsWithContext = new ArrayList<>(); for (Leaderboard leaderboard : element.getLeaderboardGroup().getLeaderboards()) { + 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 1670f3a7083..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 @@ -48,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, 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/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.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.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/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/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, From 8b1b833d66549158252564ba3257c5c4acff9822 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Tue, 31 Jul 2018 16:40:30 +0200 Subject: [PATCH 77/82] Forgot to optimize some retrieval processors --- .../TrackedLegOfCompetitorRetrievalProcessor.java | 3 +++ .../impl/components/TrackedLegRetrievalProcessor.java | 3 +++ .../impl/components/TrackedRaceRetrievalProcessor.java | 6 ++++++ .../impl/components/WindFixRetrievalProcessor.java | 3 +++ .../impl/components/WindTrackRetrievalProcessor.java | 3 +++ 5 files changed, 18 insertions(+) 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)); From dab40a2755f64f19837f7d49a9a31c5187f3fdf6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 1 Aug 2018 00:35:42 +0200 Subject: [PATCH 78/82] upgraded TracAPI from 3.10.1 to 3.11.0 (more robust course/passing instruction update handling; see bug 4680) Change-Id: I2531c7d369816237bd59b6280f61d77ea229cf67 --- .../domain/test/RouteAssemblyTest.java | 6 + .../impl/RaceCourseReceiver.java | 8 + .../gwt/ui/server/SailingServiceImpl.java | 2 +- .../META-INF/MANIFEST.MF | 3 +- java/com.tractrac.clientmodule/README.txt | 34 ++- .../javadoc/allclasses-frame.html | 4 +- .../javadoc/allclasses-noframe.html | 4 +- .../tractrac/model/lib/api/IIdentifiable.html | 4 +- .../com/tractrac/model/lib/api/INamed.html | 4 +- .../tractrac/model/lib/api/ModelLocator.html | 4 +- .../api/attachment/AbstractAttachable.html | 4 +- .../model/lib/api/attachment/IAttachable.html | 4 +- .../lib/api/attachment/IAttachmentKey.html | 4 +- .../api/attachment/IAttachmentManager.html | 4 +- .../lib/api/attachment/package-frame.html | 4 +- .../lib/api/attachment/package-summary.html | 4 +- .../lib/api/attachment/package-tree.html | 4 +- .../model/lib/api/data/IControlPassing.html | 4 +- .../model/lib/api/data/IControlPassings.html | 8 +- .../model/lib/api/data/IMessageData.html | 4 +- .../model/lib/api/data/IPosition.html | 4 +- .../model/lib/api/data/IPositionFactory.html | 4 +- .../model/lib/api/data/IPositionOffset.html | 4 +- .../model/lib/api/data/IPositionSnapped.html | 4 +- .../model/lib/api/data/IStartStopData.html | 4 +- .../model/lib/api/data/ITimeData.html | 4 +- .../model/lib/api/data/package-frame.html | 4 +- .../model/lib/api/data/package-summary.html | 4 +- .../model/lib/api/data/package-tree.html | 4 +- .../lib/api/event/CreateModelException.html | 4 +- .../model/lib/api/event/DataSource.html | 4 +- .../model/lib/api/event/EventType.html | 4 +- .../model/lib/api/event/ICompetitor.html | 4 +- .../model/lib/api/event/ICompetitorClass.html | 4 +- .../tractrac/model/lib/api/event/IEvent.html | 4 +- .../model/lib/api/event/IEventFactory.html | 4 +- .../tractrac/model/lib/api/event/IRace.html | 4 +- .../model/lib/api/event/IRaceCompetitor.html | 4 +- .../model/lib/api/event/IRaceSerie.html | 4 +- .../tractrac/model/lib/api/event/ITeam.html | 4 +- .../api/event/RaceCompetitorStatusType.html | 4 +- .../lib/api/event/RaceLoadingException.html | 4 +- .../model/lib/api/event/RaceStatusType.html | 4 +- .../lib/api/event/RaceVisibilityType.html | 4 +- .../model/lib/api/event/StartTimeType.html | 4 +- .../model/lib/api/event/package-frame.html | 4 +- .../model/lib/api/event/package-summary.html | 4 +- .../model/lib/api/event/package-tree.html | 10 +- .../model/lib/api/metadata/IMetadata.html | 4 +- .../lib/api/metadata/IMetadataContainer.html | 4 +- .../lib/api/metadata/IMetadataFactory.html | 4 +- .../api/metadata/IPropertiesContainer.html | 4 +- .../model/lib/api/metadata/package-frame.html | 4 +- .../lib/api/metadata/package-summary.html | 4 +- .../model/lib/api/metadata/package-tree.html | 4 +- .../tractrac/model/lib/api/package-frame.html | 4 +- .../model/lib/api/package-summary.html | 4 +- .../tractrac/model/lib/api/package-tree.html | 4 +- .../model/lib/api/route/IControl.html | 4 +- .../model/lib/api/route/IControlPoint.html | 4 +- .../model/lib/api/route/IControlRoute.html | 4 +- .../model/lib/api/route/IPathRoute.html | 4 +- .../lib/api/route/IPathRouteFactory.html | 4 +- .../model/lib/api/route/IPathSegment.html | 4 +- .../tractrac/model/lib/api/route/IRoute.html | 4 +- .../model/lib/api/route/ISegment.html | 4 +- .../model/lib/api/route/package-frame.html | 4 +- .../model/lib/api/route/package-summary.html | 4 +- .../model/lib/api/route/package-tree.html | 4 +- .../model/lib/api/sensor/ISensorData.html | 4 +- .../model/lib/api/sensor/package-frame.html | 4 +- .../model/lib/api/sensor/package-summary.html | 4 +- .../model/lib/api/sensor/package-tree.html | 4 +- .../model/lib/api/spatial/ICoordinate.html | 4 +- .../lib/api/spatial/ICoordinateSequence.html | 4 +- .../model/lib/api/spatial/IExtent.html | 4 +- .../model/lib/api/spatial/IGeoCoordinate.html | 4 +- .../model/lib/api/spatial/package-frame.html | 4 +- .../lib/api/spatial/package-summary.html | 4 +- .../model/lib/api/spatial/package-tree.html | 4 +- .../lib/api/IEventSubscriber.html | 4 +- .../subscription/lib/api/IRaceSubscriber.html | 285 ++++++++++++------ .../subscription/lib/api/ISubscriber.html | 4 +- .../lib/api/ISubscriberFactory.html | 4 +- .../lib/api/ISubscriberListener.html | 4 +- .../SubscriberInitializationException.html | 16 +- .../lib/api/SubscriptionLocator.html | 4 +- .../ICompetitorSensorDataListener.html | 4 +- .../api/competitor/ICompetitorsListener.html | 4 +- .../lib/api/competitor/IPositionListener.html | 4 +- .../competitor/IPositionOffsetListener.html | 4 +- .../competitor/IPositionSnappedListener.html | 4 +- .../lib/api/competitor/package-frame.html | 4 +- .../lib/api/competitor/package-summary.html | 4 +- .../lib/api/competitor/package-tree.html | 4 +- .../api/control/IControlPassingsListener.html | 4 +- .../IControlPointPositionListener.html | 4 +- .../IControlPointSensorDataListener.html | 4 +- .../control/IControlRouteChangeListener.html | 35 ++- .../lib/api/control/IControlsListener.html | 4 +- .../lib/api/control/package-frame.html | 4 +- .../lib/api/control/package-summary.html | 4 +- .../lib/api/control/package-tree.html | 4 +- .../api/event/IConnectionStatusListener.html | 4 +- .../lib/api/event/IEventMessageListener.html | 4 +- .../api/event/ILiveDataEvent.StatusType.html | 4 +- .../lib/api/event/ILiveDataEvent.html | 4 +- .../subscription/lib/api/event/IMessage.html | 4 +- .../lib/api/event/IServerTimeListener.html | 4 +- .../lib/api/event/IStoredDataEvent.Type.html | 4 +- .../lib/api/event/IStoredDataEvent.html | 4 +- .../lib/api/event/package-frame.html | 4 +- .../lib/api/event/package-summary.html | 4 +- .../lib/api/event/package-tree.html | 6 +- .../subscription/lib/api/package-frame.html | 4 +- .../subscription/lib/api/package-summary.html | 4 +- .../subscription/lib/api/package-tree.html | 4 +- .../lib/api/race/IRaceCompetitorListener.html | 4 +- .../lib/api/race/IRaceMessageListener.html | 4 +- .../IRaceStartStopTimesChangeListener.html | 4 +- .../lib/api/race/IRacesListener.html | 4 +- .../race/IStartStopTimesChangeListener.html | 4 +- .../lib/api/race/package-frame.html | 4 +- .../lib/api/race/package-summary.html | 4 +- .../lib/api/race/package-tree.html | 4 +- .../lib/api/route/IRoutesListener.html | 4 +- .../lib/api/route/package-frame.html | 4 +- .../lib/api/route/package-summary.html | 4 +- .../lib/api/route/package-tree.html | 4 +- .../javadoc/constant-values.html | 4 +- .../javadoc/deprecated-list.html | 4 +- .../javadoc/help-doc.html | 4 +- .../javadoc/index-files/index-1.html | 4 +- .../javadoc/index-files/index-10.html | 4 +- .../javadoc/index-files/index-11.html | 4 +- .../javadoc/index-files/index-12.html | 4 +- .../javadoc/index-files/index-13.html | 4 +- .../javadoc/index-files/index-14.html | 4 +- .../javadoc/index-files/index-15.html | 32 +- .../javadoc/index-files/index-16.html | 4 +- .../javadoc/index-files/index-17.html | 9 +- .../javadoc/index-files/index-18.html | 4 +- .../javadoc/index-files/index-2.html | 4 +- .../javadoc/index-files/index-3.html | 4 +- .../javadoc/index-files/index-4.html | 4 +- .../javadoc/index-files/index-5.html | 4 +- .../javadoc/index-files/index-6.html | 4 +- .../javadoc/index-files/index-7.html | 12 +- .../javadoc/index-files/index-8.html | 4 +- .../javadoc/index-files/index-9.html | 4 +- .../javadoc/index.html | 2 +- .../javadoc/overview-frame.html | 4 +- .../javadoc/overview-summary.html | 4 +- .../javadoc/overview-tree.html | 14 +- .../javadoc/serialized-form.html | 4 +- .../lib/TracAPI-src.jar | Bin 70130 -> 70100 bytes .../com.tractrac.clientmodule/lib/TracAPI.jar | Bin 5256574 -> 5298468 bytes java/com.tractrac.clientmodule/pom.xml | 2 +- .../app/tracapi/AbstractListener.java | 190 ++++++++++++ .../app/tracapi/DelayListener.java | 55 ++++ .../app/tracapi/EventListener.java | 131 ++++++-- .../subscription/app/tracapi/Main.java | 69 +++-- 162 files changed, 1020 insertions(+), 465 deletions(-) create mode 100755 java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/AbstractListener.java create mode 100755 java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/DelayListener.java 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.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 00b9a06cb3f..09393c99366 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 @@ -1504,7 +1504,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; } 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 -

    June 2018

    From 2e0b87a1fe2caf85f851d84731d676426b2db6db Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 1 Aug 2018 11:34:29 +0200 Subject: [PATCH 80/82] use full date/time format for mark passing editor Change-Id: I7cd88e6d50ecc286a3e6b909ae200b8b67fb7305 --- .../gwt/ui/client/shared/charts/EditMarkPassingsPanel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 949a4d2a4a4..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,6 +11,7 @@ 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; @@ -134,7 +135,7 @@ public class EditMarkPassingsPanel extends AbstractCompositeComponent, SafeHtml>(new AnchorCell()) { @Override public SafeHtml getValue(final Pair object) { From 3f76a17195cbcd3f9b2cf3385edbbc1c64acaa40 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 1 Aug 2018 11:35:18 +0200 Subject: [PATCH 81/82] added release notes for mark passing editor change Change-Id: Id84f82a85319cab4b892de1c4f80df11066db3ca --- java/com.sap.sailing.www/release_notes_admin.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index fa0033203fd..aff77239787 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. From 51add11c476f3d7966dcf7fd14f3ba24ee39fc6b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 1 Aug 2018 18:53:59 +0200 Subject: [PATCH 82/82] bug4699: when inferring start from mark passings, ensure to keep using start of tracking for candidate time range Change-Id: Ib50adbfbf3f3e553a09795d2330f4a25d41424b5 --- .../CandidateFinder.java | 7 +++ .../MarkPassingUpdateListener.java | 19 ++++++++ .../impl/CandidateFinderImpl.java | 45 ++++++++++++++++--- 3 files changed, 64 insertions(+), 7 deletions(-) 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) {