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