19d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair/*
29d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Copyright 2007 the original author or authors.
39d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
49d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Licensed under the Apache License, Version 2.0 (the "License");
59d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * you may not use this file except in compliance with the License.
69d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * You may obtain a copy of the License at
79d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
89d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *      http://www.apache.org/licenses/LICENSE-2.0
99d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Unless required by applicable law or agreed to in writing, software
119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * distributed under the License is distributed on an "AS IS" BASIS,
129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * See the License for the specific language governing permissions and
149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * limitations under the License.
159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair */
169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairpackage org.mockftpserver.stub;
179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.IOException;
199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.ServerSocket;
209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.Socket;
219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.SocketTimeoutException;
229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.HashMap;
239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.Iterator;
249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.Map;
259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.ResourceBundle;
269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.apache.log4j.Logger;
289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.MockFtpServerException;
299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.Command;
309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.CommandHandler;
319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.CommandNames;
329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.ReplyTextBundleAware;
339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.ReplyTextBundleUtil;
349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.session.DefaultSession;
359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.session.Session;
369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.util.Assert;
399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.util.AssertFailedException;
409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.AborCommandHandler;
419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.AcctCommandHandler;
429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.AlloCommandHandler;
439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.AppeCommandHandler;
449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.CdupCommandHandler;
459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.ConnectCommandHandler;
469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.CwdCommandHandler;
479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.DeleCommandHandler;
489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.HelpCommandHandler;
499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.ListCommandHandler;
509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.MkdCommandHandler;
519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.ModeCommandHandler;
529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.NlstCommandHandler;
539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.NoopCommandHandler;
549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.PassCommandHandler;
559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.PasvCommandHandler;
569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.PortCommandHandler;
579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.PwdCommandHandler;
589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.QuitCommandHandler;
599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.ReinCommandHandler;
609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.RestCommandHandler;
619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.RetrCommandHandler;
629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.RmdCommandHandler;
639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.RnfrCommandHandler;
649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.RntoCommandHandler;
659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.SiteCommandHandler;
669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.SmntCommandHandler;
679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.StatCommandHandler;
689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.StorCommandHandler;
699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.StouCommandHandler;
709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.StruCommandHandler;
719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.SystCommandHandler;
729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.TypeCommandHandler;
739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.stub.command.UserCommandHandler;
749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair/**
769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <b>StubFtpServer</b> is the top-level class for a "stub" implementation of an FTP Server,
779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * suitable for testing FTP client code or standing in for a live FTP server. It supports
789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * the main FTP commands by defining handlers for each of the corresponding low-level FTP
799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * server commands (e.g. RETR, DELE, LIST). These handlers implement the {@link CommandHandler}
809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * interface.
819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <p>
829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <b>StubFtpServer</b> works out of the box with default command handlers that return
839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * success reply codes and empty data (for retrieved files, directory listings, etc.).
849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * The command handler for any command can be easily configured to return custom data
859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * or reply codes. Or it can be replaced with a custom {@link CommandHandler}
869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * implementation. This allows simulation of a complete range of both success and
879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * failure scenarios. The command handlers can also be interrogated to verify command
889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * invocation data such as command parameters and timestamps.
899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <p>
909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <b>StubFtpServer</b> can be fully configured programmatically or within a Spring Framework
919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * ({@link http://www.springframework.org/}) or similar container.
929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <p>
939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <h4>Starting the StubFtpServer</h4>
949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Here is how to start the <b>StubFtpServer</b> with the default configuration.
959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <pre><code>
969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * StubFtpServer stubFtpServer = new StubFtpServer();
979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * stubFtpServer.start();
989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * </code></pre>
999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <p>
1009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <h4>Retrieving Command Handlers</h4>
1019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * You can retrieve the existing {@link CommandHandler} defined for an FTP server command
1029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * by calling the {@link #getCommandHandler(String)} method, passing in the FTP server
1039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * command name. For example:
1049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <pre><code>
1059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
1069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * </code></pre>
1079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <p>
1089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <h4>Replacing Command Handlers</h4>
1099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * You can replace the existing {@link CommandHandler} defined for an FTP server command
1109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * by calling the {@link #setCommandHandler(String, CommandHandler)} method, passing
1119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * in the FTP server command name and {@link CommandHandler} instance. For example:
1129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <pre><code>
1139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
1149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * pwdCommandHandler.setDirectory("some/dir");
1159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
1169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * </code></pre>
1179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * You can also replace multiple command handlers at once by using the {@link #setCommandHandlers(Map)}
1189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * method. That is especially useful when configuring the server through the <b>Spring Framework</b>.
1199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <h4>FTP Command Reply Text ResourceBundle</h4>
1209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * <p>
1219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * The default text asociated with each FTP command reply code is contained within the
1229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
1239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
1249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
1259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * completely replace the ResourceBundle file by calling the calling the
1269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * {@link #setReplyTextBaseName(String)} method.
1279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
1289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * @version $Revision$ - $Date$
1299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
1309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * @author Chris Mair
1319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair */
1329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairpublic final class StubFtpServer implements Runnable {
1339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /** Default basename for reply text ResourceBundle */
1359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public static final String REPLY_TEXT_BASENAME = "ReplyText";
1369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private static final int DEFAULT_SERVER_CONTROL_PORT = 21;
1379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private static Logger LOG = Logger.getLogger(StubFtpServer.class);
1399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    // Simple value object that holds the socket and thread for a single session
1419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private static class SessionInfo {
1429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        private Socket socket;
1439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        private Thread thread;
1449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
1459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
1479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private ServerSocket serverSocket = null;
1489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    ResourceBundle replyTextBundle;             // non-private for testing only
1499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private volatile boolean terminate = false;
1509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Map commandHandlers;
1519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Thread serverThread;
1529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private int serverControlPort = DEFAULT_SERVER_CONTROL_PORT;
1539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Object startLock = new Object();
1549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    // Map of Session -> SessionInfo
1569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Map sessions = new HashMap();
1579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
1599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Create a new instance. Initialize the default command handlers and
1609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * reply text ResourceBundle.
1619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
1629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public StubFtpServer() {
1639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        replyTextBundle = ResourceBundle.getBundle(REPLY_TEXT_BASENAME);
1649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        commandHandlers = new HashMap();
1669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
1689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        // Initialize the default CommandHandler mappings
1709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.ABOR, new AborCommandHandler());
1719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.ACCT, new AcctCommandHandler());
1729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.ALLO, new AlloCommandHandler());
1739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.APPE, new AppeCommandHandler());
1749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.PWD, pwdCommandHandler);            // same as XPWD
1759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.CONNECT, new ConnectCommandHandler());
1769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.CWD, new CwdCommandHandler());
1779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.CDUP, new CdupCommandHandler());
1789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.DELE, new DeleCommandHandler());
1799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.HELP, new HelpCommandHandler());
1809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.LIST, new ListCommandHandler());
1819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.MKD, new MkdCommandHandler());
1829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.MODE, new ModeCommandHandler());
1839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.NOOP, new NoopCommandHandler());
1849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.NLST, new NlstCommandHandler());
1859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.PASS, new PassCommandHandler());
1869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.PASV, new PasvCommandHandler());
1879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.PORT, new PortCommandHandler());
1889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.RETR, new RetrCommandHandler());
1899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.QUIT, new QuitCommandHandler());
1909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.REIN, new ReinCommandHandler());
1919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.REST, new RestCommandHandler());
1929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.RMD, new RmdCommandHandler());
1939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.RNFR, new RnfrCommandHandler());
1949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.RNTO, new RntoCommandHandler());
1959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.SITE, new SiteCommandHandler());
1969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.SMNT, new SmntCommandHandler());
1979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.STAT, new StatCommandHandler());
1989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.STOR, new StorCommandHandler());
1999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.STOU, new StouCommandHandler());
2009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.STRU, new StruCommandHandler());
2019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.SYST, new SystCommandHandler());
2029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.TYPE, new TypeCommandHandler());
2039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.USER, new UserCommandHandler());
2049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        setCommandHandler(CommandNames.XPWD, pwdCommandHandler);           // same as PWD
2059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Start a new Thread for this server instance
2099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void start() {
2119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        serverThread = new Thread(this);
2129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        serverThread.start();
2139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        // Wait until the thread is initialized
2159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        synchronized(startLock){
2169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            try {
2179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                startLock.wait();
2189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            catch (InterruptedException e) {
2209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                e.printStackTrace();
2219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                throw new MockFtpServerException(e);
2229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * The logic for the server thread
2289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see java.lang.Runnable#run()
2299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void run() {
2319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
2329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.info("Starting the server on port " + serverControlPort);
2339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            serverSocket = serverSocketFactory.createServerSocket(serverControlPort);
2349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            // Notify to allow the start() method to finish and return
2369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            synchronized(startLock) {
2379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                startLock.notify();
2389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            serverSocket.setSoTimeout(500);
2419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            while(!terminate) {
2429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                try {
2439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    Socket clientSocket = serverSocket.accept();
2449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    LOG.info("Connection accepted from host " + clientSocket.getInetAddress());
2459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    DefaultSession session = new DefaultSession(clientSocket, commandHandlers);
2479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    Thread sessionThread = new Thread(session);
2489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    sessionThread.start();
2499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    SessionInfo sessionInfo = new SessionInfo();
2519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    sessionInfo.socket = clientSocket;
2529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    sessionInfo.thread = sessionThread;
2539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    sessions.put(session, sessionInfo);
2549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                catch(SocketTimeoutException socketTimeoutException) {
2569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    LOG.trace("Socket accept() timeout");
2579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
2619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.error("Error", e);
2629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        finally {
2649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.debug("Cleaning up server...");
2669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            try {
2689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                serverSocket.close();
2699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                for (Iterator iter = sessions.keySet().iterator(); iter.hasNext();) {
2719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    Session session = (Session) iter.next();
2729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    SessionInfo sessionInfo = (SessionInfo) sessions.get(session);
2739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    session.close();
2749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    sessionInfo.thread.join(500L);
2759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    Socket sessionSocket = (Socket) sessionInfo.socket;
2769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    if (sessionSocket != null) {
2779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                        sessionSocket.close();
2789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    }
2799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            catch (IOException e) {
2829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                e.printStackTrace();
2839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                throw new MockFtpServerException(e);
2849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            catch (InterruptedException e) {
2869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                e.printStackTrace();
2879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                throw new MockFtpServerException(e);
2889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.info("Server stopped.");
2909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Stop this server instance and wait for it to terminate.
2959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void stop() {
2979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        LOG.trace("Stopping the server...");
2999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        terminate = true;
3009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
3029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            serverThread.join();
3039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (InterruptedException e) {
3059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            e.printStackTrace();
3069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException(e);
3079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return the CommandHandler defined for the specified command name
3129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param name - the command name
3139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the CommandHandler defined for name
3149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public CommandHandler getCommandHandler(String name) {
3169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return (CommandHandler) commandHandlers.get(Command.normalizeName(name));
3179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Override the default CommandHandlers with those in the specified Map of
3219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * commandName>>CommandHandler. This will only override the default CommandHandlers
3229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * for the keys in <code>commandHandlerMapping</code>. All other default CommandHandler
3239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * mappings remain unchanged.
3249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
3259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param commandHandlers - the Map of commandName->CommandHandler; these override the defaults
3269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
3279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @throws AssertFailedException - if the commandHandlerMapping is null
3289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setCommandHandlers(Map commandHandlerMapping) {
3309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(commandHandlerMapping, "commandHandlers");
3319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        for (Iterator iter = commandHandlerMapping.keySet().iterator(); iter.hasNext();) {
3329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            String commandName = (String) iter.next();
3339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            setCommandHandler(commandName, (CommandHandler) commandHandlerMapping.get(commandName));
3349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Set the CommandHandler for the specified command name. If the CommandHandler implements
3399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * the {@link ReplyTextBundleAware} interface and its <code>replyTextBundle</code> attribute
3409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * is null, then set its <code>replyTextBundle</code> to the <code>replyTextBundle</code> of
3419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * this StubFtpServer.
3429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
3439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param commandName - the command name to which the CommandHandler will be associated
3449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param commandHandler - the CommandHandler
3459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
3469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @throws AssertFailedException - if the commandName or commandHandler is null
3479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setCommandHandler(String commandName, CommandHandler commandHandler) {
3499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(commandName, "commandName");
3509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(commandHandler, "commandHandler");
3519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        commandHandlers.put(Command.normalizeName(commandName), commandHandler);
3529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, replyTextBundle);
3539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Set the reply text ResourceBundle to a new ResourceBundle with the specified base name,
3579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * accessible on the CLASSPATH. See {@link ResourceBundle#getBundle(String)}.
3589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param baseName - the base name of the resource bundle, a fully qualified class name
3599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setReplyTextBaseName(String baseName) {
3619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        replyTextBundle = ResourceBundle.getBundle(baseName);
3629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Set the port number to which the server control connection socket will bind. The default value is 21.
3669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param serverControlPort - the port number for the server control connection ServerSocket
3679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setServerControlPort(int serverControlPort) {
3699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        this.serverControlPort = serverControlPort;
3709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    //-------------------------------------------------------------------------
3739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    // Internal Helper Methods
3749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    //-------------------------------------------------------------------------
3759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return true if this server is fully shutdown -- i.e., there is no active (alive) threads and
3789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * all sockets are closed. This method is intended for testing only.
3799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return true if this server is fully shutdown
3809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    boolean isShutdown() {
3829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        boolean shutdown = !serverThread.isAlive() && serverSocket.isClosed();
3839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        for (Iterator iter = sessions.keySet().iterator(); iter.hasNext();) {
3859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            SessionInfo sessionInfo = (SessionInfo) iter.next();
3869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            shutdown = shutdown && sessionInfo.socket.isClosed() && !sessionInfo.thread.isAlive();
3879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return shutdown;
3899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return true if this server has started -- i.e., there is an active (alive) server threads
3939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * and non-null server socket. This method is intended for testing only.
3949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return true if this server has started
3959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    boolean isStarted() {
3979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return serverThread != null && serverThread.isAlive() && serverSocket != null;
3989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair}