Skip to content

Commit

Permalink
extend AutoClosable and add reset method
Browse files Browse the repository at this point in the history
  • Loading branch information
kirviq committed Mar 19, 2016
1 parent 7fb4412 commit d955e8e
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 56 deletions.
136 changes: 80 additions & 56 deletions src/main/java/com/dumbster/smtp/SimpleSmtpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,46 @@

/** Dummy SMTP server for testing purposes. */
@Slf4j
public final class SimpleSmtpServer {

/** When stopping wait this long for any still ongoing transmission */
private static final int STOP_TIMEOUT = 20000;
public final class SimpleSmtpServer implements AutoCloseable {

/** Default SMTP port is 25. */
public static final int DEFAULT_SMTP_PORT = 25;

/** pick any free port. */
public static final int AUTO_SMTP_PORT = 0;

/** When stopping wait this long for any still ongoing transmission */
private static final int STOP_TIMEOUT = 20000;

private static final Pattern CRLF = Pattern.compile("\r\n");

/** Stores all of the email received since this instance started up. */
private final List<SmtpMessage> receivedMail;

/** Indicates the server thread that it should stop */
private volatile boolean stopped = false;

/** The server socket this server listens to. */
private final ServerSocket serverSocket;

/** Thread that does the work. */
private final Thread workerThread;

private static final Pattern CRLF = Pattern.compile("\r\n");
/** Indicates the server thread that it should stop */
private volatile boolean stopped = false;

/**
* Creates an instance of a started SimpleSmtpServer.
*
* @param port port number the server should listen to
* @return a reference to the running SMTP server
*/
public static SimpleSmtpServer start(int port) throws IOException {
return new SimpleSmtpServer(new ServerSocket(Math.max(port, 0)));
}

/**
* private constructor because factory method {@link #start(int)} better indicates that
* the created server is already running
* @param serverSocket socket to listen on
*/
private SimpleSmtpServer(ServerSocket serverSocket) {
this.receivedMail = new ArrayList<>();
this.serverSocket = serverSocket;
Expand All @@ -72,6 +88,62 @@ public void run() {
this.workerThread.start();
}

/**
* @return the port the server is listening on
*/
public int getPort() {
return serverSocket.getLocalPort();
}

/**
* @return list of {@link SmtpMessage}s received by since start up or last reset.
*/
public List<SmtpMessage> getReceivedEmails() {
synchronized (receivedMail) {
return Collections.unmodifiableList(new ArrayList<>(receivedMail));
}
}

/**
* forgets all received emails
*/
public void reset() {
synchronized (receivedMail) {
receivedMail.clear();
}
}

/**
* Stops the server. Server is shutdown after processing of the current request is complete.
*/
public void stop() {
if (stopped) {
return;
}
// Mark us closed
stopped = true;
try {
// Kick the server accept loop
serverSocket.close();
} catch (IOException e) {
log.warn("trouble closing the server socket", e);
}
// and block until worker is finished
try {
workerThread.join(STOP_TIMEOUT);
} catch (InterruptedException e) {
log.warn("interrupted when waiting for worker thread to finish", e);
}
}

/**
* synonym for {@link #stop()}
*/
@Override
public void close() {
stop();
}

/**
* Main loop of the SMTP server.
*/
Expand Down Expand Up @@ -107,29 +179,6 @@ private void performWork() {
}
}

/**
* Stops the server. Server is shutdown after processing of the current request is complete.
*/
public void stop() {
if (stopped) {
return;
}
// Mark us closed
stopped = true;
try {
// Kick the server accept loop
serverSocket.close();
} catch (IOException e) {
log.warn("trouble closing the server socket", e);
}
// and block until worker is finished
try {
workerThread.join(STOP_TIMEOUT);
} catch (InterruptedException e) {
log.warn("interrupted when waiting for worker thread to finish", e);
}
}

/**
* Handle an SMTP transaction, i.e. all activity between initial connect and QUIT command.
*
Expand Down Expand Up @@ -197,29 +246,4 @@ private static void sendResponse(PrintWriter out, SmtpResponse smtpResponse) {
out.flush();
}
}

/**
* Get email received by this instance since start up.
*
* @return List of String
*/
public List<SmtpMessage> getReceivedEmails() {
synchronized (receivedMail) {
return Collections.unmodifiableList(new ArrayList<>(receivedMail));
}
}

/**
* Creates an instance of SimpleSmtpServer and starts it.
*
* @param port port number the server should listen to
* @return a reference to the running SMTP server
*/
public static SimpleSmtpServer start(int port) throws IOException {
return new SimpleSmtpServer(new ServerSocket(port));
}

public int getPort() {
return serverSocket.getLocalPort();
}
}
13 changes: 13 additions & 0 deletions src/test/java/com/dumbster/smtp/SimpleSmtpServerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import static org.junit.Assert.assertTrue;

public class SimpleSmtpServerTest {

private SimpleSmtpServer server;

@Before
Expand Down Expand Up @@ -67,6 +68,18 @@ public void testSend() throws MessagingException {
assertThat(email.getHeaderValue("To"), is("[email protected]"));
}

@Test
public void testSendAndReset() throws MessagingException {
sendMessage(server.getPort(), "[email protected]", "Test", "Test Body", "[email protected]");
assertThat(server.getReceivedEmails(), hasSize(1));

server.reset();
assertThat(server.getReceivedEmails(), hasSize(0));

sendMessage(server.getPort(), "[email protected]", "Test", "Test Body", "[email protected]");
assertThat(server.getReceivedEmails(), hasSize(1));
}

@Test
public void testSendMessageWithCR() throws MessagingException {
String bodyWithCR = "\n\nKeep these pesky carriage returns\n\n";
Expand Down
2 changes: 2 additions & 0 deletions version.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Use Generics and unmodifiable collections to improve usability.
Reduce visibility of everything that's not public API.
Add option to auto-pick server port and get picked port from running server.
Update all kinds of dependencies. (junit4 and hamcrest for testing, slf4j for logging, lombok for coding)
Extend AutoClosable to enable use in try-with-resource.
Add reset method to allow using one server in multiple tests.

1.6 (2005-04-04)
Fixed threading and synchronization problems (sourceforge tracker id: 1111796). Thanks to Charles Hudak for pointing
Expand Down

0 comments on commit d955e8e

Please sign in to comment.