Building an FTP server Maven plugin from scratch

In this post we design and code a new Maven plugin that fires up a FTP server to aid in integration testing of FTP-bound processes, thus demonstrating the flexibility and power of Maven plugins.

Introducing Maven and testing

It is well known and accepted that the Maven build system from Apache is very powerful and capable. It is also easy to use and hard to master.

Countless plugins to extend its functionality can be found.

One of the main functionalities offered by Maven is assisting in various levels of testing, made possible by some powerful plugins.

In building and testing software, individual software components and classes should be unit tested using mockup objects and at a level of granularity that is independent of any services being available, both internal and external.

In this case Maven offers very good solutions, such as the ubiquitous Surefire plugin. Using this plugin unit testing with a variety of testing frameworks is quite straightforward and easy.

On the other hand, Integration Testing is done at a higher level, verifying that the integration between components, both internal and external is done correctly and to help identify problematic areas. Once a faulty integration is discovered, its unit test results can be examined in detail or if it is an external system, further diagnostics on it can be run.

Good judgement on the programmer is key to apply testing at the most convenient levels though on large enterprise systems usually a combination of unit testing and integration testing is most appropriate.

Though less well known than Surefire, Maven offers the Failsafe plugin, designed specifically for integration testing.

It is interesting to note that the name has been chosen to emphasize that failures don’t stop the Maven lifecycle allowing for gracefully cleaning up any resources used during the testing. As resources can be external it i risky -and rather inelegant- not to clean them up correctly.

According to the Failsafe documentation, it needs to be attached to the integration-test and verify phases (though it seems to work if attached only to the integration-phase). In any case, once invoked it runs through the relevant goals and relevant tests are run.

Testing FTP and SFTP functionality under Maven

We have discussed instances of problems where powerful, established plugins are available to do what we want (in this case, testing). However, we need to do something in Maven for where there are no plugins or they are not easy to find. Look it up twice as it is quite likely someone else has solved the problem before though it may be the case no solution exists.

One of such problems is testing systems that depend on external services such as FTP or SFTP. Say we have a client that downloads some data off such a server, such as XML files or large binaries and no other interface is available.

A good non-legacy example of such a service is YouTube’s API.

Even though YouTube offers programmatic APIs in Java for instance, to use more advanced functionality and for efficient bulk uploading an SFTP interface needs to be used. There are many other examples of such services, using both FTP and SFTP.

Unit tests on the system can be done with mockup objects as usual. However, to do integration testing things get more interesting.

We could, of course, use our YouTube production service to do some integration tests using dummy files, etc. Using production for testing is usually not a very good idea, with potential for disaster. Therefore, using the YouTube production service is not advisable.

Ideally, we should be able to create a mock but fully functional FTP environment, containing exactly the files we need where we can upload and download whatever is necessary and if needed, a small script (say, groovy or something) that simulates whatever it is that YouTube does with your files behind the scenes.

The basic workflow would look like this:

FTP Server Maven pluging Phases

Therefore, we need a plugin that fires up a FTP server and points users to specific areas of the target folder. Ideally, it fires up during the pre-integration-test phase and shuts down on the post-integration-test. Easy as a cake.
A google search of ‘maven ftp server plugin‘ yields no significant results and looking up on the official list of plugins here. Also doing a google code search is of no use, either. Lots of stuff to perform FTPs and file transfers but no starting up servers.

Instead of giving up or firing up some server using Ant or even worse, manually(!), we go on and do it the Maven way.

Namely, we develop a plugin that fires up an (S)FTP server on the desired integration test phases.

Firstly, we need a FTP backbone written in Java so it can be integrated easily and of a suitable license. The Apache FtpServer is perfect for that purpose. It is a high-performance server based on Apache MINA for I/O and it is ridiculously easy to embed from any Java app.

Creating the Maven plugin basics

Firstly, we create a plugin skeleton project using the Maven Archetype plugin:

mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create -DgroupId=cat.calidos.maven.ftpserver -DartifactId=ftpserver-maven-plugin -DarchetypeArtifactId=maven-archetype-plugin -DarchetypeGroupId=org.apache.maven.archetypes

This creates an empty Maven plugin project ready to start adding stuff such as code and tests. We also need to run

mvn eclipse:eclipse

to create Eclipse settings for the project. We also need to run it whenever we add new dependencies to the POM file so they are also included in the Eclipse world.

