diff --git a/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties b/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties index 33fba37eb18..aa925481168 100644 --- a/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties +++ b/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties @@ -1,6 +1,24 @@ -boatClassResultsSubject=New results for boat class {0} in regatta {1} of event {2} -boatClassResultsBody=New results for boat class {0} in regatta {1} of event {2} are available. See: {3} -boatClassUpcomingRaceSubject=A race for boat class {0} in regatta {1} of event {2} will start soon -boatClassUpcomingRaceBody=A race for boat class {0} in regatta {1} of event {2} will start at {4}. See: {3} -competitorResultsSubject=New results for competitor {0} in regatta {1} of event {2} -competitorResultsBody=New results for competitor {0} in regatta {1} of event {2} are available. See: {3} \ No newline at end of file +raceInRegattaOfEvent=race {0} in regatta {1} of event {2} +raceInFleetInRegattaOfEvent={0} ({3}) in regatta {1} of event {2} + +leaderboardOfEvent=leaderboard {0} of event {1} + +boatClassRaceFinishingSubject=Race for boat class {0} finishes - new results available +# 0: boatClassName, 1: raceDescription, 2: link +boatClassRaceFinishingBody=New results for boat class {0} in {1} are available. See: {2} + +boatClassScoreCorrectionSubject=New results are available for boat class {0} +# 0: boatClassName, 1: leaderboardDescription, 2: link +boatClassScoreCorrectionBody=New results for boat class {0} in {1} are available. See: {2} + +competitorPassesFinishSubject=Competitor {0} passed finish line in a race +# 0: competitorName, 1: raceDescription, 2: link +competitorPassesFinishBody=Competitor {0} passed finish line in {1}. See: {2} + +competitorScoreCorrectionSubject=New results are available for competitor {0} +# 0: competitorName, 1: leaderboardDescription, 2: link +boatClassScoreCorrectionBody=New results for competitor {0} in {1} are available. See: {2} + +boatClassUpcomingRaceSubject=A race for boat class {0} will start soon +# 0: boatClassName, 1: raceDescription, 2: time, 3: link +boatClassUpcomingRaceBody=A race for boat class {0} in {1} will start approximately at {2}. See: {3} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/SailingNotificationService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/SailingNotificationService.java index 6762dc3684a..2f8061df735 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/SailingNotificationService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/SailingNotificationService.java @@ -1,16 +1,42 @@ package com.sap.sailing.server.notification; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; public interface SailingNotificationService { - void notifyUserOnBoatClassResults(BoatClass boatClass, String eventName, String regattaDisplayName, String link); + /** + * Called when {@link RaceState} changes state to finishing/finished (?). + */ + void notifyUserOnBoatClassRaceChangesStateToFinishing(BoatClass boatClass, TrackedRace trackedRace, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet); + + /** + * Called on score correction for a race with BoatClass if the last score correction was more than one minute. + */ + void notifyUserOnBoatClassWhenScoreCorrectionsAreAvailable(BoatClass boatClass, Leaderboard leaderboard); + + /** + * Called whenever a start time is set for a race with {@link BoatClass} through the race's {@link RaceState}. + */ + void notifyUserOnBoatClassUpcomingRace(BoatClass boatClass, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet, TimePoint when); - void notifyUserOnBoatClassUpcomingRace(BoatClass boatClass, String eventName, String regattaDisplayName, - TimePoint when, String link); + /** + * Triggered when a competitor passes the finishing waypoint of a race. + */ + void notifyUserOnCompetitorPassesFinish(Competitor competitor, TrackedRace trackedRace, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet); - void notifyUserOnCompetitorResults(Competitor competitor, String eventName, String regattaDisplayName, String link); + /** + * Triggered when a score correction is set for competitor on any leaderboard. + */ + void notifyUserOnCompetitorScoreCorrections(Competitor competitor, Leaderboard leaderboard); } \ No newline at end of file diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/SailingNotificationServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/SailingNotificationServiceImpl.java index bee4b57d96f..0582d9f3695 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/SailingNotificationServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/SailingNotificationServiceImpl.java @@ -3,17 +3,30 @@ package com.sap.sailing.server.notification.impl; import java.util.HashSet; import java.util.Locale; import java.util.Set; +import java.util.function.BiConsumer; import org.osgi.framework.BundleContext; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.impl.RacingEventServiceImpl; import com.sap.sailing.server.impl.preferences.BoatClassResultsNotificationSet; import com.sap.sailing.server.impl.preferences.BoatClassUpcomingRaceNotificationSet; import com.sap.sailing.server.impl.preferences.CompetitorResultsNotificationSet; import com.sap.sailing.server.notification.SailingNotificationService; import com.sap.sse.common.Stoppable; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl; import com.sap.sse.mail.queue.MailQueue; @@ -22,6 +35,7 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific private final Set toStop = new HashSet<>(); private final MailQueue mailQueue; + private RacingEventService racingEventService; private final ResourceBundleStringMessagesImpl messages; private final BoatClassResultsNotificationSet boatClassResults; @@ -43,59 +57,198 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific toStop.forEach(Stoppable::stop); } - @Override - public void notifyUserOnBoatClassResults(BoatClass boatClass, String eventName, String regattaDisplayName, - String link) { - mailQueue.addNotification(new NotificationSetNotification(boatClass, boatClassResults) { - @Override - protected String constructSubject(BoatClass objectToNotifyAbout, Locale locale) { - return messages.get(locale, "boatClassResultsSubject", boatClass.getDisplayName(), regattaDisplayName, - eventName); - } + public void setRacingEventService(RacingEventServiceImpl racingEventService) { + this.racingEventService = racingEventService; + } - @Override - protected String constructBody(BoatClass objectToNotifyAbout, Locale locale) { - return messages.get(locale, "boatClassResultsBody", boatClass.getDisplayName(), regattaDisplayName, - eventName); + private Pair calculateAssociatedEventForLeaderboard(final Leaderboard leaderboard) { + Set foundEvents = new HashSet<>(); + Set foundLeaderboardGroups = new HashSet<>(); + racingEventService.getAllEvents().forEach(event -> { + event.getLeaderboardGroups().forEach(leaderboardGroup -> { + if (Util.contains(leaderboardGroup.getLeaderboards(), leaderboard)) { + foundEvents.add(event); + foundLeaderboardGroups.add(leaderboardGroup); + } + }); + }); + int foundEventsCount = foundEvents.size(); + if (foundEventsCount == 1) { + return new Pair<>(Util.get(foundEvents, 0), Util.get(foundLeaderboardGroups, 0)); + } else if (foundEventsCount > 1 && foundLeaderboardGroups.size() == 1) { + // could be a series + LeaderboardGroup leaderboardGroup = Util.get(foundLeaderboardGroups, 0); + if (leaderboardGroup.hasOverallLeaderboard()) { + CourseArea defaultCourseArea = leaderboard.getDefaultCourseArea(); + if (defaultCourseArea != null) { + for (Event event : foundEvents) { + if (Util.contains(event.getVenue().getCourseAreas(), defaultCourseArea)) { + return new Pair<>(event, leaderboardGroup); + } + } + } } + } + // No associated event found or association is ambiguous + return null; + } + + private void doWithEvent(Leaderboard leaderboard, BiConsumer consumer) { + if (racingEventService == null) { + // TODO log + return; + } + Pair eventAndLeaderboardGroup = calculateAssociatedEventForLeaderboard(leaderboard); + if (eventAndLeaderboardGroup != null) { + consumer.accept(eventAndLeaderboardGroup.getA(), eventAndLeaderboardGroup.getB()); + } + } + + private String calculateRaceDescription(Locale locale, Event event, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet) { + String raceName = raceColumn.getName(); + String eventName = event.getName(); + String leaderboardDisplayName = leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() + : leaderboard.getName(); + + if (Util.size(raceColumn.getFleets()) > 1) { + return messages.get(locale, "raceInFleetInRegattaOfEvent", raceName, leaderboardDisplayName, eventName, + fleet.getName()); + } else { + return messages.get(locale, "raceInRegattaOfEvent", raceName, leaderboardDisplayName, eventName); + + } + } + + private String calculateLeaderboardDescription(Locale locale, Event event, Leaderboard leaderboard) { + String eventName = event.getName(); + String leaderboardDisplayName = leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() + : leaderboard.getName(); + + return messages.get(locale, "leaderboardOfEvent", leaderboardDisplayName, eventName); + } + + @Override + public void notifyUserOnBoatClassRaceChangesStateToFinishing(BoatClass boatClass, TrackedRace trackedRace, + Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) { + doWithEvent(leaderboard, (event, leaderboardGroup) -> { + RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier(); + // TODO where to get the base URL? + String link = "/gwt/RaceBoard.html?eventId=" + event.getId() + "&leaderboardName=" + leaderboard.getName() + + "&leaderboardGroupName=" + leaderboardGroup.getName() + "&raceName=" + + raceIdentifier.getRaceName() + "&showMapControls=true&viewShowNavigationPanel=true®attaName=" + + raceIdentifier.getRegattaName(); + + mailQueue.addNotification(new NotificationSetNotification(boatClass, boatClassResults) { + @Override + protected String constructSubject(BoatClass objectToNotifyAbout, Locale locale) { + return messages.get(locale, "boatClassRaceFinishingSubject", boatClass.getDisplayName()); + } + + @Override + protected String constructBody(BoatClass objectToNotifyAbout, Locale locale) { + String raceDescription = calculateRaceDescription(locale, event, leaderboard, raceColumn, fleet); + return messages.get(locale, "boatClassRaceFinishingBody", boatClass.getDisplayName(), + raceDescription, link); + } + }); }); } @Override - public void notifyUserOnBoatClassUpcomingRace(BoatClass boatClass, String eventName, String regattaDisplayName, - TimePoint when, String link) { - mailQueue.addNotification(new NotificationSetNotification(boatClass, boatClassUpcomingRace) { - @Override - protected String constructSubject(BoatClass objectToNotifyAbout, Locale locale) { - return messages.get(locale, "boatClassUpcomingRaceSubject", boatClass.getDisplayName(), - regattaDisplayName, eventName); - } + public void notifyUserOnBoatClassWhenScoreCorrectionsAreAvailable(BoatClass boatClass, Leaderboard leaderboard) { + doWithEvent(leaderboard, (event, leaderboardGroup) -> { + // TODO where to get the base URL? + String link = "/gwt/Home.html#/regatta/races/:eventId=" + event.getId() + "®attaId=" + + leaderboard.getName(); - @Override - protected String constructBody(BoatClass objectToNotifyAbout, Locale locale) { - // TODO properly format time in the user's locale - String time = when.toString(); - return messages.get(locale, "boatClassUpcomingRaceBody", boatClass.getDisplayName(), regattaDisplayName, - eventName, time); - } + mailQueue.addNotification(new NotificationSetNotification(boatClass, boatClassResults) { + @Override + protected String constructSubject(BoatClass objectToNotifyAbout, Locale locale) { + return messages.get(locale, "boatClassScoreCorrectionSubject", boatClass.getDisplayName()); + } + + @Override + protected String constructBody(BoatClass objectToNotifyAbout, Locale locale) { + String leaderboardDescription = calculateLeaderboardDescription(locale, event, leaderboard); + return messages.get(locale, "boatClassScoreCorrectionBody", boatClass.getDisplayName(), + leaderboardDescription, link); + } + }); + }); + } + + public void notifyUserOnBoatClassUpcomingRace(BoatClass boatClass, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet, TimePoint when) { + doWithEvent(leaderboard, (event, leaderboardGroup) -> { + // TODO where to get the base URL? + String link = "/gwt/Home.html#/regatta/races/:eventId="+event.getId()+"®attaId=" + leaderboard.getName(); + + mailQueue.addNotification(new NotificationSetNotification(boatClass, boatClassUpcomingRace) { + @Override + protected String constructSubject(BoatClass objectToNotifyAbout, Locale locale) { + return messages.get(locale, "boatClassUpcomingRaceSubject", boatClass.getDisplayName()); + } + + @Override + protected String constructBody(BoatClass objectToNotifyAbout, Locale locale) { + String raceDescription = calculateRaceDescription(locale, event, leaderboard, raceColumn, fleet); + // TODO properly format time in the user's locale + String time = when.toString(); + return messages.get(locale, "boatClassUpcomingRaceBody", boatClass.getDisplayName(), + raceDescription, time, link); + } + }); }); } @Override - public void notifyUserOnCompetitorResults(Competitor competitor, String eventName, String regattaDisplayName, - String link) { - mailQueue.addNotification(new NotificationSetNotification(competitor, competitorResults) { - @Override - protected String constructSubject(Competitor objectToNotifyAbout, Locale locale) { - return messages.get(locale, "competitorResultsSubject", competitor.getName(), regattaDisplayName, - eventName); - } + public void notifyUserOnCompetitorPassesFinish(Competitor competitor, TrackedRace trackedRace, + Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) { + doWithEvent(leaderboard, (event, leaderboardGroup) -> { + RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier(); + // TODO where to get the base URL? + String link = "/gwt/RaceBoard.html?eventId=" + event.getId() + "&leaderboardName=" + leaderboard.getName() + + "&leaderboardGroupName=" + leaderboardGroup.getName() + "&raceName=" + + raceIdentifier.getRaceName() + "&showMapControls=true&viewShowNavigationPanel=true®attaName=" + + raceIdentifier.getRegattaName(); - @Override - protected String constructBody(Competitor objectToNotifyAbout, Locale locale) { - return messages.get(locale, "competitorResultsBody", competitor.getName(), regattaDisplayName, - eventName); - } + mailQueue.addNotification(new NotificationSetNotification(competitor, competitorResults) { + @Override + protected String constructSubject(Competitor objectToNotifyAbout, Locale locale) { + return messages.get(locale, "competitorPassesFinishSubject", competitor.getName()); + } + + @Override + protected String constructBody(Competitor objectToNotifyAbout, Locale locale) { + String raceDescription = calculateRaceDescription(locale, event, leaderboard, raceColumn, fleet); + return messages.get(locale, "competitorPassesFinishBody", competitor.getName(), raceDescription, + link); + } + }); + }); + } + + @Override + public void notifyUserOnCompetitorScoreCorrections(Competitor competitor, Leaderboard leaderboard) { + doWithEvent(leaderboard, (event, leaderboardGroup) -> { + // TODO where to get the base URL? + String link = "/gwt/Home.html#/regatta/races/:eventId=" + event.getId() + "®attaId=" + + leaderboard.getName(); + + mailQueue.addNotification(new NotificationSetNotification(competitor, competitorResults) { + @Override + protected String constructSubject(Competitor objectToNotifyAbout, Locale locale) { + return messages.get(locale, "boatClassScoreCorrectionSubject", competitor.getName()); + } + + @Override + protected String constructBody(Competitor objectToNotifyAbout, Locale locale) { + String leaderboardDescription = calculateLeaderboardDescription(locale, event, leaderboard); + return messages.get(locale, "boatClassScoreCorrectionBody", competitor.getName(), + leaderboardDescription, link); + } + }); }); } }