org.springframework.integration.test.mail.TestMailServer.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.test.mail.TestMailServer.java

Source

/*
 * Copyright 2014-2019 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
 *
 *      https://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.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.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ServerSocketFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.util.Base64Utils;

/**
 * A basic test mail server for pop3, imap,
 * Serves up a canned email message with each protocol.
 * For smtp, it handles the basic handshaking and captures
 * the pertinent data so it can be verified by a test case.
 *
 * @author Gary Russell
 * @author Artem Bilan
 *
 * @since 5.0
 *
 */
public final class TestMailServer {

    public static SmtpServer smtp(int port) {
        try {
            return new SmtpServer(port);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Pop3Server pop3(int port) {
        try {
            return new Pop3Server(port);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public static ImapServer imap(int port) {
        try {
            return new ImapServer(port);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public static class SmtpServer extends MailServer {

        SmtpServer(int port) throws IOException {
            super(port);
        }

        @Override
        protected MailHandler mailHandler(Socket socket) {
            return new SmtpHandler(socket);
        }

        class SmtpHandler extends MailHandler {

            SmtpHandler(Socket socket) {
                super(socket);
            }

            @Override
            void doRun() {
                try {
                    write("220 foo SMTP");
                    while (!socket.isClosed()) {
                        String line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        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(Base64Utils.decode(line.getBytes()))));
                            sb.append("\n");
                            write("334 UGFzc3dvcmQ6");
                        } else if (line.contains("cHc=")) { // base64 'pw'
                            sb.append("password:");
                            sb.append((new String(Base64Utils.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) {
                    LOGGER.error(IO_EXCEPTION, e);
                }
            }

        }

    }

    public static class Pop3Server extends MailServer {

        Pop3Server(int port) throws IOException {
            super(port);
        }

        @Override
        protected MailHandler mailHandler(Socket socket) {
            return new Pop3Handler(socket);
        }

        class Pop3Handler extends MailHandler {

            private static final String PLUS_OK = "+OK";

            Pop3Handler(Socket socket) {
                super(socket);
            }

            @Override
            void doRun() {
                try {
                    write("+OK POP3");
                    while (!socket.isClosed()) {
                        String line = reader.readLine();
                        if ("CAPA".equals(line)) {
                            write(PLUS_OK);
                            write("USER");
                            write(".");
                        } else if ("USER user".equals(line)) {
                            write(PLUS_OK);
                        } else if ("PASS pw".equals(line)) {
                            write(PLUS_OK);
                        } else if ("STAT".equals(line)) {
                            write("+OK 1 3");
                        } else if ("NOOP".equals(line)) {
                            write(PLUS_OK);
                        } else if ("RETR 1".equals(line)) {
                            write(PLUS_OK);
                            write(MESSAGE);
                            write(".");
                        } else if ("QUIT".equals(line)) {
                            write(PLUS_OK);
                            socket.close();
                        }
                    }
                } catch (IOException e) {
                    LOGGER.error(IO_EXCEPTION, e);
                }
            }

        }

    }

    public static class ImapServer extends MailServer {

        private volatile boolean seen;

        private volatile boolean idled;

        ImapServer(int port) throws IOException {
            super(port);
        }

        @Override
        public void resetServer() {
            super.resetServer();
            this.seen = false;
            this.idled = false;
        }

        @Override
        protected MailHandler mailHandler(Socket socket) {
            return new ImapHandler(socket);
        }

        class ImapHandler extends MailHandler {

            private static final String OK_FETCH_COMPLETED = "OK FETCH completed";

            /**
             * Time to wait while IDLE before returning a result.
             */
            private static final int IDLE_WAIT_TIME = 1000;

            ImapHandler(Socket socket) {
                super(socket);
            }

            @Override
            void doRun() {
                try {
                    write("* OK IMAP4rev1 Service Ready");
                    String idleTag = "";
                    while (!socket.isClosed()) {
                        String line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        String tag = line.substring(0, line.indexOf(' ') + 1);
                        if (line.endsWith("CAPABILITY")) {
                            write("* CAPABILITY IDLE IMAP4rev1");
                            write(tag + "OK CAPABILITY completed");
                        } else if (line.endsWith("LOGIN user pw")) {
                            write(tag + "OK LOGIN completed");
                        } else if (line.endsWith("LIST \"\" INBOX")) {
                            write("* LIST \"/\" \"INBOX\"");
                            write(tag + "OK LIST completed");
                        } else if (line.endsWith("LIST \"\" \"\"")) {
                            write("* LIST \"/\" \"\"");
                            write(tag + "OK LIST completed");
                        } else if (line.endsWith("SELECT INBOX")) {
                            write("* 1 EXISTS");
                            if (!seen) {
                                write("* 1 RECENT");
                                write("* OK [UNSEEN 1]");
                            } else {
                                write("* OK");
                            }
                            write("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)]"); // \* - user flags allowed
                            write(tag + "OK SELECT completed");
                        } else if (line.endsWith("EXAMINE INBOX")) {
                            write(tag + "OK");
                        } else if (line.endsWith("SEARCH FROM bar@baz UNSEEN ALL")) {
                            searchReply(tag);
                        } else if (line
                                .endsWith("SEARCH NOT (DELETED) NOT (SEEN) NOT (KEYWORD testSIUserFlag) ALL")) {
                            searchReply(tag);
                            assertions.add("searchWithUserFlag");
                        } else if (line.contains("FETCH 1 (ENVELOPE")) {
                            write("* 1 FETCH (RFC822.SIZE " + MESSAGE.length()
                                    + " INTERNALDATE \"27-May-2013 09:45:41 +0000\" " + "FLAGS (\\Seen) "
                                    + "ENVELOPE (\"Mon, 27 May 2013 15:14:49 +0530\" " + "\"Test Email\" "
                                    + "((\"Bar\" NIL \"bar\" \"baz\")) " // From
                                    + "((\"Bar\" NIL \"bar\" \"baz\")) " // Sender
                                    + "((\"Bar\" NIL \"bar\" \"baz\")) " // Reply To
                                    + "((\"Foo\" NIL \"foo\" \"bar\")) " // To
                                    + "((NIL NIL \"a\" \"b\") (NIL NIL \"c\" \"d\")) " // cc
                                    + "((NIL NIL \"e\" \"f\") (NIL NIL \"g\" \"h\")) " // bcc
                                    + "\"<4DA0A7E4.3010506@baz.net>\" " // In reply to
                                    + "\"<CACVnpJkAUUfa3d_-4GNZW2WpxbB39tBCHC=T0gc7hty6dOEHcA@foo.bar.com>\") " // msgid
                                    + "BODYSTRUCTURE "
                                    + "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" 1 5)))");
                            write(tag + OK_FETCH_COMPLETED);
                        } else if (line.contains("FETCH 2 (BODYSTRUCTURE)")) {
                            write("* 2 FETCH " + "BODYSTRUCTURE "
                                    + "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" 1 5)))");
                            write(tag + OK_FETCH_COMPLETED);
                        } else if (line.contains("STORE 1 +FLAGS (\\Flagged)")) {
                            write("* 1 FETCH (FLAGS (\\Flagged))");
                            write(tag + "OK STORE completed");
                        } else if (line.contains("STORE 1 +FLAGS (\\Seen)")) {
                            write("* 1 FETCH (FLAGS (\\Flagged \\Seen))");
                            write(tag + "OK STORE completed");
                            seen = true;
                        } else if (line.contains("FETCH 1 FLAGS")) {
                            write("* 1 FLAGS(\\Seen)");
                            write(tag + OK_FETCH_COMPLETED);
                        } else if (line.contains("FETCH 1 (BODY.PEEK")) {
                            write("* 1 FETCH (BODY[]<0> {" + (MESSAGE.length() + 2) + "}");
                            write(MESSAGE);
                            write(")");
                            write(tag + OK_FETCH_COMPLETED);
                        } else if (line.contains("CLOSE")) {
                            write(tag + "OK CLOSE completed");
                        } else if (line.contains("NOOP")) {
                            write(tag + "OK NOOP completed");
                        } else if (line.endsWith("STORE 1 +FLAGS (testSIUserFlag)")) {
                            write(tag + "OK STORE completed");
                            assertions.add("storeUserFlag");
                        } else if (line.endsWith("IDLE")) {
                            write("+ idling");
                            idleTag = tag;
                            if (!idled) {
                                try {
                                    Thread.sleep(IDLE_WAIT_TIME);
                                    write("* 2 EXISTS");
                                    seen = false;
                                } catch (@SuppressWarnings("unused") InterruptedException e) {
                                    Thread.currentThread().interrupt();
                                }
                            }
                            idled = true;
                        } else if (line.equals("DONE")) {
                            write(idleTag + "OK");
                        } else if (line.contains("LOGOUT")) {
                            write(tag + "OK LOGOUT completed");
                            this.socket.close();
                        }
                    }
                } catch (IOException e) {
                    LOGGER.error(IO_EXCEPTION, e);
                }
            }

            void searchReply(String tag) throws IOException {
                if (seen) {
                    write("* SEARCH");
                } else {
                    write("* SEARCH 1");
                }
                write(tag + "OK SEARCH completed");
            }

        }

    }

    public abstract static class MailServer implements Runnable {

        protected final Log LOGGER = LogFactory.getLog(getClass()); // NOSONAR

        protected static final String IO_EXCEPTION = "IOException"; // NOSONAR

        private final ServerSocket serverSocket;

        private final ExecutorService exec = Executors.newCachedThreadPool();

        protected final Set<String> assertions = new HashSet<>(); // NOSONAR protected

        protected final List<String> messages = new ArrayList<>(); // NOSONAR protected

        private volatile boolean listening;

        MailServer(int port) throws IOException {
            this.serverSocket = ServerSocketFactory.getDefault().createServerSocket(port);
            this.listening = true;
            exec.execute(this);
        }

        public int getPort() {
            return this.serverSocket.getLocalPort();
        }

        public boolean isListening() {
            return listening;
        }

        public List<String> getMessages() {
            return messages;
        }

        public void resetServer() {
            this.assertions.clear();
        }

        public boolean assertReceived(String assertion) {
            return this.assertions.contains(assertion);
        }

        @Override
        public void run() {
            try {
                while (!serverSocket.isClosed()) {
                    Socket socket = this.serverSocket.accept();
                    exec.execute(mailHandler(socket));
                }
            } catch (@SuppressWarnings("unused") IOException e) {
                this.listening = false;
            }
        }

        protected abstract MailHandler mailHandler(Socket socket);

        public void stop() {
            try {
                this.serverSocket.close();
            } catch (IOException e) {
                LOGGER.error(IO_EXCEPTION, e);
            }
            this.exec.shutdownNow();
        }

        public abstract class MailHandler implements Runnable {

            public static final String BODY = "foo\r\n";

            public static final String MESSAGE = "To: Foo <foo@bar>\r\n" + "cc: a@b, c@d\r\n" + "bcc: e@f, g@h\r\n"
                    + "From: Bar <bar@baz>\r\n" + "Subject: Test Email\r\n" + "\r\n" + BODY;

            protected final Socket socket; // NOSONAR protected

            private BufferedWriter writer;

            protected StringBuilder sb = new StringBuilder(); // NOSONAR protected

            protected BufferedReader reader; // NOSONAR protected

            MailHandler(Socket socket) {
                this.socket = socket;
            }

            @Override
            public void run() {
                try {
                    this.reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
                    this.writer = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()));
                } catch (IOException e) {
                    LOGGER.error(IO_EXCEPTION, e);
                }
                doRun();
            }

            protected void write(String str) throws IOException {
                this.writer.write(str);
                this.writer.write("\r\n");
                this.writer.flush();
            }

            abstract void doRun();

        }

    }

    private TestMailServer() {
    }

}