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