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}