Skip to content

Commit 13600c4

Browse files
committed
implement sending email using Java Mail
1 parent d987a9b commit 13600c4

File tree

13 files changed

+406
-1
lines changed

13 files changed

+406
-1
lines changed

pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@
4343
<groupId>org.springframework.boot</groupId>
4444
<artifactId>spring-boot-starter-security</artifactId>
4545
</dependency>
46+
<dependency>
47+
<groupId>org.springframework.boot</groupId>
48+
<artifactId>spring-boot-starter-mail</artifactId>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.springframework.boot</groupId>
52+
<artifactId>spring-boot-configuration-processor</artifactId>
53+
<optional>true</optional>
54+
</dependency>
4655
<dependency>
4756
<groupId>mysql</groupId>
4857
<artifactId>mysql-connector-java</artifactId>
@@ -56,6 +65,10 @@
5665
<artifactId>commons-io</artifactId>
5766
<version>${commons-io.version}</version>
5867
</dependency>
68+
<dependency>
69+
<groupId>org.freemarker</groupId>
70+
<artifactId>freemarker</artifactId>
71+
</dependency>
5972

6073
<dependency>
6174
<groupId>org.springframework.boot</groupId>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.taskagile.config;
2+
3+
import javax.validation.constraints.Email;
4+
import javax.validation.constraints.NotBlank;
5+
import org.springframework.boot.context.properties.ConfigurationProperties;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.validation.annotation.Validated;
8+
9+
@Configuration
10+
@ConfigurationProperties(prefix="app")
11+
@Validated
12+
public class ApplicationProperties {
13+
14+
/**
15+
* Default `from` value of emails sent out by the system
16+
*/
17+
@Email
18+
@NotBlank
19+
private String mailFrom;
20+
21+
public void setMailFrom(String mailFrom) {
22+
this.mailFrom = mailFrom;
23+
}
24+
25+
public String getMailFrom() {
26+
return mailFrom;
27+
}
28+
}
Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,59 @@
11
package com.taskagile.domain.common.mail;
22

3+
import freemarker.template.Configuration;
4+
import freemarker.template.Template;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.beans.factory.annotation.Value;
38
import org.springframework.stereotype.Component;
9+
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
10+
import org.springframework.util.Assert;
11+
12+
import java.util.HashMap;
13+
import java.util.Map;
414

