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

Commit f8b75ce

Browse files
fmarchandkrqnf
authored andcommitted
XD-2076 & XD-2498 & XD-3076 : Add SSL and attachments to mail sink
1 parent b7b5bec commit f8b75ce

File tree

12 files changed

+392
-39
lines changed

12 files changed

+392
-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.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.integration.mail.MailHeaders;
21+
import org.springframework.mail.MailException;
22+
import org.springframework.mail.javamail.JavaMailSenderImpl;
23+
import org.springframework.mail.javamail.MimeMessageHelper;
24+
import org.springframework.mail.javamail.MimeMessagePreparator;
25+
import org.springframework.messaging.Message;
26+
import org.springframework.util.StringUtils;
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: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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.DummySSLSocketFactory;
20+
import com.icegreen.greenmail.util.GreenMail;
21+
import com.icegreen.greenmail.util.ServerSetup;
22+
import com.icegreen.greenmail.util.ServerSetupTest;
23+
import org.junit.AfterClass;
24+
import org.junit.Assert;
25+
import org.junit.BeforeClass;
26+
import org.junit.Test;
27+
import org.springframework.xd.dirt.server.singlenode.SingleNodeApplication;
28+
import org.springframework.xd.dirt.test.SingleNodeIntegrationTestSupport;
29+
import org.springframework.xd.dirt.test.SingletonModuleRegistry;
30+
import org.springframework.xd.dirt.test.process.SingleNodeProcessingChainProducer;
31+
import org.springframework.xd.dirt.test.process.SingleNodeProcessingChainSupport;
32+
import org.springframework.xd.module.ModuleType;
33+
import org.springframework.xd.test.RandomConfigurationSupport;
34+
35+
import javax.mail.Message;
36+
import javax.mail.MessagingException;
37+
import javax.mail.internet.MimeMultipart;
38+
import java.io.IOException;
39+
import java.nio.file.Paths;
40+
import java.security.Security;
41+
42+
import static org.hamcrest.MatcherAssert.assertThat;
43+
import static org.hamcrest.Matchers.is;
44+
45+
/**
46+
* @author Franck MARCHAND
47+
*/
48+
public class MailSinkIntegrationTest {
49+
public static final int TIMEOUT = 5000;
50+
private static SingleNodeApplication application;
51+
52+
private static GreenMail greenMail = new GreenMail(new ServerSetup[] { ServerSetupTest.SMTPS, ServerSetupTest.SMTP, ServerSetupTest.IMAPS });
53+
54+
@BeforeClass
55+
public static void setUp() {
56+
Security.setProperty("ssl.SocketFactory.provider", DummySSLSocketFactory.class.getName());
57+
greenMail.setUser("[email protected]", "[email protected]", "test1");
58+
greenMail.start();
59+
60+
new RandomConfigurationSupport();
61+
application = new SingleNodeApplication().run();
62+
SingleNodeIntegrationTestSupport singleNodeIntegrationTestSupport = new SingleNodeIntegrationTestSupport(application);
63+
singleNodeIntegrationTestSupport.addModuleRegistry(new SingletonModuleRegistry(ModuleType.sink, "mail"));
64+
}
65+
66+
@Test
67+
public void testSSLMailSinkWithAttachmentsIntegration() throws IOException, InterruptedException, MessagingException {
68+
69+
String filePath = Paths.get("src/test/resources/attachment.txt").toAbsolutePath().toString();
70+
String filePath2 = Paths.get("src/test/resources/attachment2.txt").toAbsolutePath().toString();
71+
String filePath3 = Paths.get("src/test/resources/attachment3.txt").toAbsolutePath().toString();
72+
73+
SingleNodeProcessingChainProducer chain = SingleNodeProcessingChainSupport.chainProducer(application, "testMailSink", String.format(
74+
"mail --host=localhost " + "--to='''[email protected]''' --from='''[email protected]''' --replyTo='''[email protected]''' "
75+
+ "--subject='''testXD''' --port=3465 --username='[email protected]' --password='test1' " + "--ssl=true --auth=true --attachmentExpression='''%s;%s;%s''' "
76+
+ "--attachmentFilename='''test.txt;test2.txt;test3.txt'''", filePath, filePath2, filePath3));
77+
78+
chain.sendPayload(filePath);
79+
80+
assertThat(greenMail.waitForIncomingEmail(TIMEOUT, 1), is(true));
81+
chain.destroy();
82+
83+
Message[] messages = greenMail.getReceivedMessages();
84+
assertThat(messages.length, is(1));
85+
86+
assertThat(messages[0].getSubject(), is("testXD"));
87+
88+
MimeMultipart mp = (MimeMultipart)messages[0].getContent();
89+
90+
assertThat(mp.getCount(), is(4));
91+
boolean allPartsArePresents = true;
92+
93+
for(int i=0;i<4;i++) {
94+
try {
95+
if(mp.getBodyPart(i) == null)
96+
allPartsArePresents = false;
97+
} catch (MessagingException e) {
98+
Assert.fail();
99+
}
100+
}
101+
102+
assertThat(allPartsArePresents, is(true));
103+
}
104+
105+
@Test
106+
public void testUnsecuredMailSinkIntegration() throws IOException, InterruptedException, MessagingException {
107+
greenMail.reset();
108+
109+
String filePath = Paths.get("src/test/resources/attachment.txt").toAbsolutePath().toString();
110+
String filePath2 = Paths.get("src/test/resources/attachment2.txt").toAbsolutePath().toString();
111+
String filePath3 = Paths.get("src/test/resources/attachment3.txt").toAbsolutePath().toString();
112+
113+
SingleNodeProcessingChainProducer chain = SingleNodeProcessingChainSupport.chainProducer(application, "testMailSink2", String.format(
114+
"mail --host=localhost " + "--to='''[email protected]''' --from='''[email protected]''' --replyTo='''[email protected]''' "
115+
+ "--subject='''testXD2''' --port=3025 --username='[email protected]' --password='test1' "
116+
+ "--starttls=false --ssl=false --auth=true --attachmentExpression='''%s;%s;%s''' "
117+
+ "--attachmentFilename='''test.txt;test2.txt;test3.txt'''",
118+
filePath, filePath2,filePath3));
119+
120+
chain.sendPayload(filePath);
121+
122+
assertThat(greenMail.waitForIncomingEmail(TIMEOUT, 1), is(true));
123+
chain.destroy();
124+
125+
Message[] messages = greenMail.getReceivedMessages();
126+
assertThat(messages.length, is(1));
127+
128+
assertThat(messages[0].getSubject(), is("testXD2"));
129+
130+
MimeMultipart mp = (MimeMultipart)messages[0].getContent();
131+
132+
assertThat(mp.getCount(), is(4));
133+
134+
boolean allPartsArePresents = true;
135+
136+
for(int i=0;i<4;i++) {
137+
try {
138+
if(mp.getBodyPart(i) == null)
139+
allPartsArePresents = false;
140+
} catch (MessagingException e) {
141+
Assert.fail();
142+
}
143+
}
144+
145+
assertThat(allPartsArePresents, is(true));
146+
147+
}
148+
149+
@AfterClass
150+
public static void tearDown() {
151+
greenMail.stop();
152+
}
153+
}
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

gradle/build-extensions.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ project('spring-xd-extension-mail') {
66
description = 'Spring XD Mail'
77
dependencies {
88
compile "org.springframework.integration:spring-integration-mail"
9+
compile "com.sun.mail:javax.mail"
910
compile project(":spring-xd-module-spi")
11+
compile project(":spring-xd-dirt")
12+
testCompile project(":spring-xd-test")
13+
testCompile "com.icegreen:greenmail:$greenmailVersion"
1014
}
1115
}
1216

gradle/wrapper/gradle-wrapper.jar

-338 Bytes
Binary file not shown.

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#Wed May 13 10:44:31 CEST 2015
1+
#Tue Nov 10 21:53:09 CET 2015
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME

0 commit comments

Comments
 (0)