DSL: Mail namespace & MailSendingMessageHandler

DSL:Add SMTP Mail Server

Polishing - PR Comments

DSL: Mail: add `PropertiesConfigurer` lambda

* Move `IntegrationFlowTests` to the `flows` package to avoid cross scanning for components
* Make `MailTests` as integration test and move `Mail.outboundAdapter` to the `IntegrationFlow` `@Bean` definition
This commit is contained in:
Gary Russell
2014-08-22 16:35:53 +03:00
committed by Artem Bilan
parent 325ee9a4f3
commit ba4f52d59c
9 changed files with 499 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
/*
* Copyright 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.integration.dsl.mail;
/**
* @author Gary Russell
*
*/
public class Mail {
public static MailSendingMessageHandlerSpec outboundAdapter(String host) {
return new MailSendingMessageHandlerSpec(host);
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 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.integration.dsl.mail;
import java.util.Properties;
import javax.activation.FileTypeMap;
import org.springframework.integration.dsl.core.MessageHandlerSpec;
import org.springframework.integration.dsl.support.PropertiesBuilder;
import org.springframework.integration.dsl.support.PropertiesConfigurer;
import org.springframework.integration.mail.MailSendingMessageHandler;
import org.springframework.mail.javamail.JavaMailSenderImpl;
/**
* @author Gary Russell
* @author Artem Bilan
*/
public class MailSendingMessageHandlerSpec extends
MessageHandlerSpec<MailSendingMessageHandlerSpec, MailSendingMessageHandler> {
private final JavaMailSenderImpl sender = new JavaMailSenderImpl();
MailSendingMessageHandlerSpec (String host) {
this.sender.setHost(host);
this.target = new MailSendingMessageHandler(this.sender);
}
public MailSendingMessageHandlerSpec javaMailProperties(Properties javaMailProperties) {
this.sender.setJavaMailProperties(javaMailProperties);
return this;
}
public MailSendingMessageHandlerSpec javaMailProperties(PropertiesConfigurer propertiesConfigurer) {
PropertiesBuilder properties = new PropertiesBuilder();
propertiesConfigurer.configure(properties);
return javaMailProperties(properties.get());
}
public MailSendingMessageHandlerSpec protocol(String protocol) {
this.sender.setProtocol(protocol);
return this;
}
public MailSendingMessageHandlerSpec port(int port) {
this.sender.setPort(port);
return this;
}
public MailSendingMessageHandlerSpec credentials(String username, String password) {
this.sender.setUsername(username);
this.sender.setPassword(password);
return this;
}
public MailSendingMessageHandlerSpec defaultEncoding(String defaultEncoding) {
this.sender.setDefaultEncoding(defaultEncoding);
return this;
}
public MailSendingMessageHandlerSpec defaultFileTypeMap(FileTypeMap defaultFileTypeMap) {
this.sender.setDefaultFileTypeMap(defaultFileTypeMap);
return this;
}
@Override
protected MailSendingMessageHandler doGet() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides Mail Components support for Spring Integration Java DSL.
*/
package org.springframework.integration.dsl.mail;

View File

@@ -0,0 +1,38 @@
/*
* Copyright 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.integration.dsl.support;
import java.util.Properties;
/**
* @author Gary Russell
*
*/
public class PropertiesBuilder {
private final Properties properties = new Properties();
public PropertiesBuilder put(Object key, Object value) {
properties.put(key, value);
return this;
}
public Properties get() {
return this.properties;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 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.integration.dsl.support;
/**
* @author Artem Bilan
*/
public interface PropertiesConfigurer {
void configure(PropertiesBuilder propertiesBuilder);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.dsl.test;
package org.springframework.integration.dsl.test.flows;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
@@ -86,6 +86,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
@@ -114,6 +115,8 @@ import org.springframework.integration.dsl.jms.Jms;
import org.springframework.integration.dsl.sftp.Sftp;
import org.springframework.integration.dsl.support.Pollers;
import org.springframework.integration.dsl.support.Transformers;
import org.springframework.integration.dsl.test.TestFtpServer;
import org.springframework.integration.dsl.test.TestSftpServer;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.event.core.MessagingEvent;
import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler;
@@ -1275,6 +1278,7 @@ public class IntegrationFlowTests {
}
@Configuration
@Import({TestFtpServer.class, TestSftpServer.class})
@EnableAutoConfiguration
@IntegrationComponentScan
public static class ContextConfiguration {

View File

@@ -0,0 +1,133 @@
/*
* Copyright 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.integration.dsl.test.mail;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Properties;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.mail.Mail;
import org.springframework.integration.dsl.test.mail.PoorMansMailServer.SmtpServer;
import org.springframework.integration.mail.MailHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.SocketUtils;
/**
* @author Gary Russell
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class MailTests {
private final static int port = SocketUtils.findAvailableTcpPort();
private static SmtpServer server = PoorMansMailServer.smtp(port);
@BeforeClass
public static void setup() throws InterruptedException {
int n = 0;
while (n++ < 100 && !server.isListening()) {
Thread.sleep(100);
}
assertTrue(n < 100);
}
@AfterClass
public static void tearDown() {
server.stop();
}
@Autowired
@Qualifier("sendMailChannel")
private MessageChannel sendMailChannel;
@Autowired
@Qualifier("sendMailEndpoint.handler")
private MessageHandler sendMailHandler;
@Test
public void testOutbound() throws Exception {
assertEquals("localhost", TestUtils.getPropertyValue(this.sendMailHandler, "mailSender.host"));
Properties javaMailProperties = TestUtils.getPropertyValue(this.sendMailHandler,
"mailSender.javaMailProperties", Properties.class);
assertEquals("true", javaMailProperties.getProperty("mail.debug"));
this.sendMailChannel.send(MessageBuilder.withPayload("foo")
.setHeader(MailHeaders.SUBJECT, "foo")
.setHeader(MailHeaders.FROM, "foo@bar")
.setHeader(MailHeaders.TO, "bar@baz")
.build());
int n = 0;
while (n++ < 100 && server.getMessages().size() == 0) {
Thread.sleep(100);
}
assertTrue(server.getMessages().size() > 0);
String message = server.getMessages().get(0);
assertThat(message, endsWith("foo\n"));
assertThat(message, containsString("foo@bar"));
assertThat(message, containsString("bar@baz"));
assertThat(message, containsString("user:user"));
assertThat(message, containsString("password:pw"));
}
@Configuration
@EnableIntegration
public static class ContextConfiguration {
@Bean
public IntegrationFlow sendMailFlow() {
return IntegrationFlows.from("sendMailChannel")
.handle(Mail.outboundAdapter("localhost")
.port(port)
.credentials("user", "pw")
.protocol("smtp")
.javaMailProperties(p -> p.put("mail.debug", "true")),
e -> e.id("sendMailEndpoint"))
.get();
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 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.integration.dsl.test.mail;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ServerSocketFactory;
import org.apache.sshd.common.util.Base64;
/**
* @author Gary Russell
*
*/
public class PoorMansMailServer {
public static SmtpServer smtp(int port) {
try {
return new SmtpServer(port);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public static class SmtpServer implements Runnable {
private final ServerSocket socket;
private final ExecutorService exec = Executors.newCachedThreadPool();
private final List<String> messages = new ArrayList<String>();
private volatile boolean listening;
public SmtpServer(int port) throws IOException {
this.socket = ServerSocketFactory.getDefault().createServerSocket(port);
this.listening = true;
exec.execute(this);
}
public boolean isListening() {
return listening;
}
public List<String> getMessages() {
return messages;
}
@Override
public void run() {
try {
while (!socket.isClosed()) {
Socket socket = this.socket.accept();
exec.execute(new SmtpHandler(socket));
}
}
catch (IOException e) {
this.listening = false;
}
}
public void stop() {
try {
this.socket.close();
}
catch (IOException e) {
e.printStackTrace();
}
this.exec.shutdownNow();
}
public class SmtpHandler implements Runnable {
private final Socket socket;
private BufferedWriter writer;
public SmtpHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
StringBuilder sb = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
this.writer = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()));
write("220 foo SMTP");
while (!socket.isClosed()) {
String line = reader.readLine();
if (line.contains("EHLO")) {
write("250-foo hello [0,0,0,0], foo");
write("250-AUTH LOGIN PLAIN");
write("250 OK");
}
else if (line.contains("MAIL FROM")) {
write("250 OK");
}
else if (line.contains("RCPT TO")) {
write("250 OK");
}
else if (line.contains("AUTH LOGIN")) {
write("334 VXNlcm5hbWU6");
}
else if (line.contains("dXNlcg==")) { // base64 'user'
sb.append("user:");
sb.append((new String(new Base64().decode(line.getBytes()))));
sb.append("\n");
write("334 UGFzc3dvcmQ6");
}
else if (line.contains("cHc=")) { // base64 'pw'
sb.append("password:");
sb.append((new String(new Base64().decode(line.getBytes()))));
sb.append("\n");
write("235");
}
else if (line.equals("DATA")) {
write("354");
}
else if (line.equals(".")) {
write("250");
}
else if (line.equals("QUIT")) {
write("221");
socket.close();
}
else {
sb.append(line);
sb.append("\n");
}
}
messages.add(sb.toString());
}
catch (IOException e) {
e.printStackTrace();
}
}
private void write(String str) throws IOException {
this.writer.write(str);
this.writer.write("\r\n");
this.writer.flush();
}
}
}
private PoorMansMailServer() {
}
}