001: /*
002: * Dumbster - a dummy SMTP server
003: * Copyright 2004 Jason Paul Kitchen
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package com.dumbster.smtp;
018:
019: import java.net.ServerSocket;
020: import java.net.Socket;
021: import java.util.List;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.io.BufferedReader;
025: import java.io.InputStreamReader;
026: import java.io.PrintWriter;
027: import java.io.IOException;
028:
029: /**
030: * Dummy SMTP server for testing purposes.
031: *
032: * @todo constructor allowing user to pass preinitialized ServerSocket
033: */
034: public class SimpleSmtpServer implements Runnable {
035: /**
036: * Stores all of the email received since this instance started up.
037: */
038: private List receivedMail;
039:
040: /**
041: * Default SMTP port is 25.
042: */
043: public static final int DEFAULT_SMTP_PORT = 25;
044:
045: /**
046: * Indicates whether this server is stopped or not.
047: */
048: private volatile boolean stopped = true;
049:
050: /**
051: * Handle to the server socket this server listens to.
052: */
053: private ServerSocket serverSocket;
054:
055: /**
056: * Port the server listens on - set to the default SMTP port initially.
057: */
058: private int port = DEFAULT_SMTP_PORT;
059:
060: /**
061: * Timeout listening on server socket.
062: */
063: private static final int TIMEOUT = 500;
064:
065: /**
066: * Constructor.
067: * @param port port number
068: */
069: public SimpleSmtpServer(int port) {
070: receivedMail = new ArrayList();
071: this .port = port;
072: }
073:
074: /**
075: * Main loop of the SMTP server.
076: */
077: public void run() {
078: stopped = false;
079: try {
080: try {
081: serverSocket = new ServerSocket(port);
082: serverSocket.setSoTimeout(TIMEOUT); // Block for maximum of 1.5 seconds
083: } finally {
084: synchronized (this ) {
085: // Notify when server socket has been created
086: notifyAll();
087: }
088: }
089:
090: // Server: loop until stopped
091: while (!isStopped()) {
092: // Start server socket and listen for client connections
093: Socket socket = null;
094: try {
095: socket = serverSocket.accept();
096: } catch (Exception e) {
097: if (socket != null) {
098: socket.close();
099: }
100: continue; // Non-blocking socket timeout occurred: try accept() again
101: }
102:
103: // Get the input and output streams
104: BufferedReader input = new BufferedReader(
105: new InputStreamReader(socket.getInputStream()));
106: PrintWriter out = new PrintWriter(socket
107: .getOutputStream());
108:
109: synchronized (this ) {
110: /*
111: * We synchronize over the handle method and the list update because the client call completes inside
112: * the handle method and we have to prevent the client from reading the list until we've updated it.
113: * For higher concurrency, we could just change handle to return void and update the list inside the method
114: * to limit the duration that we hold the lock.
115: */
116: List msgs = handleTransaction(out, input);
117: receivedMail.addAll(msgs);
118: }
119: socket.close();
120: }
121: } catch (Exception e) {
122: /** @todo Should throw an appropriate exception here. */
123: e.printStackTrace();
124: } finally {
125: if (serverSocket != null) {
126: try {
127: serverSocket.close();
128: } catch (IOException e) {
129: e.printStackTrace();
130: }
131: }
132: }
133: }
134:
135: /**
136: * Check if the server has been placed in a stopped state. Allows another thread to
137: * stop the server safely.
138: * @return true if the server has been sent a stop signal, false otherwise
139: */
140: public synchronized boolean isStopped() {
141: return stopped;
142: }
143:
144: /**
145: * Stops the server. Server is shutdown after processing of the current request is complete.
146: */
147: public synchronized void stop() {
148: // Mark us closed
149: stopped = true;
150: try {
151: // Kick the server accept loop
152: serverSocket.close();
153: } catch (IOException e) {
154: // Ignore
155: }
156: }
157:
158: /**
159: * Handle an SMTP transaction, i.e. all activity between initial connect and QUIT command.
160: *
161: * @param out output stream
162: * @param input input stream
163: * @return List of SmtpMessage
164: * @throws IOException
165: */
166: private List handleTransaction(PrintWriter out, BufferedReader input)
167: throws IOException {
168: // Initialize the state machine
169: SmtpState smtpState = SmtpState.CONNECT;
170: SmtpRequest smtpRequest = new SmtpRequest(
171: SmtpActionType.CONNECT, "", smtpState);
172:
173: // Execute the connection request
174: SmtpResponse smtpResponse = smtpRequest.execute();
175:
176: // Send initial response
177: sendResponse(out, smtpResponse);
178: smtpState = smtpResponse.getNextState();
179:
180: List msgList = new ArrayList();
181: SmtpMessage msg = new SmtpMessage();
182:
183: while (smtpState != SmtpState.CONNECT) {
184: String line = input.readLine();
185:
186: if (line == null) {
187: break;
188: }
189:
190: // Create request from client input and current state
191: SmtpRequest request = SmtpRequest.createRequest(line,
192: smtpState);
193: // Execute request and create response object
194: SmtpResponse response = request.execute();
195: // Move to next internal state
196: smtpState = response.getNextState();
197: // Send reponse to client
198: sendResponse(out, response);
199:
200: // Store input in message
201: String params = request.getParams();
202: msg.store(response, params);
203:
204: // If message reception is complete save it
205: if (smtpState == SmtpState.QUIT) {
206: msgList.add(msg);
207: msg = new SmtpMessage();
208: }
209: }
210:
211: return msgList;
212: }
213:
214: /**
215: * Send response to client.
216: * @param out socket output stream
217: * @param smtpResponse response object
218: */
219: private static void sendResponse(PrintWriter out,
220: SmtpResponse smtpResponse) {
221: if (smtpResponse.getCode() > 0) {
222: int code = smtpResponse.getCode();
223: String message = smtpResponse.getMessage();
224: out.print(code + " " + message + "\r\n");
225: out.flush();
226: }
227: }
228:
229: /**
230: * Get email received by this instance since start up.
231: * @return List of String
232: */
233: public synchronized Iterator getReceivedEmail() {
234: return receivedMail.iterator();
235: }
236:
237: /**
238: * Get the number of messages received.
239: * @return size of received email list
240: */
241: public synchronized int getReceivedEmailSize() {
242: return receivedMail.size();
243: }
244:
245: /**
246: * Creates an instance of SimpleSmtpServer and starts it. Will listen on the default port.
247: * @return a reference to the SMTP server
248: */
249: public static SimpleSmtpServer start() {
250: return start(DEFAULT_SMTP_PORT);
251: }
252:
253: /**
254: * Creates an instance of SimpleSmtpServer and starts it.
255: * @param port port number the server should listen to
256: * @return a reference to the SMTP server
257: */
258: public static SimpleSmtpServer start(int port) {
259: SimpleSmtpServer server = new SimpleSmtpServer(port);
260: Thread t = new Thread(server);
261: t.start();
262:
263: // Block until the server socket is created
264: synchronized (server) {
265: try {
266: server.wait();
267: } catch (InterruptedException e) {
268: // Ignore don't care.
269: }
270: }
271: return server;
272: }
273:
274: }
|