Skip to content
This repository was archived by the owner on Apr 5, 2022. It is now read-only.

Commit 6845cba

Browse files
committed
XD-3703 : Add SSL and attachments to mail sink
1 parent b7b5bec commit 6845cba

File tree

16 files changed

+454
-39
lines changed

16 files changed

+454
-39
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ ext {
131131
equalsverifierVersion = '1.1.3'
132132
ftpServerVersion = '1.0.6'
133133
apacheSshdVersion = '0.10.1'
134-
greenmailVersion = '1.3.1b'
134+
greenmailVersion = '1.4.1'
135135
httpClientVersion = '4.2.5'
136136
jcloudsVersion = '1.7.0'
137137
oracleToolsVersion = '1.2.2'

extensions/spring-xd-extension-mail/src/main/java/org/springframework/xd/mail/MailSinkOptionsMetadata.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@
2020

2121
import org.springframework.xd.module.options.spi.Mixin;
2222
import org.springframework.xd.module.options.spi.ModuleOption;
23-
23+
import org.springframework.xd.module.options.spi.ProfileNamesProvider;
2424

2525
/**
2626
* Captures options for the {@code mail} sink module.
2727
*
2828
* @author Eric Bottard
29+
* @author Franck Marchand
2930
*/
3031
@Mixin(MailServerMixin.class)
31-
public class MailSinkOptionsMetadata {
32+
public class MailSinkOptionsMetadata implements ProfileNamesProvider {
33+
34+
public static final String WITH_ATTACHMENT = "with-attachment";
35+
public static final String WITHOUT_ATTACHMENT = "without-attachment";
3236

3337
private String bcc = "null";
3438

@@ -44,6 +48,61 @@ public class MailSinkOptionsMetadata {
4448

4549
private String to = "null";
4650

51+
private boolean auth = false;
52+
53+
private boolean starttls = false;
54+
55+
private boolean ssl = false;
56+
57+
private String attachmentExpression;
58+
59+
private String attachmentFilename;
60+
61+
public String getAttachmentExpression() {
62+
return attachmentExpression;
63+
}
64+
65+
@ModuleOption("file uri to attach to the mail")
66+
public void setAttachmentExpression(String attachmentExpression) {
67+
this.attachmentExpression = attachmentExpression;
68+
}
69+
70+
public String getAttachmentFilename() {
71+
return attachmentFilename;
72+
}
73+
74+
@ModuleOption("name of the attachment that will appear in the mail")
75+
public void setAttachmentFilename(String attachmentFilename) {
76+
this.attachmentFilename = attachmentFilename;
77+
}
78+
79+
public boolean isAuth() {
80+
return auth;
81+
}
82+
83+
@ModuleOption("enable authentication for mail sending connection")
84+
public void setAuth(boolean auth) {
85+
this.auth = auth;
86+
}
87+
88+
public boolean isSsl() {
89+
return ssl;
90+
}
91+
92+
@ModuleOption("enable ssl for mail sending connection")
93+
public void setSsl(boolean ssl) {
94+
this.ssl = ssl;
95+
}
96+
97+
public boolean isStarttls() {
98+
return starttls;
99+
}
100+
101+
@ModuleOption("enable ttl for mail sending connection")
102+
public void setStarttls(boolean starttls) {
103+
this.starttls = starttls;
104+
}
105+
47106
// @NotNull as a String, but the contents can be the String
48107
// "null", which is a SpEL expression in its own right.
49108
@NotNull
@@ -71,7 +130,6 @@ public String getReplyTo() {
71130
return replyTo;
72131
}
73132

74-
75133
@NotNull
76134
public String getSubject() {
77135
return subject;
@@ -117,5 +175,10 @@ public void setTo(String to) {
117175
this.to = to;
118176
}
119177

178+
@Override
179+
public String[] profilesToActivate() {
180+
return new String[] { (attachmentExpression != null && attachmentFilename != null) ? WITH_ATTACHMENT : WITHOUT_ATTACHMENT };
181+
}
182+
120183

121184
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2013-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.xd.mail;
18+
19+
import org.apache.commons.lang3.StringUtils;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.integration.mail.MailHeaders;
22+
import org.springframework.mail.MailException;
23+
import org.springframework.mail.javamail.JavaMailSenderImpl;
24+
import org.springframework.mail.javamail.MimeMessageHelper;
25+
import org.springframework.mail.javamail.MimeMessagePreparator;
26+
import org.springframework.messaging.Message;
27+
28+
import javax.mail.MessagingException;
29+
import javax.mail.internet.MimeMessage;
30+
import java.nio.file.Paths;
31+
32+
/**
33+
* This transformer can handle ssl, tls and attachments for
34+
* the mail sink.
35+
*
36+
* @author Franck Marchand
37+
*/
38+
public class MailTransformer {
39+
public static final String MAIL_ATTACHMENT = "mail_attachment";
40+
41+
@Autowired
42+
private JavaMailSenderImpl sender;
43+
44+
public Message<String> sendMail(final Message<String> msg) {
45+
46+
MimeMessage mimeMsg = sender.createMimeMessage();
47+
48+
String subject = (String) msg.getHeaders().get(MailHeaders.SUBJECT);
49+
final String validSubject = subject!=null ? subject : "";
50+
final String to = (String) msg.getHeaders().get(MailHeaders.TO);
51+
final String cc = (String) msg.getHeaders().get(MailHeaders.CC);
52+
final String bcc = (String) msg.getHeaders().get(MailHeaders.BCC);
53+
final String from = (String) msg.getHeaders().get(MailHeaders.FROM);
54+
final String replyTo = (String) msg.getHeaders().get(MailHeaders.REPLY_TO);
55+
final String attachmentFilename = (String) msg.getHeaders().get(MailHeaders.ATTACHMENT_FILENAME);
56+
final String attachment = (String) msg.getHeaders().get(MAIL_ATTACHMENT);
57+
58+
try {
59+
sender.send(new MimeMessagePreparator() {
60+
@Override
61+
public void prepare(MimeMessage mimeMessage) throws Exception {
62+
MimeMessageHelper mMsg = new MimeMessageHelper(mimeMessage, true);
63+
64+
mMsg.setTo(to);
65+
mMsg.setFrom(from);
66+
mMsg.setReplyTo(replyTo);
67+
mMsg.setSubject(validSubject);
68+
69+
if (bcc != null)
70+
mMsg.setBcc(bcc);
71+
if (cc != null)
72+
mMsg.setCc(cc);
73+
74+
mMsg.setText(msg.getPayload());
75+
76+
if (attachment != null && attachmentFilename != null) {
77+
String[] attachments;
78+
if(attachment.contains(";"))
79+
attachments = StringUtils.split(attachment, ";");
80+
else attachments = new String[] { attachment };
81+
82+
String[] attachmentFilenames;
83+
if(attachmentFilename.contains(";"))
84+
attachmentFilenames = StringUtils.split(attachmentFilename, ";");
85+
else attachmentFilenames = new String[] { attachmentFilename };
86+
87+
for(int i=0; i<attachments.length;i++) {
88+
try {
89+
mMsg.addAttachment(attachmentFilenames[i], Paths.get(attachments[i]).toFile());
90+
} catch (MessagingException e) {
91+
e.printStackTrace();
92+
}
93+
}
94+
}
95+
}
96+
});
97+
} catch (MailException e) {
98+
e.printStackTrace();
99+
}
100+
101+
return msg;
102+
}
103+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2013-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.xd.mail;
18+
19+
import com.icegreen.greenmail.util.*;
20+
import org.junit.*;
21+
import org.springframework.xd.dirt.server.singlenode.SingleNodeApplication;
22+
import org.springframework.xd.dirt.test.SingleNodeIntegrationTestSupport;
23+
import org.springframework.xd.dirt.test.SingletonModuleRegistry;
24+
import org.springframework.xd.dirt.test.process.SingleNodeProcessingChainProducer;
25+
import org.springframework.xd.dirt.test.process.SingleNodeProcessingChainSupport;
26+
import org.springframework.xd.module.ModuleType;
27+
import org.springframework.xd.test.RandomConfigurationSupport;
28+
29+
import javax.mail.Message;
30+
import javax.mail.MessagingException;
31+
import javax.mail.internet.MimeMultipart;
32+
import java.io.IOException;
33+
import java.nio.file.Paths;
34+
import java.security.Security;
35+
36+
import static org.hamcrest.MatcherAssert.assertThat;
37+
import static org.hamcrest.Matchers.is;
38+
39+
/**
40+
* @author Franck MARCHAND
41+
*/
42+
public class MailSinkIntegrationTest {
43+
public static final int TIMEOUT = 5000;
44+
private static SingleNodeApplication application;
45+
46+
private static GreenMail greenMail = new GreenMail(new ServerSetup[] { ServerSetupTest.SMTPS, ServerSetupTest.SMTP, ServerSetupTest.IMAPS });
47+
48+
49+
@BeforeClass
50+
public static void setUp() {
51+
Security.setProperty("ssl.SocketFactory.provider", DummySSLSocketFactory.class.getName());
52+
greenMail.setUser("someone@somewhere.fr", "someone@somewhere.fr", "test1");
53+
greenMail.setUser("nobody@nowhere.fr", "nobody@nowhere.fr", "test1");
54+
greenMail.start();
55+
56+
new RandomConfigurationSupport();
57+
application = new SingleNodeApplication().run();
58+
SingleNodeIntegrationTestSupport singleNodeIntegrationTestSupport = new SingleNodeIntegrationTestSupport(application);
59+
singleNodeIntegrationTestSupport.addModuleRegistry(new SingletonModuleRegistry(ModuleType.sink, "mail"));
60+
}
61+
62+
@Test
63+
public void testSSLMailSinkWithAttachmentsIntegration() throws IOException, InterruptedException, MessagingException {
64+
65+
String filePath = Paths.get("src/test/resources/attachment.txt").toAbsolutePath().toString();
66+
String filePath2 = Paths.get("src/test/resources/attachment2.txt").toAbsolutePath().toString();
67+
String filePath3 = Paths.get("src/test/resources/attachment3.txt").toAbsolutePath().toString();
68+
69+
SingleNodeProcessingChainProducer chain = SingleNodeProcessingChainSupport.chainProducer(application, "testMailSink", String.format(
70+
"mail --host=localhost " + "--to='''nobody@nowhere.fr''' --from='''someone@somewhere.fr''' --replyTo='''nobody@nowhere.fr''' "
71+
+ "--subject='''testXD''' --port=3465 --username='someone@somewhere.fr' --password='test1' " + "--ssl=true --auth=true --attachmentExpression='''%s;%s;%s''' "
72+
+ "--attachmentFilename='''test.txt;test2.txt;test3.txt'''", filePath, filePath2, filePath3));
73+
74+
chain.sendPayload(filePath);
75+
76+
assertThat(greenMail.waitForIncomingEmail(5000, 1), is(true));
77+
chain.destroy();
78+
79+
Message[] messages = greenMail.getReceivedMessages();
80+
assertThat(messages.length, is(1));
81+
82+
assertThat(messages[0].getSubject(), is("testXD"));
83+
84+
MimeMultipart mp = (MimeMultipart)messages[0].getContent();
85+
86+
assertThat(mp.getCount(), is(4));
87+
boolean allPartsArePresents = true;
88+
89+
for(int i=0;i<4;i++) {
90+
try {
91+
if(mp.getBodyPart(i) == null)
92+
allPartsArePresents = false;
93+
} catch (MessagingException e) {
94+
Assert.fail();
95+
}
96+
}
97+
98+
assertThat(allPartsArePresents, is(true));
99+
}
100+
101+
@Test
102+
public void testUnsecuredMailSinkIntegration() throws IOException, InterruptedException, MessagingException {
103+
greenMail.reset();
104+
105+
String filePath = Paths.get("src/test/resources/attachment.txt").toAbsolutePath().toString();
106+
String filePath2 = Paths.get("src/test/resources/attachment2.txt").toAbsolutePath().toString();
107+
String filePath3 = Paths.get("src/test/resources/attachment3.txt").toAbsolutePath().toString();
108+
109+
SingleNodeProcessingChainProducer chain = SingleNodeProcessingChainSupport.chainProducer(application, "testMailSink2", String.format(
110+
"mail --host=localhost " + "--to='''nobody@nowhere.fr''' --from='''nobody@nowhere.fr''' --replyTo='''nobody@nowhere.fr''' "
111+
+ "--subject='''testXD2''' --port=3025 --username='someone@somewhere.fr' --password='test1' "
112+
+ "--starttls=false --ssl=false --auth=true --attachmentExpression='''%s;%s;%s''' "
113+
+ "--attachmentFilename='''test.txt;test2.txt;test3.txt'''",
114+
filePath, filePath2,filePath3));
115+
116+
chain.sendPayload(filePath);
117+
118+
assertThat(greenMail.waitForIncomingEmail(TIMEOUT, 1), is(true));
119+
chain.destroy();
120+
121+
Message[] messages = greenMail.getReceivedMessages();
122+
assertThat(messages.length, is(1));
123+
124+
assertThat(messages[0].getSubject(), is("testXD2"));
125+
126+
MimeMultipart mp = (MimeMultipart)messages[0].getContent();
127+
128+
assertThat(mp.getCount(), is(4));
129+
130+
boolean allPartsArePresents = true;
131+
132+
for(int i=0;i<4;i++) {
133+
try {
134+
if(mp.getBodyPart(i) == null)
135+
allPartsArePresents = false;
136+
} catch (MessagingException e) {
137+
Assert.fail();
138+
}
139+
}
140+
141+
assertThat(allPartsArePresents, is(true));
142+
143+
}
144+
145+
@AfterClass
146+
public static void tearDown() {
147+
greenMail.stop();
148+
}
149+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No Jim ! He's not dead !
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No Jim ! He's not dead ! 2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No Jim ! He's not dead ! 3
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
info.shortDescription = Sends incoming message as email.
2+
options_class = org.springframework.xd.mail.MailSinkOptionsMetadata

0 commit comments

Comments
 (0)