1*876b9d75SXin Li -------------------------------------------------- 2*876b9d75SXin Li FakeFtpServer Getting Started 3*876b9d75SXin Li -------------------------------------------------- 4*876b9d75SXin Li 5*876b9d75SXin LiFakeFtpServer - Getting Started 6*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7*876b9d75SXin Li 8*876b9d75SXin Li <<FakeFtpServer>> is a "fake" implementation of an FTP server. It provides a high-level abstraction for 9*876b9d75SXin Li an FTP Server and is suitable for most testing and simulation scenarios. You define a virtual filesystem 10*876b9d75SXin Li (internal, in-memory) containing an arbitrary set of files and directories. These files and directories can 11*876b9d75SXin Li (optionally) have associated access permissions. You also configure a set of one or more user accounts that 12*876b9d75SXin Li control which users can login to the FTP server, and their home (default) directories. The user account is 13*876b9d75SXin Li also used when assigning file and directory ownership for new files. 14*876b9d75SXin Li 15*876b9d75SXin Li <<FakeFtpServer>> processes FTP client requests and responds with reply codes and reply messages 16*876b9d75SXin Li consistent with its configured file system and user accounts, including file and directory permissions, 17*876b9d75SXin Li if they have been configured. 18*876b9d75SXin Li 19*876b9d75SXin Li See the {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page for more information on 20*876b9d75SXin Li which features and scenarios are supported. 21*876b9d75SXin Li 22*876b9d75SXin Li In general the steps for setting up and starting the <<<FakeFtpServer>>> are: 23*876b9d75SXin Li 24*876b9d75SXin Li * Create a new <<<FakeFtpServer>>> instance, and optionally set the server control port (use a value of 0 25*876b9d75SXin Li to automatically choose a free port number). 26*876b9d75SXin Li 27*876b9d75SXin Li * Create and configure a <<<FileSystem>>>, and attach to the <<<FakeFtpServer>>> instance. 28*876b9d75SXin Li 29*876b9d75SXin Li * Create and configure one or more <<<UserAccount>>> objects and attach to the <<<FakeFtpServer>>> instance. 30*876b9d75SXin Li 31*876b9d75SXin Li [] 32*876b9d75SXin Li 33*876b9d75SXin Li Here is an example showing configuration and starting of an <<FakeFtpServer>> with a single user 34*876b9d75SXin Li account and a (simulated) Windows file system, defining a directory containing two files. 35*876b9d75SXin Li 36*876b9d75SXin Li+------------------------------------------------------------------------------ 37*876b9d75SXin LiFakeFtpServer fakeFtpServer = new FakeFtpServer(); 38*876b9d75SXin LifakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data")); 39*876b9d75SXin Li 40*876b9d75SXin LiFileSystem fileSystem = new WindowsFakeFileSystem(); 41*876b9d75SXin LifileSystem.add(new DirectoryEntry("c:\\data")); 42*876b9d75SXin LifileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890")); 43*876b9d75SXin LifileSystem.add(new FileEntry("c:\\data\\run.exe")); 44*876b9d75SXin LifakeFtpServer.setFileSystem(fileSystem); 45*876b9d75SXin Li 46*876b9d75SXin LifakeFtpServer.start(); 47*876b9d75SXin Li+------------------------------------------------------------------------------ 48*876b9d75SXin Li 49*876b9d75SXin Li If you are running on a system where the default port (21) is already in use or cannot be bound 50*876b9d75SXin Li from a user process (such as Unix), you probably need to use a different server control port. Use the 51*876b9d75SXin Li <<<FakeFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port 52*876b9d75SXin Li number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call 53*876b9d75SXin Li <<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port 54*876b9d75SXin Li number being used. Or, you can pass in a specific port number, such as 9187. 55*876b9d75SXin Li 56*876b9d75SXin Li <<FakeFtpServer>> can be fully configured programmatically or within the 57*876b9d75SXin Li {{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container. 58*876b9d75SXin Li The {{{#Example}Example Test Using FakeFtpServer}} below illustrates programmatic configuration of 59*876b9d75SXin Li <<<FakeFtpServer>>>. Alternatively, the {{{#Spring}Configuration}} section later on illustrates how to use 60*876b9d75SXin Li the <Spring Framework> to configure a <<<FakeFtpServer>>> instance. 61*876b9d75SXin Li 62*876b9d75SXin Li* {Example} Test Using FakeFtpServer 63*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 64*876b9d75SXin Li 65*876b9d75SXin Li This section includes a simplified example of FTP client code to be tested, and a JUnit 66*876b9d75SXin Li test for it that programmatically configures and uses <<FakeFtpServer>>. 67*876b9d75SXin Li 68*876b9d75SXin Li** FTP Client Code 69*876b9d75SXin Li~~~~~~~~~~~~~~~~~~ 70*876b9d75SXin Li 71*876b9d75SXin Li The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote 72*876b9d75SXin Li ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the 73*876b9d75SXin Li {{{http://commons.apache.org/net/}Apache Commons Net}} framework. 74*876b9d75SXin Li 75*876b9d75SXin Li+------------------------------------------------------------------------------ 76*876b9d75SXin Lipublic class RemoteFile { 77*876b9d75SXin Li 78*876b9d75SXin Li public static final String USERNAME = "user"; 79*876b9d75SXin Li public static final String PASSWORD = "password"; 80*876b9d75SXin Li 81*876b9d75SXin Li private String server; 82*876b9d75SXin Li private int port; 83*876b9d75SXin Li 84*876b9d75SXin Li public String readFile(String filename) throws IOException { 85*876b9d75SXin Li 86*876b9d75SXin Li FTPClient ftpClient = new FTPClient(); 87*876b9d75SXin Li ftpClient.connect(server, port); 88*876b9d75SXin Li ftpClient.login(USERNAME, PASSWORD); 89*876b9d75SXin Li 90*876b9d75SXin Li ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 91*876b9d75SXin Li boolean success = ftpClient.retrieveFile(filename, outputStream); 92*876b9d75SXin Li ftpClient.disconnect(); 93*876b9d75SXin Li 94*876b9d75SXin Li if (!success) { 95*876b9d75SXin Li throw new IOException("Retrieve file failed: " + filename); 96*876b9d75SXin Li } 97*876b9d75SXin Li return outputStream.toString(); 98*876b9d75SXin Li } 99*876b9d75SXin Li 100*876b9d75SXin Li public void setServer(String server) { 101*876b9d75SXin Li this.server = server; 102*876b9d75SXin Li } 103*876b9d75SXin Li 104*876b9d75SXin Li public void setPort(int port) { 105*876b9d75SXin Li this.port = port; 106*876b9d75SXin Li } 107*876b9d75SXin Li 108*876b9d75SXin Li // Other methods ... 109*876b9d75SXin Li} 110*876b9d75SXin Li+------------------------------------------------------------------------------ 111*876b9d75SXin Li 112*876b9d75SXin Li** JUnit Test For FTP Client Code Using FakeFtpServer 113*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 114*876b9d75SXin Li 115*876b9d75SXin Li The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use 116*876b9d75SXin Li <<FakeFtpServer>>. 117*876b9d75SXin Li 118*876b9d75SXin Li+------------------------------------------------------------------------------ 119*876b9d75SXin Liimport org.mockftpserver.fake.filesystem.FileEntry; 120*876b9d75SXin Liimport org.mockftpserver.fake.filesystem.FileSystem; 121*876b9d75SXin Liimport org.mockftpserver.fake.filesystem.UnixFakeFileSystem; 122*876b9d75SXin Liimport org.mockftpserver.fake.FakeFtpServer; 123*876b9d75SXin Liimport org.mockftpserver.fake.UserAccount; 124*876b9d75SXin Liimport org.mockftpserver.stub.example.RemoteFile; 125*876b9d75SXin Liimport org.mockftpserver.test.AbstractTest; 126*876b9d75SXin Liimport java.io.IOException; 127*876b9d75SXin Liimport java.util.List; 128*876b9d75SXin Li 129*876b9d75SXin Lipublic class RemoteFileTest extends AbstractTest { 130*876b9d75SXin Li 131*876b9d75SXin Li private static final String HOME_DIR = "/"; 132*876b9d75SXin Li private static final String FILE = "/dir/sample.txt"; 133*876b9d75SXin Li private static final String CONTENTS = "abcdef 1234567890"; 134*876b9d75SXin Li 135*876b9d75SXin Li private RemoteFile remoteFile; 136*876b9d75SXin Li private FakeFtpServer fakeFtpServer; 137*876b9d75SXin Li 138*876b9d75SXin Li public void testReadFile() throws Exception { 139*876b9d75SXin Li String contents = remoteFile.readFile(FILE); 140*876b9d75SXin Li assertEquals("contents", CONTENTS, contents); 141*876b9d75SXin Li } 142*876b9d75SXin Li 143*876b9d75SXin Li public void testReadFileThrowsException() { 144*876b9d75SXin Li try { 145*876b9d75SXin Li remoteFile.readFile("NoSuchFile.txt"); 146*876b9d75SXin Li fail("Expected IOException"); 147*876b9d75SXin Li } 148*876b9d75SXin Li catch (IOException expected) { 149*876b9d75SXin Li // Expected this 150*876b9d75SXin Li } 151*876b9d75SXin Li } 152*876b9d75SXin Li 153*876b9d75SXin Li protected void setUp() throws Exception { 154*876b9d75SXin Li super.setUp(); 155*876b9d75SXin Li fakeFtpServer = new FakeFtpServer(); 156*876b9d75SXin Li fakeFtpServer.setServerControlPort(0); // use any free port 157*876b9d75SXin Li 158*876b9d75SXin Li FileSystem fileSystem = new UnixFakeFileSystem(); 159*876b9d75SXin Li fileSystem.add(new FileEntry(FILE, CONTENTS)); 160*876b9d75SXin Li fakeFtpServer.setFileSystem(fileSystem); 161*876b9d75SXin Li 162*876b9d75SXin Li UserAccount userAccount = new UserAccount(RemoteFile.USERNAME, RemoteFile.PASSWORD, HOME_DIR); 163*876b9d75SXin Li fakeFtpServer.addUserAccount(userAccount); 164*876b9d75SXin Li 165*876b9d75SXin Li fakeFtpServer.start(); 166*876b9d75SXin Li int port = fakeFtpServer.getServerControlPort(); 167*876b9d75SXin Li 168*876b9d75SXin Li remoteFile = new RemoteFile(); 169*876b9d75SXin Li remoteFile.setServer("localhost"); 170*876b9d75SXin Li remoteFile.setPort(port); 171*876b9d75SXin Li } 172*876b9d75SXin Li 173*876b9d75SXin Li protected void tearDown() throws Exception { 174*876b9d75SXin Li super.tearDown(); 175*876b9d75SXin Li fakeFtpServer.stop(); 176*876b9d75SXin Li } 177*876b9d75SXin Li} 178*876b9d75SXin Li+------------------------------------------------------------------------------ 179*876b9d75SXin Li 180*876b9d75SXin Li Things to note about the above test: 181*876b9d75SXin Li 182*876b9d75SXin Li * The <<<FakeFtpServer>>> instance is created and started in the <<<setUp()>>> method and 183*876b9d75SXin Li stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails. 184*876b9d75SXin Li 185*876b9d75SXin Li * The server control port is set to 0 using <<<fakeFtpServer.setServerControlPort(PORT)>>>. 186*876b9d75SXin Li This means it will dynamically choose a free port. This is necessary if you are running on a 187*876b9d75SXin Li system where the default port (21) is already in use or cannot be bound from a user process (such as Unix). 188*876b9d75SXin Li 189*876b9d75SXin Li * The <<<UnixFakeFileSystem>>> filesystem is configured and attached to the <<<FakeFtpServer>>> instance 190*876b9d75SXin Li in the <<<setUp()>>> method. That includes creating a predefined <<<"/dir/sample.txt">>> file with the 191*876b9d75SXin Li specified file contents. The <<<UnixFakeFileSystem>>> has a <<<createParentDirectoriesAutomatically>>> 192*876b9d75SXin Li attribute, which defaults to <<<true>>>, meaning that parent directories will be created automatically, 193*876b9d75SXin Li as necessary. In this case, that means that the <<<"/">>> and <<<"/dir">>> parent directories will be created, 194*876b9d75SXin Li even though not explicitly specified. 195*876b9d75SXin Li 196*876b9d75SXin Li * A single <<<UserAccount>>> with the specified username, password and home directory is configured and 197*876b9d75SXin Li attached to the <<<FakeFtpServer>>> instance in the <<<setUp()>>> method. That configured user ("user") 198*876b9d75SXin Li is the only one that will be able to sucessfully log in to the <<<FakeFtpServer>>>. 199*876b9d75SXin Li 200*876b9d75SXin Li 201*876b9d75SXin Li* {Spring} Configuration 202*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~ 203*876b9d75SXin Li 204*876b9d75SXin Li You can easily configure a <<<FakeFtpServer>>> instance in the 205*876b9d75SXin Li {{{http://www.springframework.org/}Spring Framework}} or another, similar dependency-injection container. 206*876b9d75SXin Li 207*876b9d75SXin Li** Simple Spring Configuration Example 208*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 209*876b9d75SXin Li 210*876b9d75SXin Li The following example shows a <Spring> configuration file for a simple <<<FakeFtpServer>>> instance. 211*876b9d75SXin Li 212*876b9d75SXin Li+------------------------------------------------------------------------------ 213*876b9d75SXin Li<?xml version="1.0" encoding="UTF-8"?> 214*876b9d75SXin Li 215*876b9d75SXin Li<beans xmlns="http://www.springframework.org/schema/beans" 216*876b9d75SXin Li xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 217*876b9d75SXin Li xsi:schemaLocation="http://www.springframework.org/schema/beans 218*876b9d75SXin Li http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> 219*876b9d75SXin Li 220*876b9d75SXin Li <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer"> 221*876b9d75SXin Li <property name="serverControlPort" value="9981"/> 222*876b9d75SXin Li <property name="systemName" value="UNIX"/> 223*876b9d75SXin Li <property name="userAccounts"> 224*876b9d75SXin Li <list> 225*876b9d75SXin Li <bean class="org.mockftpserver.fake.UserAccount"> 226*876b9d75SXin Li <property name="username" value="joe"/> 227*876b9d75SXin Li <property name="password" value="password"/> 228*876b9d75SXin Li <property name="homeDirectory" value="/"/> 229*876b9d75SXin Li </bean> 230*876b9d75SXin Li </list> 231*876b9d75SXin Li </property> 232*876b9d75SXin Li 233*876b9d75SXin Li <property name="fileSystem"> 234*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.UnixFakeFileSystem"> 235*876b9d75SXin Li <property name="createParentDirectoriesAutomatically" value="false"/> 236*876b9d75SXin Li <property name="entries"> 237*876b9d75SXin Li <list> 238*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry"> 239*876b9d75SXin Li <property name="path" value="/"/> 240*876b9d75SXin Li </bean> 241*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.FileEntry"> 242*876b9d75SXin Li <property name="path" value="/File.txt"/> 243*876b9d75SXin Li <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/> 244*876b9d75SXin Li </bean> 245*876b9d75SXin Li </list> 246*876b9d75SXin Li </property> 247*876b9d75SXin Li </bean> 248*876b9d75SXin Li </property> 249*876b9d75SXin Li 250*876b9d75SXin Li </bean> 251*876b9d75SXin Li 252*876b9d75SXin Li</beans> 253*876b9d75SXin Li+------------------------------------------------------------------------------ 254*876b9d75SXin Li 255*876b9d75SXin Li Things to note about the above example: 256*876b9d75SXin Li 257*876b9d75SXin Li * The <<<FakeFtpServer>>> instance has a single user account for username "joe", password "password" 258*876b9d75SXin Li and home (default) directory of "/". 259*876b9d75SXin Li 260*876b9d75SXin Li * A <<<UnixFakeFileSystem>>> instance is configured with a predefined directory of "/" and a 261*876b9d75SXin Li "/File.txt" file with the specified contents. 262*876b9d75SXin Li 263*876b9d75SXin Li [] 264*876b9d75SXin Li 265*876b9d75SXin Li And here is the Java code to load the above <Spring> configuration file and start the 266*876b9d75SXin Li configured <<FakeFtpServer>>. 267*876b9d75SXin Li 268*876b9d75SXin Li+------------------------------------------------------------------------------ 269*876b9d75SXin LiApplicationContext context = new ClassPathXmlApplicationContext("fakeftpserver-beans.xml"); 270*876b9d75SXin LiFakeFtpServer = (FakeFtpServer) context.getBean("FakeFtpServer"); 271*876b9d75SXin LiFakeFtpServer.start(); 272*876b9d75SXin Li+------------------------------------------------------------------------------ 273*876b9d75SXin Li 274*876b9d75SXin Li 275*876b9d75SXin Li** Spring Configuration Example With File and Directory Permissions 276*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 277*876b9d75SXin Li 278*876b9d75SXin Li The following example shows a <Spring> configuration file for a <<<FakeFtpServer>>> instance that 279*876b9d75SXin Li also configures file and directory permissions. This will enable the <<<FakeFtpServer>>> to reply 280*876b9d75SXin Li with proper error codes when the logged in user does not have the required permissions to access 281*876b9d75SXin Li directories or files. 282*876b9d75SXin Li 283*876b9d75SXin Li+------------------------------------------------------------------------------ 284*876b9d75SXin Li<?xml version="1.0" encoding="UTF-8"?> 285*876b9d75SXin Li 286*876b9d75SXin Libeans xmlns="http://www.springframework.org/schema/beans" 287*876b9d75SXin Li xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 288*876b9d75SXin Li xsi:schemaLocation="http://www.springframework.org/schema/beans 289*876b9d75SXin Li http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> 290*876b9d75SXin Li 291*876b9d75SXin Li <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer"> 292*876b9d75SXin Li <property name="serverControlPort" value="9981"/> 293*876b9d75SXin Li <property name="userAccounts"> 294*876b9d75SXin Li <list> 295*876b9d75SXin Li <bean class="org.mockftpserver.fake.UserAccount"> 296*876b9d75SXin Li <property name="username" value="joe"/> 297*876b9d75SXin Li <property name="password" value="password"/> 298*876b9d75SXin Li <property name="homeDirectory" value="c:\"/> 299*876b9d75SXin Li </bean> 300*876b9d75SXin Li </list> 301*876b9d75SXin Li </property> 302*876b9d75SXin Li 303*876b9d75SXin Li <property name="fileSystem"> 304*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.WindowsFakeFileSystem"> 305*876b9d75SXin Li <property name="createParentDirectoriesAutomatically" value="false"/> 306*876b9d75SXin Li <property name="entries"> 307*876b9d75SXin Li <list> 308*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry"> 309*876b9d75SXin Li <property name="path" value="c:\"/> 310*876b9d75SXin Li <property name="permissionsFromString" value="rwxrwxrwx"/> 311*876b9d75SXin Li <property name="owner" value="joe"/> 312*876b9d75SXin Li <property name="group" value="users"/> 313*876b9d75SXin Li </bean> 314*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.FileEntry"> 315*876b9d75SXin Li <property name="path" value="c:\File1.txt"/> 316*876b9d75SXin Li <property name="contents" value="1234567890"/> 317*876b9d75SXin Li <property name="permissionsFromString" value="rwxrwxrwx"/> 318*876b9d75SXin Li <property name="owner" value="peter"/> 319*876b9d75SXin Li <property name="group" value="users"/> 320*876b9d75SXin Li </bean> 321*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.FileEntry"> 322*876b9d75SXin Li <property name="path" value="c:\File2.txt"/> 323*876b9d75SXin Li <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/> 324*876b9d75SXin Li <property name="permissions"> 325*876b9d75SXin Li <bean class="org.mockftpserver.fake.filesystem.Permissions"> 326*876b9d75SXin Li <constructor-arg value="rwx------"/> 327*876b9d75SXin Li </bean> 328*876b9d75SXin Li </property> 329*876b9d75SXin Li <property name="owner" value="peter"/> 330*876b9d75SXin Li <property name="group" value="users"/> 331*876b9d75SXin Li </bean> 332*876b9d75SXin Li </list> 333*876b9d75SXin Li </property> 334*876b9d75SXin Li </bean> 335*876b9d75SXin Li </property> 336*876b9d75SXin Li 337*876b9d75SXin Li </bean> 338*876b9d75SXin Li</beans> 339*876b9d75SXin Li+------------------------------------------------------------------------------ 340*876b9d75SXin Li 341*876b9d75SXin Li 342*876b9d75SXin Li Things to note about the above example: 343*876b9d75SXin Li 344*876b9d75SXin Li * The <<<FakeFtpServer>>> instance is configured with a <<<WindowsFakeFileSystem>>> and a "c:\" root 345*876b9d75SXin Li directory containing two files. Permissions and owner/group are specified for that directory, as well 346*876b9d75SXin Li as the two predefined files contained within it. 347*876b9d75SXin Li 348*876b9d75SXin Li * The permissions for "File1.txt" ("rwxrwxrwx") are specified using the "permissionsFromString" shortcut 349*876b9d75SXin Li method, while the permissions for "File2.txt" ("rwx------") are specified using the "permissions" setter, 350*876b9d75SXin Li which takes an instance of the <<<Permissions>>> class. Either method is fine. 351*876b9d75SXin Li 352*876b9d75SXin Li [] 353*876b9d75SXin Li 354*876b9d75SXin Li 355*876b9d75SXin Li* Configuring Custom CommandHandlers 356*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 357*876b9d75SXin Li 358*876b9d75SXin Li <<FakeFtpServer>> is intentionally designed to keep the lower-level details of FTP server implementation 359*876b9d75SXin Li hidden from the user. In most cases, you can simply define the files and directories in the file 360*876b9d75SXin Li system, configure one or more login users, and then fire up the server, expecting it to behave like 361*876b9d75SXin Li a <real> FTP server. 362*876b9d75SXin Li 363*876b9d75SXin Li There are some cases, however, where you might want to further customize the internal behavior of the 364*876b9d75SXin Li server. Such cases might include: 365*876b9d75SXin Li 366*876b9d75SXin Li * You want to have a particular FTP server command return a predetermined error reply 367*876b9d75SXin Li 368*876b9d75SXin Li * You want to add support for a command that is not provided out of the box by <<FakeFtpServer>> 369*876b9d75SXin Li 370*876b9d75SXin Li Note that if you need the FTP server to reply with entirely predetermined (canned) responses, then 371*876b9d75SXin Li you may want to consider using <<StubFtpServer>> instead. 372*876b9d75SXin Li 373*876b9d75SXin Li 374*876b9d75SXin Li** Using a StaticReplyCommandHandler 375*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 376*876b9d75SXin Li 377*876b9d75SXin Li You can use one of the <CommandHandler> classes defined within the <<<org.mockftpserver.core.command>>> 378*876b9d75SXin Li package to configure a custom <CommandHandler>. The following example uses the <<<StaticReplyCommandHandler>>> 379*876b9d75SXin Li from that package to add support for the FEAT command. Note that in this case, we are setting the 380*876b9d75SXin Li <CommandHandler> for a new command (i.e., one that is not supported out of the box by <<FakeFtpServer>>). 381*876b9d75SXin Li We could just as easily set the <CommandHandler> for an existing command, overriding the default <CommandHandler>. 382*876b9d75SXin Li 383*876b9d75SXin Li+------------------------------------------------------------------------------ 384*876b9d75SXin Liimport org.mockftpserver.core.command.StaticReplyCommandHandler 385*876b9d75SXin Li 386*876b9d75SXin LiFakeFtpServer ftpServer = new FakeFtpServer() 387*876b9d75SXin Li// ... set up files, directories and user accounts as usual 388*876b9d75SXin Li 389*876b9d75SXin LiStaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features"); 390*876b9d75SXin LiftpServer.setCommandHandler("FEAT", featCommandHandler); 391*876b9d75SXin Li 392*876b9d75SXin Li// ... 393*876b9d75SXin LiftpServer.start() 394*876b9d75SXin Li+------------------------------------------------------------------------------ 395*876b9d75SXin Li 396*876b9d75SXin Li 397*876b9d75SXin Li** Using a Stub CommandHandler 398*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 399*876b9d75SXin Li 400*876b9d75SXin Li You can also use a <<StubFtpServer>> <CommandHandler> -- i.e., one defined within the 401*876b9d75SXin Li <<<org.mockftpserver.stub.command>>> package. The following example uses the <stub> version of the 402*876b9d75SXin Li <<<CwdCommandHandler>>> from that package. 403*876b9d75SXin Li 404*876b9d75SXin Li+------------------------------------------------------------------------------ 405*876b9d75SXin Liimport org.mockftpserver.stub.command.CwdCommandHandler 406*876b9d75SXin Li 407*876b9d75SXin LiFakeFtpServer ftpServer = new FakeFtpServer() 408*876b9d75SXin Li// ... set up files, directories and user accounts as usual 409*876b9d75SXin Li 410*876b9d75SXin Lifinal int REPLY_CODE = 502; 411*876b9d75SXin LiCwdCommandHandler cwdCommandHandler = new CwdCommandHandler(); 412*876b9d75SXin LicwdCommandHandler.setReplyCode(REPLY_CODE); 413*876b9d75SXin LiftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler); 414*876b9d75SXin Li 415*876b9d75SXin Li// ... 416*876b9d75SXin LiftpServer.start() 417*876b9d75SXin Li+------------------------------------------------------------------------------ 418*876b9d75SXin Li 419*876b9d75SXin Li 420*876b9d75SXin Li** Creating Your Own Custom CommandHandler Class 421*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 422*876b9d75SXin Li 423*876b9d75SXin Li If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create 424*876b9d75SXin Li your own custom <CommandHandler> class. The only requirement is that it implement the 425*876b9d75SXin Li <<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from 426*876b9d75SXin Li inheriting from one of the existing abstract <CommandHandler> superclasses, such as 427*876b9d75SXin Li <<<org.mockftpserver.core.command.AbstractStaticReplyCommandHandler>>> or 428*876b9d75SXin Li <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for 429*876b9d75SXin Li more information. 430*876b9d75SXin Li 431*876b9d75SXin Li 432*876b9d75SXin Li* FTP Command Reply Text ResourceBundle 433*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 434*876b9d75SXin Li 435*876b9d75SXin Li The default text asociated with each FTP command reply code is contained within the 436*876b9d75SXin Li "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a 437*876b9d75SXin Li locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of 438*876b9d75SXin Li the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can 439*876b9d75SXin Li completely replace the ResourceBundle file by calling the calling the 440*876b9d75SXin Li <<<FakeFtpServer.setReplyTextBaseName(String)>>> method. 441*876b9d75SXin Li 442*876b9d75SXin Li* SLF4J Configuration Required to See Log Output 443*876b9d75SXin Li~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 444*876b9d75SXin Li 445*876b9d75SXin Li Note that <<FakeFtpServer>> uses {{{http://www.slf4j.org/}SLF4J}} for logging. If you want to 446*876b9d75SXin Li see the logging output, then you must configure <<SLF4J>>. (If no binding is found on the class 447*876b9d75SXin Li path, then <<SLF4J>> will default to a no-operation implementation.) 448*876b9d75SXin Li 449*876b9d75SXin Li See the {{{http://www.slf4j.org/manual.html}SLF4J User Manual}} for more information. 450