Skip to content

Commit 8bc5466

Browse files
committed
Send email asynchronously (fix #109)
1 parent 54e1a99 commit 8bc5466

File tree

4 files changed

+55
-43
lines changed

4 files changed

+55
-43
lines changed

src/main/java/ubc/pavlab/rdp/services/EmailService.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,22 @@
99
import javax.mail.MessagingException;
1010
import javax.servlet.http.HttpServletRequest;
1111
import java.util.Locale;
12+
import java.util.concurrent.Future;
1213

1314
/**
1415
*
1516
*/
1617
public interface EmailService {
1718

18-
void sendSupportMessage( String message, String name, User user, String userAgent, MultipartFile attachment, Locale locale ) throws MessagingException;
19+
Future<Void> sendSupportMessage( String message, String name, User user, String userAgent, MultipartFile attachment, Locale locale ) throws MessagingException;
1920

20-
void sendResetTokenMessage( User user, PasswordResetToken token, Locale locale ) throws MessagingException;
21+
Future<Void> sendResetTokenMessage( User user, PasswordResetToken token, Locale locale ) throws MessagingException;
2122

22-
void sendRegistrationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException;
23+
Future<Void> sendRegistrationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException;
2324

24-
void sendContactEmailVerificationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException;
25+
Future<Void> sendContactEmailVerificationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException;
2526

26-
void sendUserRegisteredEmail( User user ) throws MessagingException;
27+
Future<Void> sendUserRegisteredEmail( User user ) throws MessagingException;
2728

28-
void sendUserGeneAccessRequest( UserGene userGene, User by, String reason ) throws MessagingException;
29+
Future<Void> sendUserGeneAccessRequest( UserGene userGene, User by, String reason ) throws MessagingException;
2930
}

src/main/java/ubc/pavlab/rdp/services/EmailServiceImpl.java

+21-19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.springframework.mail.SimpleMailMessage;
77
import org.springframework.mail.javamail.JavaMailSender;
88
import org.springframework.mail.javamail.MimeMessageHelper;
9+
import org.springframework.scheduling.annotation.Async;
10+
import org.springframework.scheduling.annotation.AsyncResult;
911
import org.springframework.stereotype.Service;
1012
import org.springframework.web.multipart.MultipartFile;
1113
import org.springframework.web.util.UriComponentsBuilder;
@@ -26,6 +28,8 @@
2628
import java.time.format.FormatStyle;
2729
import java.util.Collections;
2830
import java.util.Locale;
31+
import java.util.concurrent.CompletableFuture;
32+
import java.util.concurrent.Future;
2933

3034
/**
3135
* Created by mjacobson on 19/01/18.
@@ -43,8 +47,7 @@ public class EmailServiceImpl implements EmailService {
4347
@Autowired
4448
private MessageSource messageSource;
4549

46-
private void sendSimpleMessage( String subject, String content, InternetAddress to, InternetAddress replyTo, InternetAddress cc ) throws AddressException {
47-
50+
private Future<Void> sendSimpleMessage( String subject, String content, InternetAddress to, InternetAddress replyTo, InternetAddress cc ) throws AddressException {
4851
SimpleMailMessage email = new SimpleMailMessage();
4952

5053
email.setSubject( subject );
@@ -58,11 +61,10 @@ private void sendSimpleMessage( String subject, String content, InternetAddress
5861
email.setCc( cc.toString() );
5962
}
6063

61-
emailSender.send( email );
62-
64+
return CompletableFuture.runAsync( () -> emailSender.send( email ) );
6365
}
6466

65-
private void sendMultipartMessage( String subject, String content, InternetAddress to, InternetAddress replyTo, MultipartFile attachment ) throws MessagingException {
67+
private Future<Void> sendMultipartMessage( String subject, String content, InternetAddress to, InternetAddress replyTo, MultipartFile attachment ) throws MessagingException {
6668
MimeMessage message = emailSender.createMimeMessage();
6769
MimeMessageHelper helper = new MimeMessageHelper( message, true );
6870

@@ -76,11 +78,11 @@ private void sendMultipartMessage( String subject, String content, InternetAddre
7678

7779
helper.addAttachment( attachment.getOriginalFilename(), attachment );
7880

79-
emailSender.send( message );
81+
return CompletableFuture.runAsync( () -> emailSender.send( message ) );
8082
}
8183

8284
@Override
83-
public void sendSupportMessage( String message, String name, User user, String userAgent, MultipartFile attachment, Locale locale ) throws MessagingException {
85+
public Future<Void> sendSupportMessage( String message, String name, User user, String userAgent, MultipartFile attachment, Locale locale ) throws MessagingException {
8486
InternetAddress replyTo = user.getVerifiedContactEmail().orElseThrow( () -> new MessagingException( "Could not find a verified email address for user." ) );
8587
String shortName = messageSource.getMessage( "rdp.site.shortname", new String[]{ siteSettings.getHostUri().toString() }, Locale.getDefault() );
8688
String subject = messageSource.getMessage( "EmailService.sendSupportMessage.subject", new String[]{ shortName }, locale );
@@ -90,14 +92,14 @@ public void sendSupportMessage( String message, String name, User user, String u
9092
"Message: " + message + "\r\n" +
9193
"File Attached: " + ( attachment != null && !attachment.getOriginalFilename().equals( "" ) );
9294
if ( attachment == null ) {
93-
sendSimpleMessage( subject, content, getAdminAddress(), replyTo, null );
95+
return sendSimpleMessage( subject, content, getAdminAddress(), replyTo, null );
9496
} else {
95-
sendMultipartMessage( subject, content, getAdminAddress(), replyTo, attachment );
97+
return sendMultipartMessage( subject, content, getAdminAddress(), replyTo, attachment );
9698
}
9799
}
98100

99101
@Override
100-
public void sendResetTokenMessage( User user, PasswordResetToken token, Locale locale ) throws MessagingException {
102+
public Future<Void> sendResetTokenMessage( User user, PasswordResetToken token, Locale locale ) throws MessagingException {
101103
String url = UriComponentsBuilder.fromUri( siteSettings.getHostUri() )
102104
.path( "updatePassword" )
103105
.queryParam( "id", user.getId() )
@@ -116,11 +118,11 @@ public void sendResetTokenMessage( User user, PasswordResetToken token, Locale l
116118
String content = messageSource.getMessage( "EmailService.sendResetTokenMessage", new String[]{
117119
user.getProfile().getName(), url, dateTimeFormatter.format( token.getExpiryDate().toInstant() ) }, locale );
118120

119-
sendSimpleMessage( subject, content, to, null, null );
121+
return sendSimpleMessage( subject, content, to, null, null );
120122
}
121123

122124
@Override
123-
public void sendRegistrationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException {
125+
public Future<Void> sendRegistrationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException {
124126
String shortName = messageSource.getMessage( "rdp.site.shortname", new String[]{ siteSettings.getHostUri().toString() }, locale );
125127
String registrationWelcome = messageSource.getMessage( "rdp.site.email.registration-welcome", new String[]{ siteSettings.getHostUri().toString(), shortName }, locale );
126128
String registrationEnding = messageSource.getMessage( "rdp.site.email.registration-ending", new String[]{ siteSettings.getContactEmail() }, locale );
@@ -136,11 +138,11 @@ public void sendRegistrationMessage( User user, VerificationToken token, Locale
136138
String message = registrationWelcome + "\r\n\r\n" +
137139
messageSource.getMessage( "EmailService.sendRegistrationMessage", new String[]{ confirmationUrl }, locale ) + "\r\n\r\n" +
138140
registrationEnding;
139-
sendSimpleMessage( subject, message, recipientAddress, null, null );
141+
return sendSimpleMessage( subject, message, recipientAddress, null, null );
140142
}
141143

142144
@Override
143-
public void sendContactEmailVerificationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException {
145+
public Future<Void> sendContactEmailVerificationMessage( User user, VerificationToken token, Locale locale ) throws MessagingException {
144146
InternetAddress recipientAddress = new InternetAddress( user.getProfile().getContactEmail() );
145147
String shortName = messageSource.getMessage( "rdp.site.shortname", new String[]{ siteSettings.getHostUri().toString() }, locale );
146148
String subject = messageSource.getMessage( "EmailService.sendContactEmailVerificationMessage.subject", new String[]{ shortName }, locale );
@@ -151,21 +153,21 @@ public void sendContactEmailVerificationMessage( User user, VerificationToken to
151153
.encode()
152154
.toUriString();
153155
String message = messageSource.getMessage( "EmailService.sendContactEmailVerificationMessage", new String[]{ confirmationUrl }, locale );
154-
sendSimpleMessage( subject, message, recipientAddress, null, null );
156+
return sendSimpleMessage( subject, message, recipientAddress, null, null );
155157
}
156158

157159
@Override
158-
public void sendUserRegisteredEmail( User user ) throws MessagingException {
160+
public Future<Void> sendUserRegisteredEmail( User user ) throws MessagingException {
159161
// unfortunately, there's no way to tell the dmin locale
160162
Locale locale = Locale.getDefault();
161163
String shortname = messageSource.getMessage( "rdp.site.shortname", null, locale );
162164
String subject = messageSource.getMessage( "EmailService.sendUserRegisteredEmail.subject", new String[]{ shortname }, locale );
163165
String content = messageSource.getMessage( "EmailService.sendUserRegisteredEmail", new String[]{ user.getEmail() }, locale );
164-
sendSimpleMessage( subject, content, getAdminAddress(), null, null );
166+
return sendSimpleMessage( subject, content, getAdminAddress(), null, null );
165167
}
166168

167169
@Override
168-
public void sendUserGeneAccessRequest( UserGene userGene, User replyTo, String reason ) throws MessagingException {
170+
public Future<Void> sendUserGeneAccessRequest( UserGene userGene, User replyTo, String reason ) throws MessagingException {
169171
String viewUserUrl = UriComponentsBuilder.fromUri( siteSettings.getHostUri() )
170172
.path( "userView/{userId}" )
171173
.buildAndExpand( Collections.singletonMap( "userId", replyTo.getId() ) )
@@ -179,7 +181,7 @@ public void sendUserGeneAccessRequest( UserGene userGene, User replyTo, String r
179181
String subject = messageSource.getMessage( "EmailService.sendUserGeneAccessRequest.subject", new String[]{ shortname }, locale );
180182
String content = messageSource.getMessage( "EmailService.sendUserGeneAccessRequest",
181183
new String[]{ replyTo.getProfile().getFullName(), userGene.getSymbol(), reason, viewUserUrl }, locale );
182-
sendSimpleMessage( subject, content, to, replyToAddress, getAdminAddress() );
184+
return sendSimpleMessage( subject, content, to, replyToAddress, getAdminAddress() );
183185
}
184186

185187
private InternetAddress getAdminAddress() throws AddressException {

src/main/java/ubc/pavlab/rdp/services/LoggingEmailServiceImpl.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import javax.servlet.http.HttpServletRequest;
1616
import java.text.MessageFormat;
1717
import java.util.Locale;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.concurrent.Future;
1820

1921
/**
2022
* Email service implementation used for development that simply pastes the email content into the logs.
@@ -28,49 +30,55 @@ public class LoggingEmailServiceImpl implements EmailService {
2830
private SiteSettings siteSettings;
2931

3032
@Override
31-
public void sendSupportMessage( String message, String name, User user, String userAgent, MultipartFile attachment, Locale locale ) {
33+
public Future<Void> sendSupportMessage( String message, String name, User user, String userAgent, MultipartFile attachment, Locale locale ) {
3234
log.info( MessageFormat.format( "Support message for {0}:\n{1}", user, message ) );
35+
return CompletableFuture.completedFuture( null );
3336
}
3437

3538
@Override
36-
public void sendResetTokenMessage( User user, PasswordResetToken token, Locale locale ) {
39+
public Future<Void> sendResetTokenMessage( User user, PasswordResetToken token, Locale locale ) {
3740
String url = UriComponentsBuilder.fromUri( siteSettings.getHostUri() )
3841
.path( "updatePassword" )
3942
.queryParam( "id", user.getId() )
4043
.queryParam( "token", token.getToken() )
4144
.build().encode().toUriString();
4245
log.info( MessageFormat.format( "Reset URL for {0}: {1}", user, url ) );
46+
return CompletableFuture.completedFuture( null );
4347
}
4448

4549
@Override
46-
public void sendRegistrationMessage( User user, VerificationToken token, Locale locale ) {
50+
public Future<Void> sendRegistrationMessage( User user, VerificationToken token, Locale locale ) {
4751
String confirmationUrl = UriComponentsBuilder.fromUri( siteSettings.getHostUri() )
4852
.path( "registrationConfirm" )
4953
.queryParam( "token", token.getToken() )
5054
.build()
5155
.encode()
5256
.toUriString();
5357
log.info( MessageFormat.format( "Confirmation URL for {0}: {1}", user, confirmationUrl ) );
58+
return CompletableFuture.completedFuture( null );
5459
}
5560

5661
@Override
57-
public void sendContactEmailVerificationMessage( User user, VerificationToken token, Locale locale ) {
62+
public Future<Void> sendContactEmailVerificationMessage( User user, VerificationToken token, Locale locale ) {
5863
String confirmationUrl = UriComponentsBuilder.fromUri( siteSettings.getHostUri() )
5964
.path( "user/verify-contact-email" )
6065
.queryParam( "token", token.getToken() )
6166
.build()
6267
.encode()
6368
.toUriString();
6469
log.info( MessageFormat.format( "Contact email verification URL for {0}: {1}", user, confirmationUrl ) );
70+
return CompletableFuture.completedFuture( null );
6571
}
6672

6773
@Override
68-
public void sendUserRegisteredEmail( User user ) {
74+
public Future<Void> sendUserRegisteredEmail( User user ) {
6975
log.info( MessageFormat.format( "{0} has been registered.", user ) );
76+
return CompletableFuture.completedFuture( null );
7077
}
7178

7279
@Override
73-
public void sendUserGeneAccessRequest( UserGene userGene, User by, String reason ) {
80+
public Future<Void> sendUserGeneAccessRequest( UserGene userGene, User by, String reason ) {
7481
log.info( MessageFormat.format( "{0} has been requested by {1} for: {2}.", userGene, by, reason ) );
82+
return CompletableFuture.completedFuture( null );
7583
}
7684
}

src/test/java/ubc/pavlab/rdp/services/EmailServiceImplTest.java

+13-12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.mail.MessagingException;
2525
import java.net.URI;
2626
import java.util.Locale;
27+
import java.util.concurrent.ExecutionException;
2728

2829
import static org.assertj.core.api.Assertions.assertThat;
2930
import static org.mockito.Mockito.verify;
@@ -59,9 +60,9 @@ public void setUp() {
5960
}
6061

6162
@Test
62-
public void sendUserRegistered_thenSucceed() throws MessagingException {
63+
public void sendUserRegistered_thenSucceed() throws MessagingException, ExecutionException, InterruptedException {
6364
User user = createUser( 1 );
64-
emailService.sendUserRegisteredEmail( user );
65+
emailService.sendUserRegisteredEmail( user ).get();
6566
ArgumentCaptor<SimpleMailMessage> mailMessageCaptor = ArgumentCaptor.forClass( SimpleMailMessage.class );
6667
verify( emailSender ).send( mailMessageCaptor.capture() );
6768
assertThat( mailMessageCaptor.getValue() )
@@ -73,10 +74,10 @@ public void sendUserRegistered_thenSucceed() throws MessagingException {
7374
}
7475

7576
@Test
76-
public void sendSupportMessage_thenSucceed() throws MessagingException {
77+
public void sendSupportMessage_thenSucceed() throws MessagingException, ExecutionException, InterruptedException {
7778
User user = createUser( 1 );
7879
user.setEnabled( true );
79-
emailService.sendSupportMessage( "I need help!", "John Doe", user, "Google Chrome", null, Locale.getDefault() );
80+
emailService.sendSupportMessage( "I need help!", "John Doe", user, "Google Chrome", null, Locale.getDefault() ).get();
8081
ArgumentCaptor<SimpleMailMessage> mailMessageCaptor = ArgumentCaptor.forClass( SimpleMailMessage.class );
8182
verify( emailSender ).send( mailMessageCaptor.capture() );
8283
assertThat( mailMessageCaptor.getValue() )
@@ -107,10 +108,10 @@ public void sendResetTokenMessage_thenSucceed() throws MessagingException {
107108
}
108109

109110
@Test
110-
public void sendRegistrationMessageMessage_thenSucceed() throws MessagingException {
111+
public void sendRegistrationMessageMessage_thenSucceed() throws MessagingException, ExecutionException, InterruptedException {
111112
User user = createUser( 1 );
112113
VerificationToken token = createVerificationToken( user, "1234" );
113-
emailService.sendRegistrationMessage( user, token, Locale.getDefault() );
114+
emailService.sendRegistrationMessage( user, token, Locale.getDefault() ).get();
114115
ArgumentCaptor<SimpleMailMessage> mailMessageCaptor = ArgumentCaptor.forClass( SimpleMailMessage.class );
115116
verify( emailSender ).send( mailMessageCaptor.capture() );
116117
assertThat( mailMessageCaptor.getValue() )
@@ -122,11 +123,11 @@ public void sendRegistrationMessageMessage_thenSucceed() throws MessagingExcepti
122123
}
123124

124125
@Test
125-
public void sendContactEmailVerificationMessage_thenSucceed() throws MessagingException {
126+
public void sendContactEmailVerificationMessage_thenSucceed() throws MessagingException, ExecutionException, InterruptedException {
126127
User user = createUser( 1 );
127128
user.getProfile().setContactEmail( "[email protected]" );
128129
VerificationToken token = createContactEmailVerificationToken( user, "1234" );
129-
emailService.sendContactEmailVerificationMessage( user, token, Locale.getDefault() );
130+
emailService.sendContactEmailVerificationMessage( user, token, Locale.getDefault() ).get();
130131
ArgumentCaptor<SimpleMailMessage> mailMessageCaptor = ArgumentCaptor.forClass( SimpleMailMessage.class );
131132
verify( emailSender ).send( mailMessageCaptor.capture() );
132133
assertThat( mailMessageCaptor.getValue() )
@@ -138,11 +139,11 @@ public void sendContactEmailVerificationMessage_thenSucceed() throws MessagingEx
138139
}
139140

140141
@Test
141-
public void sendContactEmailVerificationMessage_whenTokenContainsInvalidCharacter_thenSucceed() throws MessagingException {
142+
public void sendContactEmailVerificationMessage_whenTokenContainsInvalidCharacter_thenSucceed() throws MessagingException, ExecutionException, InterruptedException {
142143
User user = createUser( 1 );
143144
user.getProfile().setContactEmail( "[email protected]" );
144145
VerificationToken token = createContactEmailVerificationToken( user, "1234+" );
145-
emailService.sendContactEmailVerificationMessage( user, token, Locale.getDefault() );
146+
emailService.sendContactEmailVerificationMessage( user, token, Locale.getDefault() ).get();
146147
ArgumentCaptor<SimpleMailMessage> mailMessageCaptor = ArgumentCaptor.forClass( SimpleMailMessage.class );
147148
verify( emailSender ).send( mailMessageCaptor.capture() );
148149
assertThat( mailMessageCaptor.getValue() )
@@ -154,15 +155,15 @@ public void sendContactEmailVerificationMessage_whenTokenContainsInvalidCharacte
154155
}
155156

156157
@Test
157-
public void sendUserGeneAccessRequest_thenSucceed() throws MessagingException {
158+
public void sendUserGeneAccessRequest_thenSucceed() throws MessagingException, ExecutionException, InterruptedException {
158159
User user = createUser( 1 );
159160
user.getProfile().setContactEmail( "[email protected]" );
160161
user.getProfile().setContactEmailVerified( true );
161162
User user2 = createUser( 2 );
162163
user2.getProfile().setContactEmail( "[email protected]" );
163164
user2.getProfile().setContactEmailVerified( true );
164165
UserGene userGene = createUserGene( 1, createGene( 1, createTaxon( 1 ) ), user2, TierType.TIER1, PrivacyLevelType.PRIVATE );
165-
emailService.sendUserGeneAccessRequest( userGene, user, "Because." );
166+
emailService.sendUserGeneAccessRequest( userGene, user, "Because." ).get();
166167
ArgumentCaptor<SimpleMailMessage> mailMessageCaptor = ArgumentCaptor.forClass( SimpleMailMessage.class );
167168
verify( emailSender ).send( mailMessageCaptor.capture() );
168169
SimpleMailMessage mailMessage = mailMessageCaptor.getValue();

0 commit comments

Comments
 (0)