1abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair/*
2abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * Copyright 2007 the original author or authors.
3abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair *
4abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * Licensed under the Apache License, Version 2.0 (the "License");
5abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * you may not use this file except in compliance with the License.
6abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * You may obtain a copy of the License at
7abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair *
8abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair *      http://www.apache.org/licenses/LICENSE-2.0
9abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair *
10abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * Unless required by applicable law or agreed to in writing, software
11abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * distributed under the License is distributed on an "AS IS" BASIS,
12abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * See the License for the specific language governing permissions and
14abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * limitations under the License.
15abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair */
16abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairpackage org.mockftpserver.core.server;
17abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
18dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismairimport org.slf4j.Logger;
19dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismairimport org.slf4j.LoggerFactory;
20abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.MockFtpServerException;
21abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.command.Command;
22abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.command.CommandHandler;
23abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.session.DefaultSession;
24abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.session.Session;
25abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
26abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
27abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport org.mockftpserver.core.util.Assert;
28abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
29abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport java.io.IOException;
30b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismairimport java.net.*;
31abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport java.util.HashMap;
32abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport java.util.Iterator;
33abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport java.util.Map;
34abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairimport java.util.ResourceBundle;
35abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
36abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair/**
37c561a9b3509b5021ddb2e2f8165e903909281269chrismair * This is the abstract superclass for "mock" implementations of an FTP Server,
38abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * suitable for testing FTP client code or standing in for a live FTP server. It supports
39abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * the main FTP commands by defining handlers for each of the corresponding low-level FTP
40abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * server commands (e.g. RETR, DELE, LIST). These handlers implement the {@link org.mockftpserver.core.command.CommandHandler}
41abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * interface.
42abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * <p/>
432cfd46ef00809da42b315de79e7a457ceb40f70bchrismair * By default, mock FTP Servers bind to the server control port of 21. You can use a different server control
442cfd46ef00809da42b315de79e7a457ceb40f70bchrismair * port by setting the <code>serverControlPort</code> property. If you specify a value of <code>0</code>,
452cfd46ef00809da42b315de79e7a457ceb40f70bchrismair * then a free port number will be chosen automatically; call <code>getServerControlPort()</code> AFTER
462cfd46ef00809da42b315de79e7a457ceb40f70bchrismair * <code>start()</code> has been called to determine the actual port number being used. Using a non-default
472cfd46ef00809da42b315de79e7a457ceb40f70bchrismair * port number is usually necessary when running on Unix or some other system where that port number is
482cfd46ef00809da42b315de79e7a457ceb40f70bchrismair * already in use or cannot be bound from a user process.
49abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * <p/>
50c561a9b3509b5021ddb2e2f8165e903909281269chrismair * <h4>Command Handlers</h4>
51c561a9b3509b5021ddb2e2f8165e903909281269chrismair * You can set the existing {@link CommandHandler} defined for an FTP server command
52c561a9b3509b5021ddb2e2f8165e903909281269chrismair * by calling the {@link #setCommandHandler(String, CommandHandler)} method, passing
53c561a9b3509b5021ddb2e2f8165e903909281269chrismair * in the FTP server command name and {@link CommandHandler} instance.
54c561a9b3509b5021ddb2e2f8165e903909281269chrismair * You can also replace multiple command handlers at once by using the {@link #setCommandHandlers(Map)}
55c561a9b3509b5021ddb2e2f8165e903909281269chrismair * method. That is especially useful when configuring the server through the <b>Spring Framework</b>.
56abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * <p/>
57c561a9b3509b5021ddb2e2f8165e903909281269chrismair * You can retrieve the existing {@link CommandHandler} defined for an FTP server command by
58c561a9b3509b5021ddb2e2f8165e903909281269chrismair * calling the {@link #getCommandHandler(String)} method, passing in the FTP server command name.
59abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * <p/>
60abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * <h4>FTP Command Reply Text ResourceBundle</h4>
61abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * The default text asociated with each FTP command reply code is contained within the
62abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
63abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
64abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
65abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * completely replace the ResourceBundle file by calling the calling the
66abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * {@link #setReplyTextBaseName(String)} method.
67abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair *
68abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * @author Chris Mair
69abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair * @version $Revision$ - $Date$
70c561a9b3509b5021ddb2e2f8165e903909281269chrismair * @see org.mockftpserver.fake.FakeFtpServer
71c561a9b3509b5021ddb2e2f8165e903909281269chrismair * @see org.mockftpserver.stub.StubFtpServer
72abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair */
73abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismairpublic abstract class AbstractFtpServer implements Runnable {
74abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
75abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
76abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Default basename for reply text ResourceBundle
77abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
78abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public static final String REPLY_TEXT_BASENAME = "ReplyText";
79abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private static final int DEFAULT_SERVER_CONTROL_PORT = 21;
80abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
81dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismair    protected Logger LOG = LoggerFactory.getLogger(getClass());
82abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
83abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    // Simple value object that holds the socket and thread for a single session
84abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private static class SessionInfo {
855e9566a04364b1cad5c33001a37d4638bc8a93e6chrismair        Socket socket;
865e9566a04364b1cad5c33001a37d4638bc8a93e6chrismair        Thread thread;
87abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
88abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
89725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    protected ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
90abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private ServerSocket serverSocket = null;
91128e81688f779fe96344b5a47cc4ad29292c381dchrismair    private ResourceBundle replyTextBundle;
92abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private volatile boolean terminate = false;
93abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private Map commandHandlers;
94abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private Thread serverThread;
95abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private int serverControlPort = DEFAULT_SERVER_CONTROL_PORT;
96abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private final Object startLock = new Object();
97abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
98abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    // Map of Session -> SessionInfo
99abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    private Map sessions = new HashMap();
100abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
101abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
102abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Create a new instance. Initialize the default command handlers and
103abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * reply text ResourceBundle.
104abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
105abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public AbstractFtpServer() {
106128e81688f779fe96344b5a47cc4ad29292c381dchrismair        replyTextBundle = ResourceBundle.getBundle(REPLY_TEXT_BASENAME);
107abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        commandHandlers = new HashMap();
108abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
109abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
110abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
111abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Start a new Thread for this server instance
112abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
113abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void start() {
114abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        serverThread = new Thread(this);
115abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
116abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        synchronized (startLock) {
117abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            try {
11832e4b536bedd4220dcba8062afb5cb340a391033chrismair                // Start here in case server thread runs faster than main thread.
11932e4b536bedd4220dcba8062afb5cb340a391033chrismair                // See https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=1925590&group_id=208647
12032e4b536bedd4220dcba8062afb5cb340a391033chrismair                serverThread.start();
12132e4b536bedd4220dcba8062afb5cb340a391033chrismair
12232e4b536bedd4220dcba8062afb5cb340a391033chrismair                // Wait until the server thread is initialized
123abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                startLock.wait();
124abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
125abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            catch (InterruptedException e) {
126abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                e.printStackTrace();
127abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                throw new MockFtpServerException(e);
128abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
129abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
130abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
131abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
132abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
133abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * The logic for the server thread
134abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
135abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @see Runnable#run()
136abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
137abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void run() {
138abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        try {
139abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            LOG.info("Starting the server on port " + serverControlPort);
140abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            serverSocket = serverSocketFactory.createServerSocket(serverControlPort);
1412cfd46ef00809da42b315de79e7a457ceb40f70bchrismair            if (serverControlPort == 0) {
1422cfd46ef00809da42b315de79e7a457ceb40f70bchrismair                this.serverControlPort = serverSocket.getLocalPort();
1432cfd46ef00809da42b315de79e7a457ceb40f70bchrismair                LOG.info("Actual server port is " + this.serverControlPort);
1442cfd46ef00809da42b315de79e7a457ceb40f70bchrismair            }
145abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
146abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            // Notify to allow the start() method to finish and return
147abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            synchronized (startLock) {
148abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                startLock.notify();
149abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
150abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
151abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            while (!terminate) {
152abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                try {
153abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    Socket clientSocket = serverSocket.accept();
154abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    LOG.info("Connection accepted from host " + clientSocket.getInetAddress());
155abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
156725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair                    Session session = createSession(clientSocket);
157abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    Thread sessionThread = new Thread(session);
158abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    sessionThread.start();
159abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
160abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    SessionInfo sessionInfo = new SessionInfo();
161abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    sessionInfo.socket = clientSocket;
162abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    sessionInfo.thread = sessionThread;
163abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                    sessions.put(session, sessionInfo);
164abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                }
165b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair                catch (SocketException e) {
166b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair                    LOG.trace("Socket exception: " + e.toString());
167abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair                }
168abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
169abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
170abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        catch (IOException e) {
171abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            LOG.error("Error", e);
172abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
173abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        finally {
174abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
175abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            LOG.debug("Cleaning up server...");
176abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
177ff2c5859ff26cc343450d495b77c9c6a806211acchrismair            // Ensure that the start() method is not still blocked
178ff2c5859ff26cc343450d495b77c9c6a806211acchrismair            synchronized (startLock) {
179ff2c5859ff26cc343450d495b77c9c6a806211acchrismair                startLock.notifyAll();
180ff2c5859ff26cc343450d495b77c9c6a806211acchrismair            }
181ff2c5859ff26cc343450d495b77c9c6a806211acchrismair
182abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            try {
18335db51a87c51dc0c4bbb5bc1eaf1a283b68a44b5chrismair                if (serverSocket != null) {
18435db51a87c51dc0c4bbb5bc1eaf1a283b68a44b5chrismair                    serverSocket.close();
18535db51a87c51dc0c4bbb5bc1eaf1a283b68a44b5chrismair                }
186b6177486a2f20f9937e70571bbc557918484026cchrismair                closeSessions();
187abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
188abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            catch (IOException e) {
1892076c899605689976d8f9b3d012817e4931d535fchrismair                LOG.error("Error cleaning up server", e);
190abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
191abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            catch (InterruptedException e) {
1922076c899605689976d8f9b3d012817e4931d535fchrismair                LOG.error("Error cleaning up server", e);
193abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            }
194abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            LOG.info("Server stopped.");
1958653796de8a99b3efc5172825320e05d0e77154echrismair            terminate = false;
196abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
197abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
198abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
199abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
200abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Stop this server instance and wait for it to terminate.
201abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
202abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void stop() {
203abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
204abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        LOG.trace("Stopping the server...");
205abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        terminate = true;
206abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
207b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair        if (serverSocket != null) {
208b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair            try {
209b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair                serverSocket.close();
210b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair            } catch (IOException e) {
211b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair                throw new MockFtpServerException(e);
212b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair            }
213b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair        }
214b5c534dd0ab41438502ee18fa6f88c3f8dbbe314chrismair
215abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        try {
216df7926b99bae616fd99f6b9711279826f201f437chrismair            if (serverThread != null) {
217df7926b99bae616fd99f6b9711279826f201f437chrismair                serverThread.join();
218df7926b99bae616fd99f6b9711279826f201f437chrismair            }
219abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
220abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        catch (InterruptedException e) {
221abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            e.printStackTrace();
222abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            throw new MockFtpServerException(e);
223abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
224abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
225abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
226abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
227abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Return the CommandHandler defined for the specified command name
228abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
229abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param name - the command name
230abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @return the CommandHandler defined for name
231abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
232abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public CommandHandler getCommandHandler(String name) {
233abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        return (CommandHandler) commandHandlers.get(Command.normalizeName(name));
234abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
235abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
236abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
237abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Override the default CommandHandlers with those in the specified Map of
238abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * commandName>>CommandHandler. This will only override the default CommandHandlers
239abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * for the keys in <code>commandHandlerMapping</code>. All other default CommandHandler
240abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * mappings remain unchanged.
241abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
242abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param commandHandlerMapping - the Map of commandName->CommandHandler; these override the defaults
243abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @throws org.mockftpserver.core.util.AssertFailedException
244abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *          - if the commandHandlerMapping is null
245abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
246abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void setCommandHandlers(Map commandHandlerMapping) {
247abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        Assert.notNull(commandHandlerMapping, "commandHandlers");
248abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        for (Iterator iter = commandHandlerMapping.keySet().iterator(); iter.hasNext();) {
249abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            String commandName = (String) iter.next();
250abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            setCommandHandler(commandName, (CommandHandler) commandHandlerMapping.get(commandName));
251abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
252abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
253abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
254abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
255abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Set the CommandHandler for the specified command name. If the CommandHandler implements
256abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * the {@link org.mockftpserver.core.command.ReplyTextBundleAware} interface and its <code>replyTextBundle</code> attribute
257abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * is null, then set its <code>replyTextBundle</code> to the <code>replyTextBundle</code> of
258abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * this StubFtpServer.
259abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
260abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param commandName    - the command name to which the CommandHandler will be associated
261abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param commandHandler - the CommandHandler
262abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @throws org.mockftpserver.core.util.AssertFailedException
263abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *          - if the commandName or commandHandler is null
264abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
265abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void setCommandHandler(String commandName, CommandHandler commandHandler) {
266abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        Assert.notNull(commandName, "commandName");
267abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        Assert.notNull(commandHandler, "commandHandler");
268abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        commandHandlers.put(Command.normalizeName(commandName), commandHandler);
269abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        initializeCommandHandler(commandHandler);
270abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
271abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
272abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
273abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Set the reply text ResourceBundle to a new ResourceBundle with the specified base name,
274abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * accessible on the CLASSPATH. See {@link java.util.ResourceBundle#getBundle(String)}.
275abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
276abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param baseName - the base name of the resource bundle, a fully qualified class name
277abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
278abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void setReplyTextBaseName(String baseName) {
279abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        replyTextBundle = ResourceBundle.getBundle(baseName);
280abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
281abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
282abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
283128e81688f779fe96344b5a47cc4ad29292c381dchrismair     * Return the ReplyText ResourceBundle. Set the bundle through the  {@link #setReplyTextBaseName(String)}  method.
284128e81688f779fe96344b5a47cc4ad29292c381dchrismair     *
285128e81688f779fe96344b5a47cc4ad29292c381dchrismair     * @return the reply text ResourceBundle
286128e81688f779fe96344b5a47cc4ad29292c381dchrismair     */
287128e81688f779fe96344b5a47cc4ad29292c381dchrismair    public ResourceBundle getReplyTextBundle() {
288128e81688f779fe96344b5a47cc4ad29292c381dchrismair        return replyTextBundle;
289128e81688f779fe96344b5a47cc4ad29292c381dchrismair    }
290128e81688f779fe96344b5a47cc4ad29292c381dchrismair
291128e81688f779fe96344b5a47cc4ad29292c381dchrismair    /**
292abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Set the port number to which the server control connection socket will bind. The default value is 21.
293abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
294abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param serverControlPort - the port number for the server control connection ServerSocket
295abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
296abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public void setServerControlPort(int serverControlPort) {
297abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        this.serverControlPort = serverControlPort;
298abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
299abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
300abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
301a09d6b7f15be8099d68a00e4f320c97185318317chrismair     * Return the port number to which the server control connection socket will bind. The default value is 21.
302a09d6b7f15be8099d68a00e4f320c97185318317chrismair     *
303a09d6b7f15be8099d68a00e4f320c97185318317chrismair     * @return the port number for the server control connection ServerSocket
304a09d6b7f15be8099d68a00e4f320c97185318317chrismair     */
305a09d6b7f15be8099d68a00e4f320c97185318317chrismair    public int getServerControlPort() {
306a09d6b7f15be8099d68a00e4f320c97185318317chrismair        return serverControlPort;
307a09d6b7f15be8099d68a00e4f320c97185318317chrismair    }
308a09d6b7f15be8099d68a00e4f320c97185318317chrismair
309a09d6b7f15be8099d68a00e4f320c97185318317chrismair    /**
310abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Return true if this server is fully shutdown -- i.e., there is no active (alive) threads and
311abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * all sockets are closed. This method is intended for testing only.
312abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
313abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @return true if this server is fully shutdown
314abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
315abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public boolean isShutdown() {
316abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        boolean shutdown = !serverThread.isAlive() && serverSocket.isClosed();
317abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
318f2152557999c6eeca3b2018166ddad4c015ddd89chrismair        for (Iterator iter = sessions.values().iterator(); iter.hasNext();) {
319abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            SessionInfo sessionInfo = (SessionInfo) iter.next();
320abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair            shutdown = shutdown && sessionInfo.socket.isClosed() && !sessionInfo.thread.isAlive();
321abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        }
322abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        return shutdown;
323abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
324abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
325abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
326abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Return true if this server has started -- i.e., there is an active (alive) server threads
327abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * and non-null server socket. This method is intended for testing only.
328abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
329abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @return true if this server has started
330abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
331abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    public boolean isStarted() {
332abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair        return serverThread != null && serverThread.isAlive() && serverSocket != null;
333abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    }
334abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
335b6177486a2f20f9937e70571bbc557918484026cchrismair    //-------------------------------------------------------------------------
336b6177486a2f20f9937e70571bbc557918484026cchrismair    // Internal Helper Methods
337b6177486a2f20f9937e70571bbc557918484026cchrismair    //-------------------------------------------------------------------------
338b6177486a2f20f9937e70571bbc557918484026cchrismair
339725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    /**
340725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair     * Create a new Session instance for the specified client Socket
341725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair     *
342725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair     * @param clientSocket - the Socket associated with the client
343725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair     * @return a Session
344725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair     */
345725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    protected Session createSession(Socket clientSocket) {
346725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair        return new DefaultSession(clientSocket, commandHandlers);
347725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    }
348725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair
349b6177486a2f20f9937e70571bbc557918484026cchrismair    private void closeSessions() throws InterruptedException, IOException {
350b6177486a2f20f9937e70571bbc557918484026cchrismair        for (Iterator iter = sessions.entrySet().iterator(); iter.hasNext();) {
351b6177486a2f20f9937e70571bbc557918484026cchrismair            Map.Entry entry = (Map.Entry) iter.next();
352b6177486a2f20f9937e70571bbc557918484026cchrismair            Session session = (Session) entry.getKey();
353b6177486a2f20f9937e70571bbc557918484026cchrismair            SessionInfo sessionInfo = (SessionInfo) entry.getValue();
354b6177486a2f20f9937e70571bbc557918484026cchrismair            session.close();
355b6177486a2f20f9937e70571bbc557918484026cchrismair            sessionInfo.thread.join(500L);
356b6177486a2f20f9937e70571bbc557918484026cchrismair            Socket sessionSocket = sessionInfo.socket;
357b6177486a2f20f9937e70571bbc557918484026cchrismair            if (sessionSocket != null) {
358b6177486a2f20f9937e70571bbc557918484026cchrismair                sessionSocket.close();
359b6177486a2f20f9937e70571bbc557918484026cchrismair            }
360b6177486a2f20f9937e70571bbc557918484026cchrismair        }
361b6177486a2f20f9937e70571bbc557918484026cchrismair    }
362b6177486a2f20f9937e70571bbc557918484026cchrismair
363abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    //------------------------------------------------------------------------------------
364abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    // Abstract method declarations
365abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    //------------------------------------------------------------------------------------
366abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
367abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    /**
368abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * Initialize a CommandHandler that has been registered to this server. What "initialization"
369abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * means is dependent on the subclass implementation.
370abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     *
371abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     * @param commandHandler - the CommandHandler to initialize
372abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair     */
373abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair    protected abstract void initializeCommandHandler(CommandHandler commandHandler);
374abc66ab652b34d39ea5a00a75b1d7c7cc157a84fchrismair
375dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismair}
376