stubftpserver-getting-started.apt revision 93102446a7b7c3d17888064b4e2e4e5cb534e6d0
1		--------------------------------------------------
2					StubFtpServer Getting Started
3		--------------------------------------------------
4
5StubFtpServer - Getting Started
6
7  <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by 
8  implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR, 
9  DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes, 
10  allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can 
11  also be interrogated to verify command invocation data such as command parameters and timestamps.
12
13  <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured 
14  programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container.
15
16  Here is how to start the <<StubFtpServer>> with the default configuration. This will return 
17  success reply codes, and return empty data (for retrieved files, directory listings, etc.).
18
19+------------------------------------------------------------------------------  
20StubFtpServer stubFtpServer = new StubFtpServer();
21stubFtpServer.start();
22+------------------------------------------------------------------------------  
23  
24* CommandHandlers
25
26  <CommandHandler>s are the heart of the <<StubFtpServer>>.
27
28  <<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server 
29  command. See the list of <CommandHandler> classes associated with FTP server commands in 
30  {{{stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}.
31
32  You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the
33  <<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command
34  name. For example:
35  
36+------------------------------------------------------------------------------  
37PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
38+------------------------------------------------------------------------------  
39
40  You can replace the existing <CommandHandler> defined for an FTP server command by calling the
41  <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing 
42  in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the 
43  <<<CommandHandler>>> instance. For example:
44  
45+------------------------------------------------------------------------------  
46PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
47pwdCommandHandler.setDirectory("some/dir");
48stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
49+------------------------------------------------------------------------------  
50
51
52** Generic CommandHandlers
53
54  <<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace
55  the default command handler for an FTP command. See the Javadoc for more information.
56  
57  * <<StaticReplyCommadHandler>>
58
59    <<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply 
60    code and text. This can be a useful replacement for a default <CommandHandler> if you want a 
61    certain FTP command to always send back an error reply code.
62  
63  * <<SimpleCompositeCommandHandler>>
64
65    <<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal 
66    ordered list of <CommandHandler>s to which it delegates. Starting with the first 
67    <CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to) 
68    the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list.
69
70
71* Programmatic Configuration
72
73  You can customize the behavior of the FTP server through programmatic configuration.
74  <<StubFtpServer>> automatically creates a default <CommandHandler> for each supported command.
75  If you want to customize the behavior of the server, you should create and configure a replacement
76  <CommandHandler> for each command to be customized.
77  
78  The {{{#Example}Example Test Using Stub Ftp Server}} illustrates replacing the default 
79  <CommandHandler> with a customized handler.
80
81* Spring Configuration
82
83  You can easily configure a <<StubFtpServer>> instance in the 
84  {{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring>
85  configuration file.
86  
87+------------------------------------------------------------------------------  
88<?xml version="1.0" encoding="UTF-8"?>
89
90<beans xmlns="http://www.springframework.org/schema/beans"
91       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
92       xsi:schemaLocation="http://www.springframework.org/schema/beans 
93           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
94
95  <bean id="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer">
96  
97    <property name="commandHandlers">
98      <map>
99        <entry key="LIST">
100          <bean class="org.mockftpserver.stub.command.ListCommandHandler">
101            <property name="directoryListing">
102              <value>
103                11-09-01 12:30PM  406348 File2350.log
104                11-01-01 1:30PM &lt;DIR&gt; 0 archive
105              </value>
106            </property>
107          </bean>
108        </entry>
109
110        <entry key="PWD">
111          <bean class="org.mockftpserver.stub.command.PwdCommandHandler">
112            <property name="directory" value="foo/bar" />
113          </bean>
114        </entry>
115
116        <entry key="DELE">
117          <bean class="org.mockftpserver.stub.command.DeleCommandHandler">
118            <property name="replyCode" value="450" />
119          </bean>
120        </entry>
121
122        <entry key="RETR">
123          <bean class="org.mockftpserver.stub.command.RetrCommandHandler">
124            <property name="fileContents" 
125              value="Sample file contents line 1&#10;Line 2&#10;Line 3"/>
126          </bean>
127        </entry>
128
129      </map>
130    </property>
131  </bean>
132
133</beans>
134+------------------------------------------------------------------------------  
135
136  This example overrides the default handlers for the following FTP commands:
137  
138  * LIST - replies with a predefined directory listing
139  
140  * PWD - replies with a predefined directory pathname
141  
142  * DELE - replies with an error reply code (450)
143  
144  * RETR - replies with predefined contents for a retrieved file
145
146  []
147
148  And here is the Java code to load the above <Spring> configuration file and start the
149  configured <<StubFtpServer>>.
150
151+------------------------------------------------------------------------------  
152ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml");
153stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer");
154stubFtpServer.start();
155+------------------------------------------------------------------------------  
156
157
158* Retrieving Command Invocation Data
159
160  Each <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one for each time the
161  <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>> that triggered
162  the invocation (containing the command name and parameters), as well as the invocation timestamp
163  and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional 
164  <CommandHandler>-specific data. See the Javadoc for more information.
165  
166  You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the
167  <<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired
168  invocation. You can get the number of invocations for a <CommandHandler> by calling
169  <<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates 
170  retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>.
171  
172  
173  
174* {Example} Test Using StubFtpServer
175
176  This section includes a simplified example of FTP client code to be tested, and a JUnit 
177  test for it that uses <<StubFtpServer>>.
178
179** FTP Client Code
180
181  The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote 
182  ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the
183  {{{http://commons.apache.org/net/}Apache Commons Net}} framework.
184
185+------------------------------------------------------------------------------  
186public class RemoteFile {
187
188    private String server;
189
190    public String readFile(String filename) throws SocketException, IOException {
191
192        FTPClient ftpClient = new FTPClient();
193        ftpClient.connect(server);
194
195        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
196        boolean success = ftpClient.retrieveFile(filename, outputStream);
197        ftpClient.disconnect();
198
199        if (!success) {
200            throw new IOException("Retrieve file failed: " + filename);
201        }
202        return outputStream.toString();
203    }
204    
205    public void setServer(String server) {
206        this.server = server;
207    }
208    
209    // Other methods ...
210}
211+------------------------------------------------------------------------------  
212
213** JUnit Test For FTP Client Code Using StubFtpServer
214
215  The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use 
216  <<StubFtpServer>>.
217
218+------------------------------------------------------------------------------  
219import java.io.IOException;
220import org.mockftpserver.core.command.InvocationRecord;
221import org.mockftpserver.stub.StubFtpServer;
222import org.mockftpserver.stub.command.RetrCommandHandler;
223import org.mockftpserver.test.AbstractTest;
224
225public class RemoteFileTest extends AbstractTest {
226
227    private static final String FILENAME = "dir/sample.txt";
228
229    private RemoteFile remoteFile;
230    private StubFtpServer stubFtpServer;
231    
232    /**
233     * Test readFile() method 
234     */
235    public void testReadFile() throws Exception {
236
237        final String CONTENTS = "abcdef 1234567890";
238
239        // Replace the default RETR CommandHandler; customize returned file contents
240        RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
241        retrCommandHandler.setFileContents(CONTENTS);
242        stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
243        
244        stubFtpServer.start();
245        
246        String contents = remoteFile.readFile(FILENAME);
247
248        // Verify returned file contents
249        assertEquals("contents", CONTENTS, contents);
250        
251        // Verify the submitted filename
252        InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0);
253        String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY);
254        assertEquals("filename", FILENAME, filename);
255    }
256
257    /**
258     * Test the readFile() method when the FTP transfer fails (returns a non-success reply code) 
259     */
260    public void testReadFileThrowsException() {
261
262        // Replace the default RETR CommandHandler; return failure reply code
263        RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
264        retrCommandHandler.setOverrideFinalReplyCode(550);
265        stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
266        
267        stubFtpServer.start();
268
269        try {
270            remoteFile.readFile(FILENAME);
271            fail("Expected IOException");
272        }
273        catch (IOException expected) {
274            // Expected this
275        }
276    }
277    
278    /**
279     * @see org.mockftpserver.test.AbstractTest#setUp()
280     */
281    protected void setUp() throws Exception {
282        super.setUp();
283        remoteFile = new RemoteFile();
284        remoteFile.setServer("localhost");
285        stubFtpServer = new StubFtpServer();
286    }
287
288    /**
289     * @see org.mockftpserver.test.AbstractTest#tearDown()
290     */
291    protected void tearDown() throws Exception {
292        super.tearDown();
293        stubFtpServer.stop();
294    }
295}
296+------------------------------------------------------------------------------  
297
298  Things to note about the above test:
299  
300  * The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started
301    there because it must be configured differently for each test. The <<<StubFtpServer>>> instance 
302    is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.
303  
304
305* FTP Command Reply Text ResourceBundle
306
307  The default text asociated with each FTP command reply code is contained within the
308  "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
309  locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of 
310  the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can 
311  completely replace the ResourceBundle file by calling the calling the 
312  <<<StubFtpServer.setReplyTextBaseName(String)>>> method. 
313