Firstly, we add the dependencies for the Apache Ftp server (*):

	
		org.apache.ftpserver
		ftpserver-core
		1.0.6
	
	
		org.apache.ftpserver
		ftplet-api
		1.0.6
	
	
		org.slf4j
		slf4j-api
		1.6.1
	
	
		org.slf4j
		slf4j-log4j12
		1.6.1
	

We can find the details on dependencies and versions on a Maven repo such as MVNrepository.

In this case, we also add the Simple Logging Facade for Java ‘Log4j’ implementation< so the ftp server can output logs. This means that whenever we use the maven plugin we should provide a log4j configuration file through the usual methods. Plugin will still work but will complain about the missing configuration. In any case, should we desire to use another logging framework we only need to change the dependency to our chosen implementation artifact.

Adding our first mojo

Next, we need to start adding some ‘mojos‘, or ‘Maven plain Old Java Object’, which are the fine-grained goals we need to run (or what is the same, tasks that we want Maven to execute). For more information, read the Introduction to the Build Lifecycle article on the official documentation which is very clarifying and chock full of valuable information.

Examining the source of other Maven plugins, we observe a common enough pattern which is to have an abstract superclass with some of the attributes needed by our mojos.

public abstract class AbstractFtpServerMojo extends AbstractMojo {

On this class we can add common attributes to our mojos such as the Maven project instance

/** Encompassing maven project
*	@parameter default-value="${project}" 
* 	@required
* 	@readonly
*/
protected MavenProject mavenProject;

To the attributes we add the revelant annotations which let the Maven runtime inject following the popular Inversion of Control pattern. More documentation on the annotations can be found on the Mojo API Specification.

Basically, we need one mojo to start the server at the ‘pre-integration-test’ phase and another to stop it at the ‘post-integration-test’ phase, which gives us this a class hierarchy like this:

Maven FTP Server mojo classes

Ok, so now we need to take a look at the Apache Ftp Server documentation to discover how to embed it.

Fortunately it is pretty straightforward. A server factory instance is created from which a server instance can be created and attached to a specific port. Any such configuration such as adding users, setting up SSL keystores or port and interface attachment are configured using ‘listeners‘, which are configuration classes. Multiple listener instances can be set onto the same instance, to allow for multiple interfaces, etc. In the case of this example plugin we aim to configure a subset of all setup possibilities as it is only for integration testing and shouldn’t be used to deploy production FTP servers. For proper deployment, it can be run and configured fully from the command line or as a Windows service very easily.

Ok, so we add a method to the ‘run’ mojo to create the relevant instances

/** Create a server instance with default values
*///////////////////////////////////////////////////////////////////////////////
private void initServer() {

	// add relevant system property variables
	if (systemPropertyVariables!=null) {
		Set propertyNames = systemPropertyVariables.keySet();
		for (Iterator iterator = propertyNames.iterator(); iterator.hasNext();) {
			String propertyName = (String) iterator.next();
			String propertyValue = (String) systemPropertyVariables.get(propertyName);
			getLog().debug("Setting system variable '"+propertyName+"'");
			System.setProperty(propertyName, propertyValue);
		}
	}
	
	serverFactory = new FtpServerFactory();
	factory = new ListenerFactory();

	// set the port of the listener
	getLog().debug("Using port "+port);
	factory.setPort(port);
	serverFactory.addListener("default", factory.createListener());
	server = serverFactory.createServer();

}	// initServer

Firstly, we add any system variables that our POM wants to have added to our environment. This is a common pattern on many plugins and allows plugin users to fine tune system properties on that particular plugin environment. A very important possibility is to be able to configure a custom location of the log4j configuration using the ‘log4j.configuration’ system variable. For another example of setting system properties you can look at the excellent Jetty Maven plugin.

As properties are just key-value pairs of strings, we allow for the Maven injection to inject them defining the appropriate attribute parameter.

/** Additional system property variables (to pass onto tests, etc.)
*	@parameter 
*/
protected Map systemPropertyVariables;

Secondly, we create the factory, listener and server instance. To make use of the more sophisticated features of the Apache Ftp Server we would only need to modify this method a little bit, for instance to allow to bind to a different network interface, etc.

Adding user management

Another common enough configuration that is definitely needed is user setup, including password setup, write permissions, home location, simultaneous logins, etc.

Examining the documentation we see a simple yet very flexible API to manage users. An interface to an user manager class is provided, ‘org.apache.ftpserver.ftplet.UserManager’ and two ready made implementations are available: a properties-based manager and a database-based one. This is very convenient and we could easily have the mojo provide database connection details or a path to a property file. However, we would rather provide configuration details on the POM file itself so all settings are selfcontained in the Maven world.

This means we need a simple factory, user manager and a trivial user class, where all user details are created in Java code.

We start by creating a skeleton factory:

public class SimpleUserManagerFactory implements UserManagerFactory {

And fill the necessary methods, in this case, only the ‘createUserManager’ method.
We follow by creating a simple User class:

public class User implements org.apache.ftpserver.ftplet.User  {

We shouldn’t really use the Ftp Server User implementation ‘BaseUser’ for two reasons: a) it’s in a private implementation package ‘impl’ on ftpserver-core so it can’t really be used (the interface is public and resides in the ftplet-api library) and b) we want to populate its details through the POM file so we need to add appropriate Maven annotations to its attributes.

It is no big deal as it is an easy enough interface to implement, with several key-value pairs, related to the different settings users can have such as maximum number of logins, username, password, etc. We tag the attributes with the Maven annotations and implement all the interface methods.

/** @parameter
*	@required */
protected String name;

/** @parameter
*	@required */
protected String password;

Creating the user manager is also pretty straightforward, implementing the UserManager interface of the ftplet API:

public class SimpleUserManager implements UserManager {

As a ‘repository’ to hold the User data we employ a simple in-memory Map instance:

private HashMap	users;

The resulting class structure is clear in its intent and purpose:

Maven FTP Server plugin user management classes

Testing user management code

Both the manager and the manager factory classes are pretty much self-contained it is fairly easy to create tests for most of that functionality. For example. in the case of the user manager, on the setUp() jUnit method we create a sample environment which we use on each test.

/* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*///////////////////////////////////////////////////////////////////////////////
protected void setUp() throws Exception {

	super.setUp();
	
	String adminName = "admin";
	userManager = new SimpleUserManager(adminName,new ClearTextPasswordEncryptor(),false);
	saveUser("demo","demo");
	saveUser("demo2","demo");
	saveUser(adminName,"administrator");
	
}	// setUp

Note that we use the clear text password encryptor both on the test and on the functional code as the passwords are only stored in memory therefore they do not really need to be encrypted and they are in plain view in the POM file anyway.

Using the sample environment we test functionality of the user manager:

/**
* Test method for {@link cat.calidos.maven.ftpserver.users.SimpleUserManager#authenticate(org.apache.ftpserver.ftplet.Authentication)}.
*///////////////////////////////////////////////////////////////////////////////
public void testAuthenticate() {

	loginShouldWork("demo", "demo");
	loginShouldFail("demo", "FAIL");
	loginShouldFail("DOESNTEXIST", "whatever");
	loginShouldWork("admin", "administrator");
	loginShouldFail("admin", "FAIL");
	
}	// testAuthenticate

Methods ‘saveUser’, ‘loginShouldWork’ and ‘loginShouldFail’ are private convenience methods to aid in testing and make tests more readable, tests are still code and should be readable and well-structured like regular functional code.

Run server mojo

Next, we add some code to have our configured FTP server instance run:

/** Startup the server and store it on the project properties if possible
*	@throws MojoFailureException 
*//////////////////////////////////////////////////////////////////////////////
private void runServer() throws MojoFailureException {

	getLog().debug("About to start FTP server...");
	
	try {
		
		server.start();
		getLog().info("FTP server started.");

	} catch (FtpException e) {

		getLog().error("Could not start FTP server...");
		throw new MojoFailureException("Could not start FTP server instance", e);
		
	}
	
	Properties properties = null;
	if (mavenProject!=null) {
		properties = mavenProject.getProperties();
		properties.put(FtpServerConstants.FTPSERVER_KEY, server);
	} else {
		throw new MojoFailureException("Can't add ftpserver instance as maven project is null");
	}
	
}	// runServer

The ‘start’ method does not block and creates a new thread, where the server code will run and listen for upcoming connections. We also add the instance to the project properties so the server can be stopped gracefully on the ‘post-integration-test’ phase.

Also, we do not forget to add the annotation to the ‘FtpServerRunMojo’ class that binds the run mojo to the ‘pre-integration-test’ phase:

/** Mojo to start the apache FTP server as an integration instance
*	@goal run
*	@phase pre-integration-test
*///////////////////////////////////////////////////////////////////////////////

Stopping the server mojo

Once our run mojo starts the server, integration tests can be run on the ‘integration-test’ phase. Please bear in mind that client POMs can still be configured to run the server on whatever phases they need through XML tweaking.

The stop mojo ‘execute’ method is quite straightforward as well:

public void execute() throws MojoFailureException {

	getLog().debug("Stopping FTP server...");
	Properties properties = null;
	if (mavenProject!=null) {
		properties = mavenProject.getProperties();
	} else {
		throw new MojoFailureException("Can't access maven project to stop FTP server (null)");
	}
	
	if (properties!=null) {

		FtpServer ftpServer;
		try {
			ftpServer = (FtpServer) properties.get(FtpServerConstants.FTPSERVER_KEY);
		} catch (ClassCastException e) {
			throw new MojoFailureException("Context doesn't contain a valid ftp server instance",e);
		}
		if (ftpServer==null) {
			throw new MojoFailureException("Context doesn't contain any ftp server instance");
		}
		if (!ftpServer.isStopped()) {
			ftpServer.stop();
			getLog().info("FTP server stopped.");
		} else {
			getLog().info("FTP server was stopped already");
		}

	} else {
		throw new MojoFailureException("Maven project has null properties",new NullPointerException());
	}
	
}	// execute

After error and sanity checks we retrieve the ftp server instance and stop it. Easy.
Bear in mind that, exceptions during the integration-test phase should be instances of ‘MojoFailureException’ which do not cause the build to die (please check the appropriate documentation on the Maven reference book).

So, if for instance a single test fails, the other tests can run and the server is gracefully stopped at the end. Using ‘MojoExecutionException’ would cause the whole build to stop and if done before the stop mojo has a chance to run the server is not stopped. All probable exceptions on the Maven ftp server plugin are of the type ‘MojoFailureException’ as we do not want to stop the whole build if integration tests fail, we report them and it will be up to the client developer to decide what to do.

Testing the mojos the right way

Unit testing mojo code is made easier thanks to the ‘AbstractMojoTestCase’

public class FtpServerMojoTest extends AbstractMojoTestCase {

This superclass contains some methods to help in testing mojos, mainly methods to read POM files and run mojos.

We also need an FTP client to connect to the running instance. The excellent Apache Commons Net library is ideal for that purpose.

To use it we add the relevant test dependencies to our project POM with a ‘test’ scope which means they will only be used on the ‘test’ phase and not when the plugin is ran:

	
		org.apache.maven.plugin-testing
		maven-plugin-testing-harness
		1.2
		test
	
	
	    commons-net
	    commons-net
   		 3.0.1
    	test
    

So what we want to do is run the FTP server startup method on each test setup and stop it on each test teardown.

/** Read pom and start FTP server
*	@throws java.lang.Exception
*///////////////////////////////////////////////////////////////////////////////
public void setUp() throws Exception {
		
	
	super.setUp() ;

	pom = getTestFile("src/test/resources/unit/test-project/pom.xml");
	assertNotNull("Test pom not found",pom);
	assertTrue("Test pom not found",pom.exists());
	
	port = getFreePort();
	
	FtpServerRunMojo runMojo = (FtpServerRunMojo) lookupMojo("run", pom);

	// all this will be set on a real project (it's not on the test environment)
	runMojo.serverRoot = new File(PlexusTestCase.getBasedir()+"/target");
	runMojo.port = port;
	runMojo.mavenProject = new MavenProject();
	runMojo.mavenProject.getModel().setProperties(new Properties());
	runMojo.execute();
	ftpServer = (FtpServer) runMojo.mavenProject.getProperties().get(FtpServerConstants.FTPSERVER_KEY);
	
    ftp = new FTPClient();
    ftp.connect("localhost",port);    

}	// setUp

First we call the superclass as it configures stuff we need to call the convenience methods, load up a test pom and find a free port to run the server. Next we lookup the ‘run’ mojo and add the relevant data that it needs to start such as the server root, port, container Maven project, etc. All that data is automatically set on a non-test environment but it is not on a test setup. Last on the setup is to connect to the FTP server.

Teardown needs to cleanup the ftp connection and run the stop mojo:

/** Run stop server mojo
* 	@throws java.lang.Exception
*///////////////////////////////////////////////////////////////////////////////
public void tearDown() throws Exception {

	
	if (ftp.isAvailable()) {
		if (ftp.isConnected()) {
			ftp.disconnect();
		}
	}
	
	FtpServerStopMojo stopMojo = (FtpServerStopMojo) lookupMojo("stop",pom);
	
	Properties properties = new Properties();
	properties.put(FtpServerConstants.FTPSERVER_KEY, ftpServer);
	stopMojo.mavenProject = new MavenProject();
	stopMojo.mavenProject.getModel().setProperties(properties);
	stopMojo.execute();
	
	super.tearDown();	// called last as it dismantles running mojo stuff
	
}	// tearDown

Note we create a Maven project instance to store the running FTP instance which will be stopped by the ‘stop’ mojo. We also call ‘super.tearDown()’ last to make sure any superclass resources are cleaned up when we are done cleaning up ourselves.

The test POM should contain appropriate test data so significant tests can be run, therefore we add different configuration settings to the XML


