Changes notification mails to use html with styled link buttons

This commit is contained in:
Benjamin Barth committed 2016-07-18 16:17:44 +02:00
1 parent 133fcdaa58
commit 23615ef97f
5 files changed
+192 -88

No files matched your search

@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<title>${title}</title>
<style type="text/css">
.linkButton, .linkButton:active, .linkButton:visited {
padding: 15px;
margin: 10px;
width: 200px;
display: inline-block;
background-color: #337ab7;
border-radius: 4px;
color: #ffffff;
border: 1px solid #2e6da4;
text-decoration: none;
}
.linkButton:hover {
background-color: #2b618e;
border: 1px solid #204d74;
}
</style>
</head>
<body>
${body}
</body>
</html>
@@ -2,23 +2,29 @@ raceInRegattaOfEvent=race {0} in regatta {1} of event {2}
raceInFleetInRegattaOfEvent={0} ({3}) in regatta {1} of event {2}
leaderboardOfEvent=leaderboard {0} of event {1}
leaderboardShowResultsLinkTitle=Show results
racesOverviewLinkTitle=Show races overview
raceboardShowRaceLinkTitle=Show race
raceboardRaceAnalysisLinkTitle=Show race analysis
boatClassRaceFinishedSubject=Race for boat class {0} has finished - new results available
# 0: boatClassName, 1: raceDescription, 2: link
boatClassRaceFinishedBody=New results for boat class {0} in {1} are available. See: {2}
# 0: boatClassName, 1: raceDescription
boatClassRaceFinishedBody=New results for boat class {0} in {1} are available.
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}
# 0: boatClassName, 1: leaderboardDescription
boatClassScoreCorrectionBody=New results for boat class {0} in {1} are available.
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}
# 0: competitorName, 1: raceDescription
competitorPassesFinishBody=Competitor {0} passed finish line in {1}.
competitorScoreCorrectionSubject=New results are available for competitor {0}
# 0: competitorName, 1: leaderboardDescription, 2: link
competitorScoreCorrectionBody=New results for competitor {0} in {1} are available. See: {2}
# 0: competitorName, 1: leaderboardDescription
competitorScoreCorrectionBody=New results for competitor {0} in {1} are available.
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}
# 0: boatClassName, 1: raceDescription, 2: time
boatClassUpcomingRaceBody=A race for boat class {0} in {1} will start approximately at {2}.
@@ -0,0 +1,42 @@
package com.sap.sailing.server.notification.impl;
import java.util.Arrays;
import com.sap.sse.common.Util.Pair;
public class NotificationMailTemplate {
private final String subject;
private final String title;
private final String text;
private final Iterable<Pair<String, String>> labelsAndLinkUrls;
@SafeVarargs
public NotificationMailTemplate(String subject, String text, Pair<String, String>... labelsAndLinkUrls) {
this(subject, null, text, labelsAndLinkUrls);
}
@SafeVarargs
public NotificationMailTemplate(String subject, String title, String text, Pair<String, String>... labelsAndLinkUrls) {
this.subject = subject;
this.title = title;
this.text = text;
this.labelsAndLinkUrls = Arrays.asList(labelsAndLinkUrls);
}
public String getSubject() {
return subject;
}
public String getTitle() {
return title;
}
public String getText() {
return text;
}
public Iterable<Pair<String, String>> getLabelsAndLinkUrls() {
return labelsAndLinkUrls;
}
}
@@ -1,5 +1,9 @@
package com.sap.sailing.server.notification.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -9,12 +13,31 @@ import javax.mail.Multipart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.mail.MailService;
import com.sap.sse.mail.queue.MailNotification;
import com.sap.sse.security.PreferenceObjectBasedNotificationSet;
public abstract class NotificationSetNotification<T> implements MailNotification {
private static final Logger logger = Logger.getLogger(NotificationSetNotification.class.getName());
private static final String TEMPLATE_FILE = "notification-mail-template.html";
private static final String TEMPLATE = loadTemplateFile();
private static String loadTemplateFile() {
StringBuilder content = new StringBuilder();
try (BufferedReader in = new BufferedReader(new InputStreamReader(
NotificationSetNotification.class.getResourceAsStream(TEMPLATE_FILE), StandardCharsets.UTF_8))) {
String line;
while ((line = in.readLine()) != null) {
content.append(line).append("\n");
}
} catch (IOException exc) {
logger.log(Level.SEVERE, "Error while loading notification mail template!", exc);
}
return content.toString();
}
private final T objectToNotifyAbout;
private final PreferenceObjectBasedNotificationSet<?, T> associatedNotificationSet;
@@ -33,17 +56,30 @@ public abstract class NotificationSetNotification<T> implements MailNotification
Multipart multipart = new MimeMultipart();
BodyPart bodyPart = new MimeBodyPart();
try {
bodyPart.setContent(constructBody(objectToNotifyAbout, locale), "text/html");
NotificationMailTemplate mailTemplate = getMailTemplate(objectToNotifyAbout, locale);
bodyPart.setContent(getMailContent(mailTemplate), "text/html");
multipart.addBodyPart(bodyPart);
mailService.sendMail(user.getEmail(), constructSubject(objectToNotifyAbout, locale), multipart);
mailService.sendMail(user.getEmail(), mailTemplate.getSubject(), multipart);
} catch (Exception e) {
logger.log(Level.SEVERE,
"Could not send mail notification for \"" + objectToNotifyAbout + "\" to user \" + user + \"", e);
}
});
}
private String getMailContent(NotificationMailTemplate notificationMailTemplate) {
StringBuilder bodyContent = new StringBuilder();
if (notificationMailTemplate.getTitle() != null) {
bodyContent.append("<h1>").append(notificationMailTemplate.getTitle()).append("</h1>");
}
bodyContent.append("<p>").append(notificationMailTemplate.getText()).append("</p>");
for (Pair<String, String> link : notificationMailTemplate.getLabelsAndLinkUrls()) {
bodyContent.append("<a class=\"linkButton\" href=\"").append(link.getB()).append("\">");
bodyContent.append(link.getA()).append("</a>");
}
return TEMPLATE.replace("${title}", notificationMailTemplate.getSubject()).replace("${body}", bodyContent.toString());
}
protected abstract NotificationMailTemplate getMailTemplate(T objectToNotifyAbout, Locale locale);
protected abstract String constructSubject(T objectToNotifyAbout, Locale locale);
protected abstract String constructBody(T objectToNotifyAbout, Locale locale);
}
@@ -173,19 +173,44 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific
return messages.get(locale, "leaderboardOfEvent", leaderboardDisplayName, eventName);
}
private String createRaceBoardLink(TrackedRace trackedRace, Leaderboard leaderboard, Event event,
LeaderboardGroup leaderboardGroup) {
RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier();
String link = getBaseURL(event).toString() + "/gwt/RaceBoard.html?eventId=" + event.getId() + "&leaderboardName=" + leaderboard.getName()
+ "&leaderboardGroupName=" + leaderboardGroup.getName() + "&raceName="
+ raceIdentifier.getRaceName() + "&showMapControls=true&viewShowNavigationPanel=true&regattaName="
+ raceIdentifier.getRegattaName();
return link;
private Pair<String, String> createRaceBoardShowRaceLink(TrackedRace trackedRace, Leaderboard leaderboard,
Event event, LeaderboardGroup leaderboardGroup, Locale locale) {
return createRaceBoardLink(trackedRace, leaderboard, event, leaderboardGroup, "PLAYER",
"raceboardShowRaceLinkTitle", locale);
}
private String createHomeRacesListLink(Leaderboard leaderboard, Event event) {
return createHomeRegattaLink("races", leaderboard, event);
private Pair<String, String> createRaceBoardRaceAnalysisLink(TrackedRace trackedRace, Leaderboard leaderboard,
Event event, LeaderboardGroup leaderboardGroup, Locale locale) {
return createRaceBoardLink(trackedRace, leaderboard, event, leaderboardGroup, "FULL_ANALYSIS",
"raceboardRaceAnalysisLinkTitle", locale);
}
private Pair<String, String> createRaceBoardLink(TrackedRace trackedRace, Leaderboard leaderboard, Event event,
LeaderboardGroup leaderboardGroup, String raceboardMode, String labelMessageKey, Locale locale) {
RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier();
String link = getBaseURL(event).toString() + "/gwt/RaceBoard.html?locale=" + locale.toLanguageTag()
+ "&eventId=" + event.getId() + "&leaderboardName=" + leaderboard.getName()
+ "&leaderboardGroupName=" + leaderboardGroup.getName() + "&raceName="
+ raceIdentifier.getRaceName() + "&showMapControls=true&viewShowNavigationPanel=true&regattaName="
+ raceIdentifier.getRegattaName() + "&mode=" + raceboardMode;
return new Pair<String, String>(messages.get(locale, labelMessageKey), link);
}
private Pair<String, String> createHomeRacesListLink(Leaderboard leaderboard, Event event, Locale locale) {
return createHomeRegattaLink("races", "racesOverviewLinkTitle", leaderboard, event, locale);
}
private Pair<String, String> createHomeLeaderboardLink(Leaderboard leaderboard, Event event,
Locale locale) {
return createHomeRegattaLink("leaderboard", "leaderboardShowResultsLinkTitle", leaderboard, event, locale);
}
private Pair<String, String> createHomeRegattaLink(String tab, String labelMessageKey, Leaderboard leaderboard,
Event event, Locale locale) {
String link = getBaseURL(event).toString() + "/gwt/Home.html?locale=" + locale.toLanguageTag()
+ "#/regatta/" + tab + "/:eventId=" + event.getId() + "&regattaId=" + leaderboard.getName();
return new Pair<String, String>(messages.get(locale, labelMessageKey), link);
}
/**
@@ -201,34 +226,21 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific
}
return result;
}
private String createHomeLeaderboardLink(Leaderboard leaderboard, Event event) {
return createHomeRegattaLink("leaderboard", leaderboard, event);
}
private String createHomeRegattaLink(String tab, Leaderboard leaderboard, Event event) {
String link = getBaseURL(event).toString() + "/gwt/Home.html#/regatta/" + tab + "/:eventId=" + event.getId() + "&regattaId="
+ leaderboard.getName();
return link;
}
@Override
public void notifyUserOnBoatClassRaceChangesStateToFinished(BoatClass boatClass, TrackedRace trackedRace,
Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) {
doWithEvent(leaderboard, (event, leaderboardGroup) -> {
String link = createRaceBoardLink(trackedRace, leaderboard, event, leaderboardGroup);
mailQueue.addNotification(new NotificationSetNotification<BoatClass>(boatClass, boatClassResults) {
@Override
protected String constructSubject(BoatClass objectToNotifyAbout, Locale locale) {
return messages.get(locale, "boatClassRaceFinishedSubject", boatClass.getDisplayName());
}
@Override
protected String constructBody(BoatClass objectToNotifyAbout, Locale locale) {
protected NotificationMailTemplate getMailTemplate(BoatClass objectToNotifyAbout, Locale locale) {
String raceDescription = calculateRaceDescription(locale, event, leaderboard, raceColumn, fleet);
return messages.get(locale, "boatClassRaceFinishedBody", boatClass.getDisplayName(),
raceDescription, encodeLink(link));
return new NotificationMailTemplate(
messages.get(locale, "boatClassRaceFinishedSubject", boatClass.getDisplayName()),
messages.get(locale, "boatClassRaceFinishedBody", boatClass.getDisplayName(), raceDescription),
createRaceBoardShowRaceLink(trackedRace, leaderboard, event, leaderboardGroup, locale),
createRaceBoardRaceAnalysisLink(trackedRace, leaderboard, event, leaderboardGroup, locale));
}
});
});
@@ -238,19 +250,15 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific
public void notifyUserOnBoatClassWhenScoreCorrectionsAreAvailable(BoatClass boatClass, Leaderboard leaderboard) {
// TODO don't send notifications when a notification for the same boatClass / leaderboard has already been sent shortly before
doWithEvent(leaderboard, (event, leaderboardGroup) -> {
String link = createHomeLeaderboardLink(leaderboard, event);
mailQueue.addNotification(new NotificationSetNotification<BoatClass>(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) {
protected NotificationMailTemplate getMailTemplate(BoatClass objectToNotifyAbout, Locale locale) {
String leaderboardDescription = calculateLeaderboardDescription(locale, event, leaderboard);
return messages.get(locale, "boatClassScoreCorrectionBody", boatClass.getDisplayName(),
leaderboardDescription, encodeLink(link));
return new NotificationMailTemplate(
messages.get(locale, "boatClassScoreCorrectionSubject", boatClass.getDisplayName()),
messages.get(locale, "boatClassScoreCorrectionBody", boatClass.getDisplayName(), leaderboardDescription),
createHomeLeaderboardLink(leaderboard, event, locale));
}
});
});
@@ -259,49 +267,37 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific
public void notifyUserOnBoatClassUpcomingRace(BoatClass boatClass, Leaderboard leaderboard, RaceColumn raceColumn,
Fleet fleet, TimePoint when) {
doWithEvent(leaderboard, (event, leaderboardGroup) -> {
String link = createHomeRacesListLink(leaderboard, event);
mailQueue.addNotification(new NotificationSetNotification<BoatClass>(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) {
protected NotificationMailTemplate getMailTemplate(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, encodeLink(link));
return new NotificationMailTemplate(
messages.get(locale, "boatClassUpcomingRaceSubject", boatClass.getDisplayName()),
messages.get(locale, "boatClassUpcomingRaceBody", boatClass.getDisplayName(),
raceDescription, time),
createHomeRacesListLink(leaderboard, event, locale));
}
});
});
}
private String encodeLink(String link) {
String encodedLink = "<a href=\""+link+"\">"+link.replace("&", "&amp;")+"</a>";
return encodedLink;
}
@Override
public void notifyUserOnCompetitorPassesFinish(Competitor competitor, TrackedRace trackedRace,
Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) {
doWithEvent(leaderboard, (event, leaderboardGroup) -> {
String link = createRaceBoardLink(trackedRace, leaderboard, event, leaderboardGroup);
mailQueue.addNotification(new NotificationSetNotification<Competitor>(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) {
protected NotificationMailTemplate getMailTemplate(Competitor objectToNotifyAbout, Locale locale) {
String raceDescription = calculateRaceDescription(locale, event, leaderboard, raceColumn, fleet);
return messages.get(locale, "competitorPassesFinishBody", competitor.getName(), raceDescription,
encodeLink(link));
return new NotificationMailTemplate(
messages.get(locale, "competitorPassesFinishSubject", competitor.getName()),
messages.get(locale, "competitorPassesFinishBody", competitor.getName(), raceDescription),
createRaceBoardShowRaceLink(trackedRace, leaderboard, event, leaderboardGroup, locale),
createRaceBoardRaceAnalysisLink(trackedRace, leaderboard, event, leaderboardGroup, locale));
}
});
});
@@ -310,19 +306,16 @@ public class SailingNotificationServiceImpl implements Stoppable, SailingNotific
@Override
public void notifyUserOnCompetitorScoreCorrections(Competitor competitor, Leaderboard leaderboard) {
doWithEvent(leaderboard, (event, leaderboardGroup) -> {
String link = createHomeLeaderboardLink(leaderboard, event);
mailQueue.addNotification(new NotificationSetNotification<Competitor>(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) {
protected NotificationMailTemplate getMailTemplate(Competitor objectToNotifyAbout, Locale locale) {
String leaderboardDescription = calculateLeaderboardDescription(locale, event, leaderboard);
return messages.get(locale, "boatClassScoreCorrectionBody", competitor.getName(),
leaderboardDescription, encodeLink(link));
return new NotificationMailTemplate(
messages.get(locale, "boatClassScoreCorrectionSubject", competitor.getName()),
messages.get(locale, "boatClassScoreCorrectionBody", competitor.getName(), leaderboardDescription),
createHomeLeaderboardLink(leaderboard, event, locale),
createHomeLeaderboardLink(leaderboard, event, locale));
}
});
});