515
@Component
616
public class DefaultMailManager implements MailManager {
717

18+
private final static Logger log = LoggerFactory.getLogger(DefaultMailManager.class);
19+
20+
private String mailFrom;
21+
private Mailer mailer;
22+
private Configuration configuration;
23+
24+
public DefaultMailManager(@Value("${app.mail-from}") String mailFrom,
25+
Mailer mailer,
26+
Configuration configuration) {
27+
this.mailFrom = mailFrom;
28+
this.mailer = mailer;
29+
this.configuration = configuration;
30+
}
31+
832
@Override
933
public void send(String emailAddress, String subject, String template, MessageVariable... variables) {
10-
// TODO implement this
34+
Assert.hasText(emailAddress, "Parameter `emailAddress` must not be blank");
35+
Assert.hasText(subject, "Parameter `subject` must not be blank");
36+
Assert.hasText(template, "Parameter `template` must not be blank");
37+
38+
String messageBody = createMessageBody(template, variables);
39+
Message message = new SimpleMessage(emailAddress, subject, messageBody, mailFrom);
40+
mailer.send(message);
41+
}
42+
43+
private String createMessageBody(String templateName, MessageVariable... variables) {
44+
try {
45+
Template template = configuration.getTemplate(templateName);
46+
Map<String, Object> model = new HashMap<>();
47+
if (variables != null) {
48+
for (MessageVariable variable : variables) {
49+
model.put(variable.getKey(), variable.getValue());
50+
}
51+
}
52+
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
53+
} catch (Exception e) {
54+
log.error("Failed to create message body from template `" + templateName + "`", e);
55+
return null;
56+
}
1157
}
58+
1259
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.taskagile.domain.common.mail;
2+
3+
public interface Mailer {
4+
5+
/**
6+
* Send a message
7+
*
8+
* @param message the message instance
9+
*/
10+
void send(Message message);
11+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.taskagile.domain.common.mail;
2+
3+
public interface Message {
4+
5+
/**
6+
* Get the recipient of the message
7+
*
8+
* @return recipient's email address
9+
*/
10+
String getTo();
11+
12+
/**
13+
* Get the subject of the message
14+
*
15+
* @return message's subject
16+
*/
17+
String getSubject();
18+
19+
/**
20+
* Get the body of the message
21+
*
22+
* @return the body of the message
23+
*/
24+
String getBody();
25+
26+
/**
27+
* Get the from of this message
28+
*
29+
* @return where this message is from
30+
*/
31+
String getFrom();
32+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.taskagile.domain.common.mail;
2+
3+
import java.util.Objects;
4+
5+
public class SimpleMessage implements Message {
6+
7+
private String to;
8+
private String subject;
9+
private String body;
10+
private String from;
11+
12+
public SimpleMessage(String to, String subject, String body, String from) {
13+
this.to = to;
14+
this.subject = subject;
15+
this.body = body;
16+
this.from = from;
17+
}
18+
19+
@Override
20+
public String getTo() {
21+
return to;
22+
}
23+
24+
@Override
25+
public String getSubject() {
26+
return subject;
27+
}
28+
29+
@Override
30+
public String getBody() {
31+
return body;
32+
}
33+
34+
public String getFrom() {
35+
return from;
36+
}
37+
38+
@Override
39+
public boolean equals(Object o) {
40+
if (this == o) return true;
41+
if (!(o instanceof SimpleMessage)) return false;
42+
SimpleMessage that = (SimpleMessage) o;
43+
return Objects.equals(to, that.to) &&
44+
Objects.equals(subject, that.subject) &&
45+
Objects.equals(body, that.body);
46+
}
47+
48+
@Override
49+
public int hashCode() {
50+
return Objects.hash(to, subject, body);
51+
}
52+
53+
@Override
54+
public String toString() {
55+
return "SimpleMessage{" +
56+
"to='" + to + '\'' +
57+
", subject='" + subject + '\'' +
58+
", body='" + body + '\'' +
59+
'}';
60+
}
61+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.taskagile.infrastructure.mail;
2+
3+
import com.taskagile.domain.common.mail.Mailer;
4+
import com.taskagile.domain.common.mail.Message;
5+
import org.apache.commons.lang3.StringUtils;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.mail.MailException;
9+
import org.springframework.mail.SimpleMailMessage;
10+
import org.springframework.mail.javamail.JavaMailSender;
11+
import org.springframework.scheduling.annotation.Async;
12+
import org.springframework.stereotype.Component;
13+
import org.springframework.util.Assert;
14+
15+
@Component
16+
public class AsyncMailer implements Mailer {
17+
18+
private static final Logger log = LoggerFactory.getLogger(AsyncMailer.class);
19+
20+
private JavaMailSender mailSender;
21+
22+
public AsyncMailer(JavaMailSender mailSender) {
23+
this.mailSender = mailSender;
24+
}
25+
26+
@Async
27+
@Override
28+
public void send(Message message) {
29+
Assert.notNull(message, "Parameter `message` must not be null");
30+
31+
try {
32+
SimpleMailMessage mailMessage = new SimpleMailMessage();
33+
34+
if (StringUtils.isNotBlank(message.getFrom())) {
35+
mailMessage.setFrom(message.getFrom());
36+
}
37+
if (StringUtils.isNotBlank(message.getSubject())) {
38+
mailMessage.setSubject(message.getSubject());
39+
}
40+
if (StringUtils.isNotEmpty(message.getBody())) {
41+
mailMessage.setText(message.getBody());
42+
}
43+
if (message.getTo() != null) {
44+
mailMessage.setTo(message.getTo());
45+
}
46+
47+
mailSender.send(mailMessage);
48+
} catch (MailException e) {
49+
log.error("Failed to send mail message", e);
50+
}
51+
}
52+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1+
app.mail-from[email protected]
2+
13
spring.datasource.url=jdbc:mysql://localhost:3306/task_agile?useSSL=false
24
spring.datasource.username=<username>
35
spring.datasource.password=<password>
46
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
57

8+
spring.jpa.open-in-view=false
9+
spring.jpa.hibernate.ddl-auto=none
10+
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
11+
12+
spring.freemarker.template-loader-path=classpath:/mail-templates/
13+
14+
spring.mail.host=localhost
15+
spring.mail.port=1025
16+
spring.mail.properties.mail.smtp.auth=false
17+
618
logging.level.com.taskagile=DEBUG
719
logging.level.org.springframework.security=DEBUG
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<h1>Welcome!</h1>
2+
<p>Here is your registration information:</p>
3+
<ul>
4+
<li>Username: ${user.username}</li>
5+
<li>Email Address: ${user.emailAddress}</li>
6+
</ul>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.taskagile.domain.common.mail;
2+
3+
import freemarker.template.Configuration;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
import org.junit.runner.RunWith;
7+
import org.mockito.ArgumentCaptor;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.boot.test.context.TestConfiguration;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.test.context.ActiveProfiles;
12+
import org.springframework.test.context.junit4.SpringRunner;
13+
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean;
14+
15+
import static org.junit.Assert.assertEquals;
16+
import static org.mockito.Mockito.mock;
17+
import static org.mockito.Mockito.verify;
18+
19+
@RunWith(SpringRunner.class)
20+
@ActiveProfiles("test")
21+
public class DefaultMailManagerTests {
22+
23+
@TestConfiguration
24+
static class DefaultMessageCreatorConfiguration {
25+
@Bean
26+
public FreeMarkerConfigurationFactoryBean getFreemarkerConfiguration() {
27+
FreeMarkerConfigurationFactoryBean factoryBean = new FreeMarkerConfigurationFactoryBean();
28+
factoryBean.setTemplateLoaderPath("/mail-templates/");
29+
return factoryBean;
30+
}
31+
}
32+
33+
@Autowired
34+
private Configuration configuration;
35+
private Mailer mailerMock;
36+
private DefaultMailManager instance;
37+
38+
@Before
39+
public void setUp() {
40+
mailerMock = mock(Mailer.class);
41+
instance = new DefaultMailManager("[email protected]", mailerMock, configuration);
42+
}
43+
44+
@Test(expected = IllegalArgumentException.class)
45+
public void send_nullEmailAddress_shouldFail() {
46+
instance.send(null, "Test subject", "test.ftl");
47+
}
48+
49+
@Test(expected = IllegalArgumentException.class)
50+
public void send_emptyEmailAddress_shouldFail() {
51+
instance.send("", "Test subject", "test.ftl");
52+
}
53+
54+
@Test(expected = IllegalArgumentException.class)
55+
public void send_nullSubject_shouldFail() {
56+
instance.send("[email protected]", null, "test.ftl");
57+
}
58+
59+
@Test(expected = IllegalArgumentException.class)
60+
public void send_emptySubject_shouldFail() {
61+
instance.send("[email protected]", "", "test.ftl");
62+
}
63+
64+
@Test(expected = IllegalArgumentException.class)
65+
public void send_nullTemplateName_shouldFail() {
66+
instance.send("[email protected]", "Test subject", null);
67+
}
68+
69+
@Test(expected = IllegalArgumentException.class)
70+
public void send_emptyTemplateName_shouldFail() {
71+
instance.send("[email protected]", "Test subject", "");
72+
}
73+
74+
@Test
75+
public void send_validParameters_shouldSucceed() {
76+
String to = "[email protected]";
77+
String subject = "Test subject";
78+
String templateName = "test.ftl";
79+
80+
instance.send(to, subject, templateName, MessageVariable.from("name", "test"));
81+
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
82+
verify(mailerMock).send(messageArgumentCaptor.capture());
83+
84+
Message messageSent = messageArgumentCaptor.getValue();
85+
assertEquals(to, messageSent.getTo());
86+
assertEquals(subject, messageSent.getSubject());
87+
assertEquals("[email protected]", messageSent.getFrom());
88+
assertEquals("Hello, test\n", messageSent.getBody());
89+
}
90+
}

0 commit comments

Comments
 (0)