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/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java index 989807af537..71dd4dc5019 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java @@ -8,9 +8,6 @@ import com.sap.sailing.gwt.autoplay.client.places.screens.liveraceloop.racemapwi import com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racemap.PreRaceRacemapPlace; import com.sap.sailing.gwt.autoplay.client.utils.AutoplayHelper; import com.sap.sailing.gwt.autoplay.client.utils.AutoplayHelper.RVWrapper; -import com.sap.sailing.gwt.home.communication.event.minileaderboard.GetMiniLeaderboardDTO; -import com.sap.sailing.gwt.home.communication.event.minileaderboard.GetMiniLeaderbordAction; -import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL; public class PreLiveRaceWithRacemapNode extends FiresPlaceNode { private final AutoPlayClientFactory cf; @@ -21,12 +18,12 @@ public class PreLiveRaceWithRacemapNode extends FiresPlaceNode { } - public void onStart() { AutoplayHelper.create(cf.getSailingService(), cf.getErrorReporter(), - cf.getAutoPlayCtx().getContextDefinition().getLeaderboardName(), cf.getAutoPlayCtx().getContextDefinition().getEventId(), - cf.getAutoPlayCtx().getEvent(), cf.getEventBus(), cf.getDispatch(), - cf.getAutoPlayCtx().getPreLiveRace(), new AsyncCallback() { + cf.getAutoPlayCtx().getContextDefinition().getLeaderboardName(), + cf.getAutoPlayCtx().getContextDefinition().getEventId(), cf.getAutoPlayCtx().getEvent(), + cf.getEventBus(), cf.getDispatch(), cf.getAutoPlayCtx().getPreLiveRace(), + new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -39,33 +36,16 @@ public class PreLiveRaceWithRacemapNode extends FiresPlaceNode { @Override public void onSuccess(RVWrapper result) { - cf.getDispatch().execute( - new GetMiniLeaderbordAction(cf.getAutoPlayCtx().getEvent().id, - cf.getAutoPlayCtx().getContextDefinition().getLeaderboardName()), - new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - // fireEvent(new DataLoadFailureEvent(MiniLeaderboardLoader.this, caught)); - firePlaceChangeAndStartTimer(); - } + PreRaceRacemapPlace place = new PreRaceRacemapPlace(); + place.setRaceMap(result.raceboardPerspective, result.csel); + // add later with settings here + place.setURL(cf.getAutoPlayCtx().getEvent().getOfficialWebsiteURL()); + setPlaceToGo(place); + getBus().fireEvent( + new AutoPlayHeaderEvent(cf.getAutoPlayCtx().getPreLiveRace().getRegattaName(), + cf.getAutoPlayCtx().getPreLiveRace().getRaceName())); + firePlaceChangeAndStartTimer(); - @Override - public void onSuccess(ResultWithTTL resultTTL) { - GetMiniLeaderboardDTO dto = resultTTL.getDto(); - PreRaceRacemapPlace place = new PreRaceRacemapPlace(); - place.setLeaderBoardDTO(dto); - place.setRaceMap(result.raceboardPerspective, result.csel); - // add later with settings here - place.setURL(cf.getAutoPlayCtx().getEvent().getOfficialWebsiteURL()); - setPlaceToGo(place); - getBus().fireEvent( - new AutoPlayHeaderEvent( - cf.getAutoPlayCtx().getPreLiveRace().getRegattaName(), - cf.getAutoPlayCtx().getPreLiveRace().getRaceName())); - firePlaceChangeAndStartTimer(); - - } - }); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/afterliveraceloop/boats/RaceEndWithBoatsPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/afterliveraceloop/boats/RaceEndWithBoatsPresenterImpl.java index 81c18a839fd..948d037038e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/afterliveraceloop/boats/RaceEndWithBoatsPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/afterliveraceloop/boats/RaceEndWithBoatsPresenterImpl.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.autoplay.client.places.screens.afterliveraceloop.boa import java.util.List; +import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.Label; import com.sap.sailing.domain.common.Distance; @@ -95,10 +96,17 @@ public class RaceEndWithBoatsPresenterImpl extends AutoPlayPresenterConfigured lifeRaceResult = leaderboardPanel.getLeaderboard().getRaceList(); + RaceColumnDTO raceColumn = null; + for(RaceColumnDTO column:lifeRaceResult){ + if(column.containsRace(lifeRace)){ + raceColumn = column; + } + } + GWT.debugger(); List sortedCompetitors = leaderboardPanel.getLeaderboard() - .getCompetitorsFromBestToWorst(preselectedRaceColumn); + .getCompetitorsFromBestToWorst(raceColumn); if (sortedCompetitors.size() >= 3) { view.setFirst(sortedCompetitors.get(0)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java index cc3e12fbd47..c40061e0b4e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java @@ -20,7 +20,9 @@ import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; +import com.sap.sailing.gwt.ui.raceboard.QuickRanksDTOFromLeaderboardDTOProvider; import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sse.gwt.client.ErrorReporter; @@ -117,7 +119,11 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl List sortedCompetitors = leaderboardPanel.getLeaderboardTable().getVisibleItems(); if (sortedCompetitors.size() > 0) { - view.setStatistic(windSpeed,getPlace().getStatistic().getDistance(), AutoplayHelper.durationOfCurrentLiveRaceRunning()); + if(getPlace().getStatistic() == null){ + view.setStatistic(windSpeed,null, AutoplayHelper.durationOfCurrentLiveRaceRunning()); + }else{ + view.setStatistic(windSpeed,getPlace().getStatistic().getDistance(), AutoplayHelper.durationOfCurrentLiveRaceRunning()); + } } } @@ -154,6 +160,9 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl true, lifeRace, getPlace().getRaceMapSelectionProvider(), timer, null, getSlideCtx().getContextDefinition().getLeaderboardName(), errorReporter, StringMessages.INSTANCE, null, false, null, false, null, false, true, false, false, false); + + getPlace().getRaceMap().setQuickRanksDTOProvider(new QuickRanksDTOFromLeaderboardDTOProvider(new RaceCompetitorSet(getPlace().getRaceMapSelectionProvider()), lifeRace)); + view.startingWith(this, panel, getPlace().getRaceMap(), leaderboardPanel); selectionTimer.schedule(SWITCH_COMPETITOR_DELAY); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java index af3f6d34427..7248986e03e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java @@ -53,13 +53,4 @@ public class PreRaceRacemapPlace extends Place { public String getUrl() { return url; } - - public void setLeaderBoardDTO(GetMiniLeaderboardDTO dto) { - this.leaderboard = dto; - } - - public GetMiniLeaderboardDTO getLeaderboard() { - return leaderboard; - } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java index 8b2939ba4d0..d58598e7a4a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java @@ -77,7 +77,7 @@ public class PreRaceRacemapPresenterImpl extends AutoPlayPresenterConfigured
() {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java
index 1b3dff470b2..d593a3ef24d 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java
@@ -31,6 +31,7 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
 import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
 import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings;
 import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
+import com.sap.sailing.gwt.ui.raceboard.AbstractQuickRanksDTOProvider;
 import com.sap.sailing.gwt.ui.shared.EventDTO;
 import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
 import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
@@ -50,15 +51,19 @@ public class AutoplayHelper {
     private static Timer raceboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l);
     private static Date startOfLifeRace;
     public static final AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
+    /**
+     * If a racestart is longer ago, the race is never considered live, even if all other checks pass
+     */
+    private static final long NEGATIVE_SANITY_CHECK = -24 * 60 * 60 * 1000;
 
-    public static long durationOfCurrentLiveRaceRunning(){
-        if(startOfLifeRace != null){
-            return raceboardTimer.getLiveTimePointInMillis()-startOfLifeRace.getTime();
-        }else{
+    public static long durationOfCurrentLiveRaceRunning() {
+        if (startOfLifeRace != null) {
+            return raceboardTimer.getLiveTimePointInMillis() - startOfLifeRace.getTime();
+        } else {
             return 0;
         }
     }
-    
+
     public static void getLiveRace(SailingServiceAsync sailingService, ErrorReporter errorReporter, EventDTO event,
             String leaderBoardName, SailingDispatchSystem dispatch,
             AsyncCallback> callback) {
@@ -139,7 +144,7 @@ public class AutoplayHelper {
                         long startTimeInMs = raceTimes.getStartOfRace().getTime();
                         long delayToLiveInMs = raceTimes.delayToLiveInMs;
                         long startIn = startTimeInMs - serverTimeDuringRequest.getTime() - delayToLiveInMs;
-                        if (startIn <= PRE_RACE_DELAY) {
+                        if (startIn <= PRE_RACE_DELAY && startIn > NEGATIVE_SANITY_CHECK) {
                             startOfLifeRace = raceTimes.getStartOfRace();
                             return new Pair(startIn, raceIdentifier);
                         }
@@ -203,7 +208,7 @@ public class AutoplayHelper {
                                                     AutoplayHelper.asyncActionsExecutor, errorReporter, raceboardTimer,
                                                     callback, clientTimeWhenResponseWasReceived,
                                                     serverTimeDuringRequest, clientTimeWhenRequestWasSent,
-                                                    raceTimesInfo);
+                                                    raceTimesInfo, new DefaultQuickRanksDTOProvider());
                                         }
 
                                         @Override
@@ -261,7 +266,8 @@ public class AutoplayHelper {
             Iterable competitors, SailingServiceAsync sailingService,
             AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter, Timer raceboardTimer,
             AsyncCallback callback, long clientTimeWhenResponseWasReceived, Date serverTimeDuringRequest,
-            long clientTimeWhenRequestWasSent, Map raceTimesInfos) {
+            long clientTimeWhenRequestWasSent, Map raceTimesInfos,
+            AbstractQuickRanksDTOProvider provider) {
 
         ArrayList typesToConsiderOnZoom = new ArrayList<>();
         // Other zoom types such as BOATS, TAILS or WINDSENSORS are not currently used as default zoom types.
@@ -282,7 +288,7 @@ public class AutoplayHelper {
         RaceMap raceboardPerspective = new RaceMap(null, null, raceMapLifecycle, settings, sailingService,
                 asyncActionsExecutor, errorReporter, raceboardTimer, competitorSelectionProvider,
                 new RaceCompetitorSet(competitorSelectionProvider), StringMessages.INSTANCE, currentLiveRace,
-                raceMapResources, false, new DefaultQuickRanksDTOProvider());
+                raceMapResources, false, provider);
         raceboardPerspective.raceTimesInfosReceived(raceTimesInfos, clientTimeWhenRequestWasSent,
                 serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
         // raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceboardPerspective);
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 b11b5274fc0..ddeb290bbf3 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,7 +1817,8 @@ 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();
     String settingsId();
     String documentSettingsId();
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 46f8d0d02eb..d33b7e9d7b6 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,6 +1796,7 @@ 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
 settingsId=Settings ID
 documentSettingsId=Document settings ID
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 17170d36666..3bf2379da62 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
@@ -1784,3 +1784,4 @@ resetToDefault=Auf Standard zurücksetzen
 resetToDefaultInProgress=Setze zurück...
 settingsRemoved=Standardeinstellungen wiederhergestellt
 settingsRemovedError=Fehler beim Widerherstellen der Standardeinstellungen
+reallyRemoveSeries=Möchten Sie die Serie ''{0}'' wirklich entfernen? Achtung: alle Ereignisprotokolle (RaceLogs) der zugehörigen Renn-Spalten werden unwiederbringlich gelöscht!
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/QuickRanksDTOProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/QuickRanksDTOProvider.java
index ea9126bd275..a14f05f60fd 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/QuickRanksDTOProvider.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/QuickRanksDTOProvider.java
@@ -31,6 +31,13 @@ public interface QuickRanksDTOProvider {
     
     void removeQuickRanksListener(QuickRanksListener listener);
     
+    /**
+     * When switching the underlying provider, this method allows to move current listeners to the new provider.
+     * 
+     * @param newProvider
+     */
+    public void moveListernersTo(QuickRanksDTOProvider newProvider);
+
     /**
      * The strategy may or may not use this quick ranks information provided directly from the server. This information
      * may have been provided with some delay and may be slightly inconsistent with a {@link LeaderboardDTO leaderboard}
@@ -38,7 +45,8 @@ public interface QuickRanksDTOProvider {
      * {@link LeaderboardDTO leaderboard} should ignore this call if it already has quick ranks information available
      * from a leaderboard.
      * 
-     * @param quickRanksFromServer keys are the competitor IDs as strings
+     * @param quickRanksFromServer
+     *            keys are the competitor IDs as strings
      */
     void quickRanksReceivedFromServer(Map quickRanksFromServer);
     
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 36249a8fa8d..ea3d9f8eae3 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
@@ -353,7 +353,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
      * quick ranks received from a {@link RaceMapDataDTO#quickRanks} field but may choose to ignore this information, e.g.,
      * if it can assume that more current information about ranks and leg numbers is available from a {@link LeaderboardDTO}.
      */
-    private final QuickRanksDTOProvider quickRanksDTOProvider;
+    private QuickRanksDTOProvider quickRanksDTOProvider;
     
     private final CombinedWindPanel combinedWindPanel;
     private final TrueNorthIndicatorPanel trueNorthIndicatorPanel;
@@ -487,6 +487,12 @@ public class RaceMap extends AbstractCompositeComponent impleme
         this.setSize("100%", "100%");
     }
     
+    public void setQuickRanksDTOProvider(QuickRanksDTOProvider newQuickRanksDTOProvider) {
+        if (this.quickRanksDTOProvider != null) {
+            this.quickRanksDTOProvider.moveListernersTo(newQuickRanksDTOProvider);
+        }
+        this.quickRanksDTOProvider = newQuickRanksDTOProvider;
+    }
     /**
      * The {@link WindDTO#dampenedTrueWindFromDeg} direction if {@link #lastCombinedWindTrackInfoDTO} has a
      * {@link WindSourceType#COMBINED} source which has at least one fix recorded; null otherwise.
@@ -1403,7 +1409,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.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractQuickRanksDTOProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractQuickRanksDTOProvider.java
index 56832763b28..43a10d113c4 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractQuickRanksDTOProvider.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractQuickRanksDTOProvider.java
@@ -23,6 +23,13 @@ public abstract class AbstractQuickRanksDTOProvider implements QuickRanksDTOProv
         listeners.remove(listener);
     }
 
+    public void moveListernersTo(QuickRanksDTOProvider newProvider) {
+        for (QuickRanksListener quickRanksListener : listeners) {
+            newProvider.addQuickRanksListener(quickRanksListener);
+        }
+        listeners.clear();
+    }
+
     protected void notifyListeners(String competitorIdAsString, QuickRankDTO oldQuickRank, QuickRankDTO newQuickRank) {
         for (QuickRanksListener listener : listeners) {
             listener.rankChanged(competitorIdAsString, oldQuickRank, newQuickRank);
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);