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