diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index 3926f7317f9..b7b5702a4fe 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -528,7 +528,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { logger.info("Couldn't find regatta "+regattaName+" for corresponding regatta leaderboard. Not loading regatta leaderboard."); } else { result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); - result.setName(leaderboardName); + result.setName(leaderboardName); // this will temporarily set the display name; it will be adjusted later if a display name is found } return result; } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index a7e64e9d558..1925384c9a1 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -1,7 +1,6 @@ package com.sap.sailing.domain.racelogtracking.impl; import java.io.IOException; -import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -11,17 +10,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.StreamSupport; -import javax.activation.DataHandler; -import javax.activation.DataSource; -import javax.mail.BodyPart; import javax.mail.MessagingException; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMultipart; -import javax.mail.util.ByteArrayDataSource; import org.osgi.framework.ServiceReference; -import com.google.zxing.WriterException; import com.sap.sailing.domain.abstractlog.impl.LastEventOfTypeFinder; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -73,7 +65,9 @@ import com.sap.sse.common.mail.MailException; import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.i18n.ResourceBundleStringMessages; import com.sap.sse.mail.MailService; -import com.sap.sse.qrcode.QRCodeGenerationUtil; +import com.sap.sse.mail.QRCodeMimeBodyPartSupplier; +import com.sap.sse.mail.SerializableDefaultMimeBodyPartSupplier; +import com.sap.sse.mail.SerializableMultipartSupplier; import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.util.impl.NonGwtUrlHelper; @@ -322,9 +316,6 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES; String subject = String.format("%s %s", B.get(locale, "trackingInvitationFor"), invitee); - // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm - BodyPart messageTextPart = new MimeBodyPart(); - boolean hasIOSAppUrl = iOSAppUrl != null && !iOSAppUrl.isEmpty(); boolean hasAndroidAppUrl = androidAppUrl != null && !androidAppUrl.isEmpty(); boolean hasLogoUrl = logoUrl != null && !logoUrl.isEmpty(); @@ -373,26 +364,16 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { htmlText.append(""); try { - messageTextPart.setContent(htmlText.toString(), "text/html"); - - BodyPart messageImagePart = new MimeBodyPart(); - InputStream imageIs = QRCodeGenerationUtil.create(url, 250); - DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); - messageImagePart.setDataHandler(new DataHandler(imageDs)); - messageImagePart.setHeader("Content-ID", ""); - messageImagePart.setHeader("Content-Disposition", "inline;filename=\"qr.png\""); - - MimeMultipart multipart = new MimeMultipart(); - multipart.addBodyPart(messageTextPart); - multipart.addBodyPart(messageImagePart); - - getMailService().sendMail(toAddress, subject, multipart); - } catch (MessagingException | MailException | WriterException | IOException e) { + final SerializableMultipartSupplier multipartSupplier = new SerializableMultipartSupplier("Invite", + new SerializableDefaultMimeBodyPartSupplier(htmlText.toString(), "text/html"), + new QRCodeMimeBodyPartSupplier(url)); + getMailService().sendMail(toAddress, subject, multipartSupplier); + } catch (MessagingException | MailException | IOException e) { logger.log(Level.SEVERE, "Error trying to send mail to " + invitee + " with e-mail address " + toAddress, e); throw new MailException(e.getMessage()); } } - + @Override public void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash, String emails, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException { diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java index 64c8053f107..d0b8258ac10 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertThat; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import com.sap.sailing.domain.swisstimingadapter.RaceType; @@ -53,14 +52,4 @@ public class DomainFactoryTest { assertThat(raceType, is(notNullValue())); assertThat(raceType.getRaceCode(), is(OlympicRaceCode.UNKNOWN)); } - - @Ignore - @Test - public void testCompetitorLookup() throws Exception { - before(); - -// swissTimingDomainFactory.getOrCreateCompetitor(boatID, threeLetterIOCCode, name, boatClass); - - } - } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingRaceConfigurationTest.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingRaceConfigurationTest.java index 2018dc05779..9aadcc9abaf 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingRaceConfigurationTest.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingRaceConfigurationTest.java @@ -41,7 +41,6 @@ public class SwissTimingRaceConfigurationTest { } } - @Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19") @Test public void testRaceConfig_446483_no_detail_config() throws Exception { URL configFileURL = new URL(MessageFormat.format(SwissTimingReplayServiceImpl.RACE_CONFIG_URL_TEMPLATE, "446483")); @@ -59,7 +58,6 @@ public class SwissTimingRaceConfigurationTest { } - @Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19") @Test public void testRaceConfig_6260_with_detail_config() throws Exception { URL configFileURL = new URL(MessageFormat.format(SwissTimingReplayServiceImpl.RACE_CONFIG_URL_TEMPLATE, "6260")); diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java index e067deb4674..63148dc1c45 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java @@ -74,12 +74,11 @@ public class SwissTimingReplayAdapterServiceTest { assertEquals(321920, replayCountListener.rankingMarkCount); } - @Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19") @Test public void testRaceData_SAW005905_20120805_EqualsOnlineVersion() throws Exception { byte[] localCopy = read(getClass().getResourceAsStream("/SAW005905.20120805.replay")); byte[] onlineCopy = read((InputStream) new URL( - "http://live.ota.st-sportservice.com/Replay?id=446495&_start=0").getContent()); + "http://ota2.sportresult.com/Replay?id=446495&_start=0").getContent()); assertArrayEquals(localCopy, onlineCopy); } @@ -114,13 +113,12 @@ public class SwissTimingReplayAdapterServiceTest { assertEquals(9500, replayCountListener.rankingMarkCount); } - @Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19") @Test public void testRaceData_SAW005905_20120805_online() throws Exception { SwissTimingReplayTestListener replayCountListener = new SwissTimingReplayTestListener(); // race ID is 450053, as extracted from http://live.ota.st-sportservice.com/service?cmd=unity_race_overview&id=91 byte[] onlineCopy = read((InputStream) new URL( - "http://live.ota.st-sportservice.com/Replay?id=446495&_start=0").getContent()); + "http://ota2.sportresult.com/Replay?id=446495&_start=0").getContent()); new SwissTimingReplayParserImpl().readData(new ByteArrayInputStream(onlineCopy), replayCountListener); assertEquals(0, replayCountListener.keyFrameIndexSum); assertEquals(715, replayCountListener.keyFrameIndexPositionCount); diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java index 70d9e58470a..5863c666e57 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java @@ -34,7 +34,7 @@ import com.sap.sse.util.ByteArrayOutputStreamWithVisibleBuffer; public class SwissTimingReplayServiceImpl implements SwissTimingReplayService { - public static final String RACE_CONFIG_URL_TEMPLATE = "http://live.ota.st-sportservice.com/configuration?_race={0}&effective=1&additional=config"; + public static final String RACE_CONFIG_URL_TEMPLATE = "http://ota2.sportresult.com/configuration?_race={0}&effective=1&additional=config"; private static final String SWISSTIMING_DATEFORMAT_PATTERN = "dd.MM.yyyy HH:mm"; private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("GMT"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java index 99a26db6315..07bbcb5ba69 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java @@ -238,19 +238,21 @@ public class RegattaDetailsComposite extends Composite { editRacesOfRegattaSeries(regatta, series); } else if (SeriesConfigImagesBarCell.ACTION_REMOVE.equals(value)) { RegattaIdentifier identifier = new RegattaName(regatta.getName()); - sailingService.removeSeries(identifier, series.getName(), new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable cause) { - errorReporter.reportError("Error trying to remove series " + series.getName() - + ": " + cause.getMessage()); - } - - @Override - public void onSuccess(Void result) { - regattaRefresher.fillRegattas(); - } - })); + if (Window.confirm(stringMessages.reallyRemoveSeries(series.getName()))) { + sailingService.removeSeries(identifier, series.getName(), + new MarkedAsyncCallback(new AsyncCallback() { + @Override + public void onFailure(Throwable cause) { + errorReporter.reportError("Error trying to remove series " + series.getName() + + ": " + cause.getMessage()); + } + + @Override + public void onSuccess(Void result) { + regattaRefresher.fillRegattas(); + } + })); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 7ba19b393eb..4e4363b26d0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1817,6 +1817,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String showRacePlaceColumn(); String unknownLeaderboardType(String name); String selectCompetitorsToEliminate(); - String trackedRaceAlreadyLinked(); + String trackedRaceAlreadyLinked(); + String reallyRemoveSeries(String seriesName); String noData(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 495940f5793..ff3a46cbbc6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1796,4 +1796,5 @@ showRacePlaceColumn=Show race place estimatedDistance=Estimated Distance estimatedTime=Estimated Time nothingUpcoming=There are currently no further planned races +reallyRemoveSeries=Do you really want to remove the series ''{0}''? Note: all race logs for the attached race columns will also permanently be removed! noData=No data is available diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 7c825b60098..e8bf0002a79 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1775,4 +1775,5 @@ raceRankShort=Platz showRacePlaceColumn=Rennplazierungsspalte unknownLeaderboardType=Unbekannter Ranglistentyp {0} selectCompetitorsToEliminate=Zu eliminierende Teilnehmer auswählen -trackedRaceAlreadyLinked=Das Rennen ist bereits mit einer Renn-Spalte verlinkt. Wollen Sie die Verbindung wirklich ändern? \ No newline at end of file +trackedRaceAlreadyLinked=Das Rennen ist bereits mit einer Renn-Spalte verlinkt. Wollen Sie die Verbindung wirklich ändern? +reallyRemoveSeries=Möchten Sie die Serie ''{0}'' wirklich entfernen? Achtung: alle Ereignisprotokolle (RaceLogs) der zugehörigen Renn-Spalten werden unwiederbringlich gelöscht! \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/DefaultQuickRanksDTOProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/DefaultQuickRanksDTOProvider.java index 5b29028a947..ef2ef86f495 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/DefaultQuickRanksDTOProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/DefaultQuickRanksDTOProvider.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.client.shared.racemap; +import java.util.Collections; import java.util.Map; import java.util.Map.Entry; @@ -8,13 +9,13 @@ import com.sap.sailing.gwt.ui.shared.QuickRankDTO; import com.sap.sse.common.Util; public class DefaultQuickRanksDTOProvider extends AbstractQuickRanksDTOProvider { - private Map quickRanksFromServer; + private Map currentQuickRanksFromServer = Collections.emptyMap(); @Override - public void quickRanksReceivedFromServer(Map quickRanksFromServer) { - final Map oldQuickRanksFromServer = quickRanksFromServer; - this.quickRanksFromServer = quickRanksFromServer; - for (final Entry e : quickRanksFromServer.entrySet()) { + public void quickRanksReceivedFromServer(Map receivedQuickRanksFromServer) { + final Map oldQuickRanksFromServer = this.currentQuickRanksFromServer; + this.currentQuickRanksFromServer = Util.nullToEmptyMap(receivedQuickRanksFromServer); + for (final Entry e : currentQuickRanksFromServer.entrySet()) { final QuickRankDTO oldQuickRank = oldQuickRanksFromServer.get(e.getKey()); if (Util.equalsWithNull(oldQuickRank, e.getValue())) { notifyListeners(e.getKey(), oldQuickRank, e.getValue()); @@ -24,7 +25,7 @@ public class DefaultQuickRanksDTOProvider extends AbstractQuickRanksDTOProvider @Override public Map getQuickRanks() { - return quickRanksFromServer; + return currentQuickRanksFromServer; } } 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 23d206c5be6..da4aa906f4b 100644 --- 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 @@ -1406,7 +1406,8 @@ public class RaceMap extends AbstractCompositeComponent impleme final static Distance advantageLineLength = new MeterDistance(1000); // TODO this should probably rather scale with the visible area of the map; bug 616 private void showAdvantageLine(Iterable competitorsToShow, Date date, long timeForPositionTransitionMillis) { - if (map != null && lastRaceTimesInfo != null && quickRanksDTOProvider.getQuickRanks() != null && lastCombinedWindTrackInfoDTO != null) { + if (map != null && lastRaceTimesInfo != null && quickRanksDTOProvider.getQuickRanks().isEmpty() + && lastCombinedWindTrackInfoDTO != null) { boolean drawAdvantageLine = false; if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { // find competitor with highest rank diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java index ac75a0370d6..ff2252823c4 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java @@ -66,8 +66,8 @@ public class RegattaDetailsCompositePO extends PageArea { if (entry != null) { WebElement removeAction = ActionsHelper.findRemoveAction(entry.getWebElement()); removeAction.click(); - // ActionsHelper.acceptAlert(this.driver); - // waitForAjaxRequests(); + ActionsHelper.acceptAlert(this.driver); + waitForAjaxRequests(); } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationMailTemplate.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationMailTemplate.java index 27d6b5a67ee..38121b3e96b 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationMailTemplate.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationMailTemplate.java @@ -1,12 +1,20 @@ package com.sap.sailing.server.notification.impl; +import java.io.Serializable; import java.net.URL; import java.util.Arrays; import com.sap.sse.common.Util.Pair; -public class NotificationMailTemplate { - +/** + * The class needs to implement the {@link Serializable} interface because it can become part of a + * {@link SerializableMultipartSupplier}. + * + * @author Axel Uhl (D043530) + * + */ +public class NotificationMailTemplate implements Serializable { + private static final long serialVersionUID = 3291811210425722552L; private final String subject; private final String title; private final String text; diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationSetNotification.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationSetNotification.java index f61077ac829..d03c67e5033 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationSetNotification.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/notification/impl/NotificationSetNotification.java @@ -10,17 +10,12 @@ import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; -import javax.activation.DataHandler; -import javax.activation.DataSource; -import javax.mail.BodyPart; -import javax.mail.Multipart; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMultipart; -import javax.mail.util.ByteArrayDataSource; - import com.sap.sse.common.Util.Pair; import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl; import com.sap.sse.mail.MailService; +import com.sap.sse.mail.SerializableDefaultMimeBodyPartSupplier; +import com.sap.sse.mail.SerializableImageMimeBodyPartSupplier; +import com.sap.sse.mail.SerializableMultipartSupplier; import com.sap.sse.mail.queue.MailNotification; import com.sap.sse.security.PreferenceObjectBasedNotificationSet; import com.sap.sse.security.User; @@ -81,22 +76,12 @@ public abstract class NotificationSetNotification implements MailNotification // send batches with the actual mail addresses as bcc? associatedNotificationSet.forUsersWithVerifiedEmailMappedTo(objectToNotifyAbout, (user) -> { Locale locale = user.getLocaleOrDefault(); - Multipart multipart = new MimeMultipart("related"); - BodyPart bodyPart = new MimeBodyPart(); - BodyPart messageImagePart = new MimeBodyPart(); + final NotificationMailTemplate mailTemplate = getMailTemplate(objectToNotifyAbout, locale); try { - // TODO: add cid:saplogo - NotificationMailTemplate mailTemplate = getMailTemplate(objectToNotifyAbout, locale); - bodyPart.setContent(getMailContent(mailTemplate, user, locale), "text/html"); - multipart.addBodyPart(bodyPart); - - DataSource imageDs = new ByteArrayDataSource(LOGO_BYTES, "image/png"); - messageImagePart.setDataHandler(new DataHandler(imageDs)); - messageImagePart.setHeader("Content-ID", "saplogo"); - messageImagePart.setHeader("Content-Disposition", "inline;filename=\"saplogo.png\""); - multipart.addBodyPart(messageImagePart); - - mailService.sendMail(user.getEmail(), mailTemplate.getSubject(), multipart); + final SerializableMultipartSupplier multipartSupplier = new SerializableMultipartSupplier("related", + new SerializableDefaultMimeBodyPartSupplier(getMailContent(mailTemplate, user, locale), "text/html"), + new SerializableImageMimeBodyPartSupplier(LOGO_BYTES, "image/png", "saplogo", "saplogo.png")); + mailService.sendMail(user.getEmail(), mailTemplate.getSubject(), multipartSupplier); } catch (Exception e) { logger.log(Level.SEVERE, "Could not send mail notification for \"" + objectToNotifyAbout + "\" to user \"" + user + "\"", e); diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index d358e28eb08..b03ed20381e 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -514,7 +514,7 @@ public class Util { * {@link #isEmpty(Iterable) isEmpty(splitAlongWhitespaceRespectingDoubleQuotedPhrases(" \n\t ")) * * - * @return if {@code s==null}, then {@code null}, else a non-{@code null} but possibly empty sequence of {@link Strings} whose iteration order corresponds with + * @return if {@code s==null}, then {@code null}, else a non-{@code null} but possibly empty sequence of {@link String} whose iteration order corresponds with * the occurrence of the split results, left to right, in {@code s} */ public static Iterable splitAlongWhitespaceRespectingDoubleQuotedPhrases(String s) { @@ -734,4 +734,14 @@ public class Util { public static > T max(T... elements) { return Collections.max(Arrays.asList(elements)); } + + /** + * Checks if the given map is null, and if, returns an empty map. + */ + public static Map nullToEmptyMap(Map map) { + if (map == null) { + return Collections.emptyMap(); + } + return map; + } } \ No newline at end of file diff --git a/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java index f7d209626b2..9d0ef53af28 100644 --- a/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java +++ b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java @@ -8,6 +8,7 @@ import org.junit.Before; import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailService; import com.sap.sse.mail.MailServiceResolver; +import com.sap.sse.mail.SerializableMultipartSupplier; import com.sap.sse.mail.impl.MailServiceImpl; import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp; import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; @@ -34,12 +35,13 @@ public class AbstractMailServiceReplicationTest extends AbstractServerWithSingle }; mailService[0] = new MailServiceImpl(null, mailServiceResolver) { @Override - protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) + public Void internalSendMail(String toAddress, String subject, SerializableMultipartSupplier multipartSupplier) throws MailException { if (canSendMail) { Integer old = numberOfMailsSent.get(this); numberOfMailsSent.put(this, old == null ? 1 : old + 1); } + return null; } }; return mailService[0]; diff --git a/java/com.sap.sse.mail/META-INF/MANIFEST.MF b/java/com.sap.sse.mail/META-INF/MANIFEST.MF index ab125af96f5..f78820d7d6a 100755 --- a/java/com.sap.sse.mail/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.mail/META-INF/MANIFEST.MF @@ -12,6 +12,7 @@ Export-Package: com.sap.sse.mail, com.sap.sse.mail.queue.impl Import-Package: javax.mail, javax.mail.internet, + javax.mail.util;version="1.4.0", org.osgi.framework, org.osgi.util.tracker;version="1.5.1" Require-Bundle: com.sap.sse.replication, diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java index 95fbc3934c0..100f5a1e848 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java @@ -1,6 +1,8 @@ package com.sap.sse.mail; -import javax.mail.Multipart; +import java.io.IOException; + +import javax.mail.MessagingException; import com.sap.sse.common.IsManagedByCache; import com.sap.sse.common.mail.MailException; @@ -29,5 +31,5 @@ public interface MailService extends ReplicableWithObjectInputStream"); + messageImagePart.setHeader("Content-Disposition", "inline;filename=\"qr.png\""); + return messageImagePart; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableDefaultMimeBodyPartSupplier.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableDefaultMimeBodyPartSupplier.java new file mode 100755 index 00000000000..d039ab07a51 --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableDefaultMimeBodyPartSupplier.java @@ -0,0 +1,36 @@ +package com.sap.sse.mail; + +import java.io.Serializable; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; + +/** + * Can construct a {@link MimeBodyPart} out of a serializable object and a content type specification string. + * + * @author Axel Uhl (D043530) + * + */ +public class SerializableDefaultMimeBodyPartSupplier implements SerializableMimeBodyPartSupplier { + private static final long serialVersionUID = -2893291156595020570L; + private final Serializable serializableContent; + private final String contentType; + + public SerializableDefaultMimeBodyPartSupplier(Serializable serializableContent, String contentType) { + super(); + this.serializableContent = serializableContent; + this.contentType = contentType; + } + + @Override + public MimeBodyPart get() { + final MimeBodyPart result = new MimeBodyPart(); + try { + result.setContent(serializableContent, contentType); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + return result; + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableImageMimeBodyPartSupplier.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableImageMimeBodyPartSupplier.java new file mode 100755 index 00000000000..d2715f4baad --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableImageMimeBodyPartSupplier.java @@ -0,0 +1,39 @@ +package com.sap.sse.mail; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.util.ByteArrayDataSource; + +public class SerializableImageMimeBodyPartSupplier implements SerializableMimeBodyPartSupplier { + private static final long serialVersionUID = -1540223936240644379L; + private final byte[] imageBytes; + private final String contentType; + private final String contentId; + private final String imageFilename; + + public SerializableImageMimeBodyPartSupplier(byte[] imageBytes, String contentType, String contentId, + String imageFilename) { + super(); + this.imageBytes = imageBytes; + this.contentType = contentType; + this.contentId = contentId; + this.imageFilename = imageFilename; + } + + @Override + public MimeBodyPart get() { + final MimeBodyPart messageImagePart = new MimeBodyPart(); + DataSource imageDs = new ByteArrayDataSource(imageBytes, contentType); + try { + messageImagePart.setDataHandler(new DataHandler(imageDs)); + messageImagePart.setHeader("Content-ID", contentId); + messageImagePart.setHeader("Content-Disposition", "inline;filename=\"" + imageFilename + "\""); + return messageImagePart; + } catch (MessagingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableMimeBodyPartSupplier.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableMimeBodyPartSupplier.java new file mode 100755 index 00000000000..4d5f06f78ac --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableMimeBodyPartSupplier.java @@ -0,0 +1,18 @@ +package com.sap.sse.mail; + +import java.io.Serializable; +import java.util.function.Supplier; + +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; + +/** + * Plays together with {@link SerializableMultipartSupplier}. One or more instances of this type can be + * passed to its constructor so that after de-serialization a {@link Multipart} message can be constructed + * from the parts. + * + * @author Axel Uhl (D043530) + * + */ +public interface SerializableMimeBodyPartSupplier extends Supplier, Serializable { +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableMultipartSupplier.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableMultipartSupplier.java new file mode 100755 index 00000000000..603683a3290 --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/SerializableMultipartSupplier.java @@ -0,0 +1,33 @@ +package com.sap.sse.mail; + +import java.util.function.Supplier; + +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeMultipart; + +import com.sap.sse.common.Named; +import com.sap.sse.common.impl.NamedImpl; + +public class SerializableMultipartSupplier extends NamedImpl implements Supplier, Named { + private static final long serialVersionUID = 4316486727705670702L; + private final SerializableMimeBodyPartSupplier[] bodyPartSuppliers; + + public SerializableMultipartSupplier(String name, SerializableMimeBodyPartSupplier... partSuppliers) { + super(name); + this.bodyPartSuppliers = partSuppliers; + } + + @Override + public Multipart get() { + final Multipart result = new MimeMultipart(getName()); + for (final SerializableMimeBodyPartSupplier bodyPartSupplier : bodyPartSuppliers) { + try { + result.addBodyPart(bodyPartSupplier.get()); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + } + return result; + } +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java index 8709d330927..526b8a0c3e3 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -17,7 +17,6 @@ import java.util.logging.Logger; import javax.mail.Message.RecipientType; import javax.mail.MessagingException; -import javax.mail.Multipart; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; @@ -28,6 +27,7 @@ import javax.mail.internet.MimeUtility; import com.sap.sse.common.IsManagedByCache; import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailServiceResolver; +import com.sap.sse.mail.SerializableMultipartSupplier; import com.sap.sse.replication.OperationExecutionListener; import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationMasterDescriptor; @@ -68,7 +68,7 @@ public class MailServiceImpl implements ReplicableMailService { protected static interface ContentSetter { void setContent(MimeMessage msg) throws MessagingException; } - + private boolean canSendMail() { return mailProperties != null && mailProperties.containsKey("mail.transport.protocol"); } @@ -88,16 +88,12 @@ public class MailServiceImpl implements ReplicableMailService { msg.setSubject(subject); } msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); - // this fixes the DCH MIME type error // see http://tanyamadurapperuma.blogspot.de/2014/01/struggling-with-nosuchproviderexception.html Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader()); - contentSetter.setContent(msg); - // for testing with gmail //Transport ts = session.getTransport("smtps"); - Transport ts = session.getTransport(); ts.connect(); ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); @@ -134,19 +130,19 @@ public class MailServiceImpl implements ReplicableMailService { } @Override - public Void internalSendMail(String toAddress, String subject, Multipart multipartContent) throws MailException { + public Void internalSendMail(String toAddress, String subject, SerializableMultipartSupplier multipartSupplier) throws MailException { internalSendMail(toAddress, subject, new ContentSetter() { @Override public void setContent(MimeMessage msg) throws MessagingException { - msg.setContent(multipartContent); + msg.setContent(multipartSupplier.get()); } }); return null; } @Override - public void sendMail(String toAddress, String subject, Multipart multipartContent) throws MailException { - apply(s -> s.internalSendMail(toAddress, subject, multipartContent)); + public void sendMail(String toAddress, String subject, SerializableMultipartSupplier multipartSupplier) throws MailException { + apply(s -> s.internalSendMail(toAddress, subject, multipartSupplier)); } @Override diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java index 116ae1f798c..ba377862b15 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java @@ -1,12 +1,18 @@ package com.sap.sse.mail.impl; +import javax.mail.MessagingException; import javax.mail.Multipart; import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailService; +import com.sap.sse.mail.SerializableMultipartSupplier; public interface ReplicableMailService extends MailService { - Void internalSendMail(String toAddress, String subject, String body) throws MailException; + Void internalSendMail(String toAddress, String subject, String body) throws MailException, MessagingException; - Void internalSendMail(String toAddress, String subject, Multipart multipartContent) throws MailException; + /** + * @param multipartSupplier + * can produce a {@link Multipart} object and must be serializable + */ + Void internalSendMail(String toAddress, String subject, SerializableMultipartSupplier multipartSupplier) throws MailException, MessagingException; } diff --git a/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java index b4353f7139a..4ae986db449 100644 --- a/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java +++ b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java @@ -27,7 +27,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; * */ public class QRCodeGenerationUtil { - public static InputStream create(String text, int sizeInPixels) throws WriterException, IOException { + public static InputStream create(String text, int sizeInPixels) throws Exception { Hashtable hintMap = new Hashtable(); hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);