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

XD-3703 : Add SSL and attachments to mail sink #1854

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ ext {
equalsverifierVersion = '1.1.3'
ftpServerVersion = '1.0.6'
apacheSshdVersion = '0.10.1'
greenmailVersion = '1.3.1b'
greenmailVersion = '1.4.1'
httpClientVersion = '4.2.5'
jcloudsVersion = '1.7.0'
oracleToolsVersion = '1.2.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@

import org.springframework.xd.module.options.spi.Mixin;
import org.springframework.xd.module.options.spi.ModuleOption;

import org.springframework.xd.module.options.spi.ProfileNamesProvider;

/**
* Captures options for the {@code mail} sink module.
*
* @author Eric Bottard
* @author Franck Marchand
*/
@Mixin(MailServerMixin.class)
public class MailSinkOptionsMetadata {
public class MailSinkOptionsMetadata implements ProfileNamesProvider {

public static final String WITH_ATTACHMENT = "with-attachment";
public static final String WITHOUT_ATTACHMENT = "without-attachment";

private String bcc = "null";

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

private String to = "null";

private boolean auth = false;

private boolean starttls = false;

private boolean ssl = false;

private String attachmentExpression;

private String attachmentFilename;

public String getAttachmentExpression() {
return attachmentExpression;
}

@ModuleOption("file uri to attach to the mail")
public void setAttachmentExpression(String attachmentExpression) {
this.attachmentExpression = attachmentExpression;
}

public String getAttachmentFilename() {
return attachmentFilename;
}

@ModuleOption("name of the attachment that will appear in the mail")
public void setAttachmentFilename(String attachmentFilename) {
this.attachmentFilename = attachmentFilename;
}

public boolean isAuth() {
return auth;
}

@ModuleOption("enable authentication for mail sending connection")
public void setAuth(boolean auth) {
this.auth = auth;
}

public boolean isSsl() {
return ssl;
}

@ModuleOption("enable ssl for mail sending connection")
public void setSsl(boolean ssl) {
this.ssl = ssl;
}

public boolean isStarttls() {
return starttls;
}

@ModuleOption("enable ttl for mail sending connection")
public void setStarttls(boolean starttls) {
this.starttls = starttls;
}

// @NotNull as a String, but the contents can be the String
// "null", which is a SpEL expression in its own right.
@NotNull
Expand Down Expand Up @@ -71,7 +130,6 @@ public String getReplyTo() {
return replyTo;
}


@NotNull
public String getSubject() {
return subject;
Expand Down Expand Up @@ -117,5 +175,10 @@ public void setTo(String to) {
this.to = to;
}

@Override
public String[] profilesToActivate() {
return new String[] { (attachmentExpression != null && attachmentFilename != null) ? WITH_ATTACHMENT : WITHOUT_ATTACHMENT };
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.xd.mail;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.mail.MailHeaders;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.messaging.Message;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.nio.file.Paths;

/**
* This transformer can handle ssl, tls and attachments for
* the mail sink.
*
* @author Franck Marchand
*/
public class MailTransformer {
public static final String MAIL_ATTACHMENT = "mail_attachment";

@Autowired
private JavaMailSenderImpl sender;

public Message<String> sendMail(final Message<String> msg) {

MimeMessage mimeMsg = sender.createMimeMessage();

String subject = (String) msg.getHeaders().get(MailHeaders.SUBJECT);
final String validSubject = subject!=null ? subject : "";
final String to = (String) msg.getHeaders().get(MailHeaders.TO);
final String cc = (String) msg.getHeaders().get(MailHeaders.CC);
final String bcc = (String) msg.getHeaders().get(MailHeaders.BCC);
final String from = (String) msg.getHeaders().get(MailHeaders.FROM);
final String replyTo = (String) msg.getHeaders().get(MailHeaders.REPLY_TO);
final String attachmentFilename = (String) msg.getHeaders().get(MailHeaders.ATTACHMENT_FILENAME);
final String attachment = (String) msg.getHeaders().get(MAIL_ATTACHMENT);

try {
sender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper mMsg = new MimeMessageHelper(mimeMessage, true);

mMsg.setTo(to);
mMsg.setFrom(from);
mMsg.setReplyTo(replyTo);
mMsg.setSubject(validSubject);

if (bcc != null)
mMsg.setBcc(bcc);
if (cc != null)
mMsg.setCc(cc);

mMsg.setText(msg.getPayload());

if (attachment != null && attachmentFilename != null) {
String[] attachments;
if(attachment.contains(";"))
attachments = StringUtils.split(attachment, ";");
else attachments = new String[] { attachment };

String[] attachmentFilenames;
if(attachmentFilename.contains(";"))
attachmentFilenames = StringUtils.split(attachmentFilename, ";");
else attachmentFilenames = new String[] { attachmentFilename };

for(int i=0; i<attachments.length;i++) {
try {
mMsg.addAttachment(attachmentFilenames[i], Paths.get(attachments[i]).toFile());
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
}
});
} catch (MailException e) {
e.printStackTrace();
}

return msg;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.xd.mail;

import com.icegreen.greenmail.util.*;
import org.junit.*;
import org.springframework.xd.dirt.server.singlenode.SingleNodeApplication;
import org.springframework.xd.dirt.test.SingleNodeIntegrationTestSupport;
import org.springframework.xd.dirt.test.SingletonModuleRegistry;
import org.springframework.xd.dirt.test.process.SingleNodeProcessingChainProducer;
import org.springframework.xd.dirt.test.process.SingleNodeProcessingChainSupport;
import org.springframework.xd.module.ModuleType;
import org.springframework.xd.test.RandomConfigurationSupport;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.Security;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

/**
* @author Franck MARCHAND
*/
public class MailSinkIntegrationTest {
public static final int TIMEOUT = 5000;
private static SingleNodeApplication application;

private static GreenMail greenMail = new GreenMail(new ServerSetup[] { ServerSetupTest.SMTPS, ServerSetupTest.SMTP, ServerSetupTest.IMAPS });


@BeforeClass
public static void setUp() {
Security.setProperty("ssl.SocketFactory.provider", DummySSLSocketFactory.class.getName());
greenMail.setUser("[email protected]", "[email protected]", "test1");
greenMail.setUser("[email protected]", "[email protected]", "test1");
greenMail.start();

new RandomConfigurationSupport();
application = new SingleNodeApplication().run();
SingleNodeIntegrationTestSupport singleNodeIntegrationTestSupport = new SingleNodeIntegrationTestSupport(application);
singleNodeIntegrationTestSupport.addModuleRegistry(new SingletonModuleRegistry(ModuleType.sink, "mail"));
}

@Test
public void testSSLMailSinkWithAttachmentsIntegration() throws IOException, InterruptedException, MessagingException {

String filePath = Paths.get("src/test/resources/attachment.txt").toAbsolutePath().toString();
String filePath2 = Paths.get("src/test/resources/attachment2.txt").toAbsolutePath().toString();
String filePath3 = Paths.get("src/test/resources/attachment3.txt").toAbsolutePath().toString();

SingleNodeProcessingChainProducer chain = SingleNodeProcessingChainSupport.chainProducer(application, "testMailSink", String.format(
"mail --host=localhost " + "--to='''[email protected]''' --from='''[email protected]''' --replyTo='''[email protected]''' "
+ "--subject='''testXD''' --port=3465 --username='[email protected]' --password='test1' " + "--ssl=true --auth=true --attachmentExpression='''%s;%s;%s''' "
+ "--attachmentFilename='''test.txt;test2.txt;test3.txt'''", filePath, filePath2, filePath3));

chain.sendPayload(filePath);

assertThat(greenMail.waitForIncomingEmail(5000, 1), is(true));
chain.destroy();

Message[] messages = greenMail.getReceivedMessages();
assertThat(messages.length, is(1));

assertThat(messages[0].getSubject(), is("testXD"));

MimeMultipart mp = (MimeMultipart)messages[0].getContent();

assertThat(mp.getCount(), is(4));
boolean allPartsArePresents = true;

for(int i=0;i<4;i++) {
try {
if(mp.getBodyPart(i) == null)
allPartsArePresents = false;
} catch (MessagingException e) {
Assert.fail();
}
}

assertThat(allPartsArePresents, is(true));
}

@Test
public void testUnsecuredMailSinkIntegration() throws IOException, InterruptedException, MessagingException {
greenMail.reset();

String filePath = Paths.get("src/test/resources/attachment.txt").toAbsolutePath().toString();
String filePath2 = Paths.get("src/test/resources/attachment2.txt").toAbsolutePath().toString();
String filePath3 = Paths.get("src/test/resources/attachment3.txt").toAbsolutePath().toString();

SingleNodeProcessingChainProducer chain = SingleNodeProcessingChainSupport.chainProducer(application, "testMailSink2", String.format(
"mail --host=localhost " + "--to='''[email protected]''' --from='''[email protected]''' --replyTo='''[email protected]''' "
+ "--subject='''testXD2''' --port=3025 --username='[email protected]' --password='test1' "
+ "--starttls=false --ssl=false --auth=true --attachmentExpression='''%s;%s;%s''' "
+ "--attachmentFilename='''test.txt;test2.txt;test3.txt'''",
filePath, filePath2,filePath3));

chain.sendPayload(filePath);

assertThat(greenMail.waitForIncomingEmail(TIMEOUT, 1), is(true));
chain.destroy();

Message[] messages = greenMail.getReceivedMessages();
assertThat(messages.length, is(1));

assertThat(messages[0].getSubject(), is("testXD2"));

MimeMultipart mp = (MimeMultipart)messages[0].getContent();

assertThat(mp.getCount(), is(4));

boolean allPartsArePresents = true;

for(int i=0;i<4;i++) {
try {
if(mp.getBodyPart(i) == null)
allPartsArePresents = false;
} catch (MessagingException e) {
Assert.fail();
}
}

assertThat(allPartsArePresents, is(true));

}

@AfterClass
public static void tearDown() {
greenMail.stop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
No Jim ! He's not dead !
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
No Jim ! He's not dead ! 2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
No Jim ! He's not dead ! 3
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
info.shortDescription = Sends incoming message as email.
options_class = org.springframework.xd.mail.MailSinkOptionsMetadata
Loading