	cat.calidos.maven.ftpserver
	ftpserver-maven-plugin
	1.0
		
			
				admin
				admin00
			
			
				
					demo
					demo
				
				
					disabled
					disabled
					false
				
				
					classes-root
					classes-root
					./classes
				
				
					write-disabled
					write-disabled
					false
				
				
			
		
	

This setup lets us test the administrator user, a regular user, a disabled one, home folders and write-disabled users. There are far more features exposed by the Apache FTP Server though it is not the aim of this test to test them all, just to make a general set of features work as expected. Obviously, to get higher code coverage more tests can be added as needed.

After the POM is ready, we add some unit tests such as the regular user test:

/** Demo user should be able to login
*	@throws SocketException
*	@throws IOException
*//////////////////////////////////////////////////////////////////////////////
public void testDemoUser() throws SocketException, IOException {
	
    ftp.login("demo", "demo");
	int reply = ftp.getReplyCode();
    assertTrue(FTPReply.isPositiveCompletion(reply));
    assertTrue("Can't login with demo user",ftp.isConnected());
    
	String filename = "test-file";
	putFile(filename);
	boolean found = findRemoteItem(filename);
	assertTrue(filename+" can't be uploaded",found);
    
    ftp.disconnect();
    
}	// testDemoUser

We connect, add a test file and ensure it has been uploaded successfully. Both ‘putFile’ and ‘findRemoteItem’ are convenience methods created on the test class.

Adding more test cases is pretty straightforward, like in the test for readonly users:

/**	User shouldn't have write permissions
*	@throws IOException
*//////////////////////////////////////////////////////////////////////////////
public void testCannotWrite() throws IOException {
	
	ftp.login("write-disabled", "write-disabled");
	int reply = ftp.getReplyCode();
	assertTrue(FTPReply.isPositiveCompletion(reply));
	assertTrue(ftp.isConnected());
	
	String filename = "test-file2";
	putFile(filename);
	boolean found = findRemoteItem(filename);
	assertFalse(filename+" can be put on nonwrite permissions user",found);
	
	ftp.disconnect();
	
}	// testCannotWrite

Using the plugin

Doing integration testing with our completed FTP Server Maven plugin is pretty straightforward using the ‘maven-failsafe-plugin’ to fire up the tests and the ‘ftpserver-maven-plugin’ itself to run the FTP server


