1ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair/*
2ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * Copyright 2007 the original author or authors.
3ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair *
4ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * you may not use this file except in compliance with the License.
6ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * You may obtain a copy of the License at
7ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair *
8ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair *
10ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * Unless required by applicable law or agreed to in writing, software
11ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * See the License for the specific language governing permissions and
14ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * limitations under the License.
15ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair */
16ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairpackage org.mockftpserver.core.server;
17ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
18ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.apache.log4j.Logger;
19ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.MockFtpServerException;
20ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.command.Command;
21ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.command.CommandHandler;
22ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.session.DefaultSession;
23ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.session.Session;
24ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
25ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
26ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport org.mockftpserver.core.util.Assert;
27ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
28ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.io.IOException;
29ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.net.ServerSocket;
30ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.net.Socket;
31ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.net.SocketTimeoutException;
32ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.util.HashMap;
33ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.util.Iterator;
34ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.util.Map;
35ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairimport java.util.ResourceBundle;
36ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
37ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair/**
38ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <b>StubFtpServer</b> is the top-level class for a "stub" implementation of an FTP Server,
39ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * suitable for testing FTP client code or standing in for a live FTP server. It supports
40ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * the main FTP commands by defining handlers for each of the corresponding low-level FTP
41ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * server commands (e.g. RETR, DELE, LIST). These handlers implement the {@link org.mockftpserver.core.command.CommandHandler}
42ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * interface.
43ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <p/>
44ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <b>StubFtpServer</b> works out of the box with default command handlers that return
45ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * success reply codes and empty data (for retrieved files, directory listings, etc.).
46ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * The command handler for any command can be easily configured to return custom data
47ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * or reply codes. Or it can be replaced with a custom {@link org.mockftpserver.core.command.CommandHandler}
48ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * implementation. This allows simulation of a complete range of both success and
49ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * failure scenarios. The command handlers can also be interrogated to verify command
50ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * invocation data such as command parameters and timestamps.
51ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <p/>
52ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <b>StubFtpServer</b> can be fully configured programmatically or within a Spring Framework
53ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * (http://www.springframework.org/) or similar container.
54ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <p/>
55ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <h4>Starting the StubFtpServer</h4>
56ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * Here is how to start the <b>StubFtpServer</b> with the default configuration.
57ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <pre><code>
58ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * StubFtpServer stubFtpServer = new StubFtpServer();
59ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * stubFtpServer.start();
60ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * </code></pre>
61ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <p/>
62ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <h4>Retrieving Command Handlers</h4>
63ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * You can retrieve the existing {@link org.mockftpserver.core.command.CommandHandler} defined for an FTP server command
64ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * by calling the {@link #getCommandHandler(String)} method, passing in the FTP server
65ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * command name. For example:
66ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <pre><code>
67ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
68ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * </code></pre>
69ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <p/>
70ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <h4>Replacing Command Handlers</h4>
71ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * You can replace the existing {@link org.mockftpserver.core.command.CommandHandler} defined for an FTP server command
72ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * by calling the {@link #setCommandHandler(String, org.mockftpserver.core.command.CommandHandler)} method, passing
73ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * in the FTP server command name and {@link org.mockftpserver.core.command.CommandHandler} instance. For example:
74ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <pre><code>
75ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
76ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * pwdCommandHandler.setDirectory("some/dir");
77ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
78ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * </code></pre>
79ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * You can also replace multiple command handlers at once by using the {@link #setCommandHandlers(java.util.Map)}
80ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * method. That is especially useful when configuring the server through the <b>Spring Framework</b>.
81ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <h4>FTP Command Reply Text ResourceBundle</h4>
82ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * <p/>
83ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * The default text asociated with each FTP command reply code is contained within the
84ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
85ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
86ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
87ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * completely replace the ResourceBundle file by calling the calling the
88ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * {@link #setReplyTextBaseName(String)} method.
89ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair *
90ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * @author Chris Mair
91ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair * @version $Revision$ - $Date$
92ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair */
93ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismairpublic abstract class AbstractFtpServer implements Runnable {
94ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
95ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
96ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Default basename for reply text ResourceBundle
97ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
98ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public static final String REPLY_TEXT_BASENAME = "ReplyText";
99ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private static final int DEFAULT_SERVER_CONTROL_PORT = 21;
100ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
101ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    protected Logger LOG = Logger.getLogger(getClass());
102ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
103ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    // Simple value object that holds the socket and thread for a single session
104ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private static class SessionInfo {
105ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        private Socket socket;
106ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        private Thread thread;
107ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
108ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
109ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
110ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private ServerSocket serverSocket = null;
111ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private ResourceBundle replyTextBundle;
112ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private volatile boolean terminate = false;
113ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private Map commandHandlers;
114ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private Thread serverThread;
115ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private int serverControlPort = DEFAULT_SERVER_CONTROL_PORT;
116ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private final Object startLock = new Object();
117ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
118ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    // Map of Session -> SessionInfo
119ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    private Map sessions = new HashMap();
120ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
121ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
122ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Create a new instance. Initialize the default command handlers and
123ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * reply text ResourceBundle.
124ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
125ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public AbstractFtpServer() {
126ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        replyTextBundle = ResourceBundle.getBundle(REPLY_TEXT_BASENAME);
127ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        commandHandlers = new HashMap();
128ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
129ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
130ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
131ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Start a new Thread for this server instance
132ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
133ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void start() {
134ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        serverThread = new Thread(this);
135ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
136ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        synchronized (startLock) {
137ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            try {
138ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                // Start here in case server thread runs faster than main thread.
139ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                // See https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=1925590&group_id=208647
140ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                serverThread.start();
141ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
142ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                // Wait until the server thread is initialized
143ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                startLock.wait();
144ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
145ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            catch (InterruptedException e) {
146ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                e.printStackTrace();
147ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                throw new MockFtpServerException(e);
148ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
149ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
150ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
151ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
152ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
153ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * The logic for the server thread
154ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
155ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @see Runnable#run()
156ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
157ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void run() {
158ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        try {
159ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            LOG.info("Starting the server on port " + serverControlPort);
160ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            serverSocket = serverSocketFactory.createServerSocket(serverControlPort);
161ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
162ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            // Notify to allow the start() method to finish and return
163ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            synchronized (startLock) {
164ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                startLock.notify();
165ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
166ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
167ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            serverSocket.setSoTimeout(500);
168ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            while (!terminate) {
169ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                try {
170ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    Socket clientSocket = serverSocket.accept();
171ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    LOG.info("Connection accepted from host " + clientSocket.getInetAddress());
172ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
173ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    DefaultSession session = new DefaultSession(clientSocket, commandHandlers);
174ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    Thread sessionThread = new Thread(session);
175ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    sessionThread.start();
176ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
177ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    SessionInfo sessionInfo = new SessionInfo();
178ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    sessionInfo.socket = clientSocket;
179ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    sessionInfo.thread = sessionThread;
180ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    sessions.put(session, sessionInfo);
181ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                }
182ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                catch (SocketTimeoutException socketTimeoutException) {
183ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    LOG.trace("Socket accept() timeout");
184ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                }
185ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
186ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
187ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        catch (IOException e) {
188ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            LOG.error("Error", e);
189ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
190ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        finally {
191ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
192ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            LOG.debug("Cleaning up server...");
193ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
194ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            try {
195ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                if (serverSocket != null) {
196ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    serverSocket.close();
197ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                }
198ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
199ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                for (Iterator iter = sessions.keySet().iterator(); iter.hasNext();) {
200ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    Session session = (Session) iter.next();
201ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    SessionInfo sessionInfo = (SessionInfo) sessions.get(session);
202ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    session.close();
203ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    sessionInfo.thread.join(500L);
204ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    Socket sessionSocket = sessionInfo.socket;
205ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    if (sessionSocket != null) {
206ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                        sessionSocket.close();
207ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                    }
208ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                }
209ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
210ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            catch (IOException e) {
211ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                LOG.error("Error cleaning up server", e);
212ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
213ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            catch (InterruptedException e) {
214ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                LOG.error("Error cleaning up server", e);
215ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
216ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            LOG.info("Server stopped.");
217ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
218ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
219ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
220ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
221ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Stop this server instance and wait for it to terminate.
222ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
223ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void stop() {
224ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
225ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        LOG.trace("Stopping the server...");
226ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        terminate = true;
227ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
228ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        try {
229ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            if (serverThread != null) {
230ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair                serverThread.join();
231ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            }
232ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
233ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        catch (InterruptedException e) {
234ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            e.printStackTrace();
235ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            throw new MockFtpServerException(e);
236ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
237ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
238ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
239ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
240ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Return the CommandHandler defined for the specified command name
241ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
242ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param name - the command name
243ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @return the CommandHandler defined for name
244ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
245ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public CommandHandler getCommandHandler(String name) {
246ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        return (CommandHandler) commandHandlers.get(Command.normalizeName(name));
247ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
248ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
249ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
250ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Override the default CommandHandlers with those in the specified Map of
251ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * commandName>>CommandHandler. This will only override the default CommandHandlers
252ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * for the keys in <code>commandHandlerMapping</code>. All other default CommandHandler
253ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * mappings remain unchanged.
254ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
255ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param commandHandlerMapping - the Map of commandName->CommandHandler; these override the defaults
256ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
257ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *          - if the commandHandlerMapping is null
258ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
259ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void setCommandHandlers(Map commandHandlerMapping) {
260ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        Assert.notNull(commandHandlerMapping, "commandHandlers");
261ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        for (Iterator iter = commandHandlerMapping.keySet().iterator(); iter.hasNext();) {
262ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            String commandName = (String) iter.next();
263ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            setCommandHandler(commandName, (CommandHandler) commandHandlerMapping.get(commandName));
264ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
265ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
266ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
267ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
268ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Set the CommandHandler for the specified command name. If the CommandHandler implements
269ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * the {@link org.mockftpserver.core.command.ReplyTextBundleAware} interface and its <code>replyTextBundle</code> attribute
270ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * is null, then set its <code>replyTextBundle</code> to the <code>replyTextBundle</code> of
271ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * this StubFtpServer.
272ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
273ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param commandName    - the command name to which the CommandHandler will be associated
274ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param commandHandler - the CommandHandler
275ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
276ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *          - if the commandName or commandHandler is null
277ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
278ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void setCommandHandler(String commandName, CommandHandler commandHandler) {
279ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        Assert.notNull(commandName, "commandName");
280ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        Assert.notNull(commandHandler, "commandHandler");
281ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        commandHandlers.put(Command.normalizeName(commandName), commandHandler);
282ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        initializeCommandHandler(commandHandler);
283ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
284ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
285ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
286ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Set the reply text ResourceBundle to a new ResourceBundle with the specified base name,
287ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * accessible on the CLASSPATH. See {@link java.util.ResourceBundle#getBundle(String)}.
288ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
289ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param baseName - the base name of the resource bundle, a fully qualified class name
290ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
291ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void setReplyTextBaseName(String baseName) {
292ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        replyTextBundle = ResourceBundle.getBundle(baseName);
293ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
294ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
295ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
296ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Return the ReplyText ResourceBundle. Set the bundle through the  {@link #setReplyTextBaseName(String)}  method.
297ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
298ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @return the reply text ResourceBundle
299ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
300ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public ResourceBundle getReplyTextBundle() {
301ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        return replyTextBundle;
302ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
303ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
304ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
305ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Set the port number to which the server control connection socket will bind. The default value is 21.
306ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
307ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param serverControlPort - the port number for the server control connection ServerSocket
308ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
309ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public void setServerControlPort(int serverControlPort) {
310ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        this.serverControlPort = serverControlPort;
311ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
312ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
313ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    //-------------------------------------------------------------------------
314ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    // Internal Helper Methods
315ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    //-------------------------------------------------------------------------
316ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
317ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
318ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Return true if this server is fully shutdown -- i.e., there is no active (alive) threads and
319ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * all sockets are closed. This method is intended for testing only.
320ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
321ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @return true if this server is fully shutdown
322ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
323ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public boolean isShutdown() {
324ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        boolean shutdown = !serverThread.isAlive() && serverSocket.isClosed();
325ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
326ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        for (Iterator iter = sessions.keySet().iterator(); iter.hasNext();) {
327ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            SessionInfo sessionInfo = (SessionInfo) iter.next();
328ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair            shutdown = shutdown && sessionInfo.socket.isClosed() && !sessionInfo.thread.isAlive();
329ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        }
330ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        return shutdown;
331ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
332ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
333ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
334ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Return true if this server has started -- i.e., there is an active (alive) server threads
335ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * and non-null server socket. This method is intended for testing only.
336ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
337ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @return true if this server has started
338ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
339ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    public boolean isStarted() {
340ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair        return serverThread != null && serverThread.isAlive() && serverSocket != null;
341ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    }
342ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
343ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    //------------------------------------------------------------------------------------
344ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    // Abstract method declarations
345ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    //------------------------------------------------------------------------------------
346ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
347ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    /**
348ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * Initialize a CommandHandler that has been registered to this server. What "initialization"
349ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * means is dependent on the subclass implementation.
350ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     *
351ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     * @param commandHandler - the CommandHandler to initialize
352ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair     */
353ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair    protected abstract void initializeCommandHandler(CommandHandler commandHandler);
354ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair
355ad39334d4c363c6ada5863d0bb3184f5f4699d69chrismair}