	cat.calidos.maven.ftpserver
	ftpserver-maven-plugin
	1.0

		
			
				admin
				admin00
			
		
			file:target/test-classes/log4j.properties
		
	

	
	
		
			start-ftpserver
			
				run
			
		
		
			stop-ftpserver
			
				stop
			
		
	

Not that, unlike in unit testing and the ‘maven-surefire-plugin’ the standard test location for the log4j configuration file does not work so we need to specify it on the POM. We also need to specify the goals for the FTP server to startup and shutdown, much along the lines of the Maven Failsafe plugin itself.

If any of the integration tests fails the stop goal is still ran and the server shuts down gracefully.

Additionally, if running the FTP server is needed on phases other than the integration testing ones the goals be attached to the relevant phases through our client POM.

Wrapping up: creation of an FTP server plugin

We have identified a need of using a fully functional FTP and SFTP server in integration testing using Maven. Looking up available plugins on the Web does not yield any already available plugins. We have gone ahead and built a plugin from scratch using the Apache FTP Server opensource library.

You can download the source code of version 1.0 of the maven-ftpserver-plugin as well as a sample client project. Enjoy and comments are welcome.

(*) Between completing the code and writing the article Apache FtpServer came out with a new release (1.0.6). Thanks to Maven and the fair amount testing it was ridiculously easy to upgrade the project, just changed the dependency numbers on the pom file and ran a maven build.

2 thoughts on “Building an FTP server Maven plugin from scratch”

  1. Looks good to me – care to release it to the wider public? If you don’t want to then I could put the source on Github, set up a build for it & take ownership of it – hopefully get some other contributors so it can be actively maintained.

    Thanks,
    Jimmi

    1. Jimmy,

      Thanks for the feedback and interest! To be honest I was waiting for this kind of feedback to make a move (rather than have a dead github project).

      I will setup a github project for it and will happily accept your contributions/add you as admin.

      Dani

Comments are closed.