One common performance requirement for large websites is that different domain names should be used to serve static and dynamic content. The reason behind this requirement is that usually browsers do not download more than two components in parallel per domain name, because of the HTTP 1.1 specification. Thus, separating static and dynamic content under different hostnames is used to maximize the number of parallel downloads. More information about the performance increase obtained by the usage of this technique is given at Yahoo! User Interface Blog, which contains an entry titled Performance Research, Part 4: Maximizing Parallel Downloads in the Carpoon Line.
Recently I started wondering, how this requirement can be implemented by using Maven. The requirements for the Maven based solution are following:
- Static content of the Maven project must be automatically copied to the server used to server static content.
- It must be possible to copy CSS files and layout images to different remote directories.
- Created war file must be automatically copied to the server used to serve dynamic content.
- Files must be copied by using FTP protocol.
When I did some research, I learned that Maven Wagon plugin can be used to fulfill the given requirements.
Required Steps
The steps required to fulfill the given requirements are following:
- Enabling the FTP support of Maven Wagon Plugin
- Configuring the used credentials
- Configuring the Maven Wagon Plugin
Each of these steps are described with more details in following.
Enabling the FTP Support of Maven Wagon Plugin
However, since it does not support FTP protocol out of the box, I first tried to enable FTP support by following the instructions given in the usage page of the plugin’s homepage. I soon noticed that this approach does not work, because the extension adding FTP support to Maven Wagon was not compatible with the commons-net-1.4.1.jar. So, instead of following the instructions given in the plugin’s homepage, copy following jar files to your $MAVEN_HOME/lib directory:
- wagon-ftp-beta-6.jar
- oro-2.0.8.jar
- commons-net-2.0.jar
Configuring the Used Credentials
Before investigating the configuration added to the pom.xml file, we must configure the the credentials of the used servers to the settings.xml file. I recommend configuring the credentials by using user specific settings file (The default location of this file is ~/.m2/settings.xml). Following example contains own credentials for CSS files, layout images and the war file (Remember to replace the usernames and passwords with correct ones):
1<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
4 <servers>
5 <server>
6 <id>css</id>
7 <username>cssUser</username>
8 <password>cssPassword</password>
9 <!-- If you want deploy to active FTP server, uncomment the following block -->
10 <!--
11 <configuration>
12 <passiveMode>false</passiveMode>
13 </configuration>
14 -->
15 </server>
16 <server>
17 <id>images</id>
18 <username>imagesUser</username>
19 <password>imagesPassword</password>
20 <!-- If you want deploy to active FTP server, uncomment the following block -->
21 <!--
22 <configuration>
23 <passiveMode>false</passiveMode>
24 </configuration>
25 -->
26 </server>
27 <server>
28 <id>war</id>
29 <username>warUser</username>
30 <password>warPassword</password>
31 <!-- If you want deploy to active FTP server, uncomment the following block -->
32 <!--
33 <configuration>
34 <passiveMode>false</passiveMode>
35 </configuration>
36 -->
37 </server>
38 </servers>
39</settings>
Configuring the Maven Wagon Plugin
Now that we have fulfilled all the prerequisites for the usage of FTP via the Maven Wagon plugin, we can take a look of the pom file, which makes the magic happen. Since pom.xml is quite large, I will explain it in a smaller pieces, which should make it easier to understand. At first we will concentrate on the plugin declaration of the Maven Wagon plugin:
1<plugin>
2 <groupId>org.codehaus.mojo</groupId>
3 <artifactId>wagon-maven-plugin</artifactId>
4 <version>1.0-beta-3</version>
5 <!--
6 Contains unique execution declarations for CSS files,
7 layout images and war file.
8 -->
9 <executions>
10 <!--
11 Declaration for copying CSS files via FTP
12 -->
13 <execution>
14 <id>upload-css</id>
15 <!--
16 Describes the lifecycle phase when the plugin's
17 goals are being executed.
18 -->
19 <phase>install</phase>
20 <!--
21 Describes the goals are executed in the specified
22 lifecycle phase.
23 -->
24 <goals>
25 <goal>upload</goal>
26 </goals>
27 <configuration>
28 <!--
29 Describes the local directory, which contents are
30 copied.
31 -->
32 <fromDir>${css.local.dir}</fromDir>
33 <!--
34 Describes the included files.
35 -->
36 <includes>*</includes>
37 <!--
38 Describes the url of the remote FTP server.
39 -->
40 <url>${css.remote.url}</url>
41 <!--
42 Describes the remote directory, where the contents
43 of the local directory are copied.
44 -->
45 <toDir>${css.remote.dir}</toDir>
46 <!--
47 Describes the server id, which is used to fetch the
48 used credentials from settings.xml.
49 -->
50 <serverId>${css.remote.server.id}</serverId>
51 </configuration>
52 </execution>
53 <!--
54 Declaration for copying images via FTP. See the declaration
55 of CSS files for more information about the configuration.
56 -->
57 <execution>
58 <id>upload-images</id>
59 <phase>install</phase>
60 <goals>
61 <goal>upload</goal>
62 </goals>
63 <configuration>
64 <fromDir>${images.local.dir}</fromDir>
65 <includes>*</includes>
66 <url>${images.remote.url}</url>
67 <toDir>${images.remote.dir}</toDir>
68 <serverId>${images.remote.server.id}</serverId>
69 </configuration>
70 </execution>
71 <!--
72 Declaration for copying war file via FTP. See the
73 declaration of CSS for more information about the
74 configuration.
75 -->
76 <execution>
77 <id>upload-war</id>
78 <phase>install</phase>
79 <goals>
80 <goal>upload</goal>
81 </goals>
82 <configuration>
83 <fromDir>${war.local.dir}</fromDir>
84 <includes>*</includes>
85 <url>${war.remote.url}</url>
86 <toDir>${war.remote.dir}</toDir>
87 <serverId>${war.remote.server.id}</serverId>
88 </configuration>
89 </execution>
90 <executions>
91</plugin>
Cool. We have now configured the Maven Wagon plugin to copy CSS files, images and war file to remote FTP server when install lifecycle phase is executed. Obviously we now have to also configure the Maven War plugin to exclude CSS files and images from the war file. This can be done by using following plugin declaration:
1<plugin>
2 <groupId>org.apache.maven.plugins</groupId>
3 <artifactId>maven-war-plugin</artifactId>
4 <version>2.0.2</version>
5 <configuration>
6 <!--
7 Excludes the contents of the styles and image directories when copying
8 files to the directory used to create war file. Note that the directories
9 are hard coded and you might have to change the configuration
10 depending on your project structure.
11 -->
12 <warSourceExcludes>
13 styles/*, images/*
14 </warSourceExcludes>
15 </configuration>
16</plugin>
The last interesting part of the pom file contains the property declarations used in the configuration of Maven Wagon plugin. These declarations are shown in following:
1<properties>
2 <!-- Settings for CSS files -->
3 <css.local.dir>${basedir}/src/main/webapp/styles</css.local.dir>
4 <!-- Insert the remote target directory path for CSS files -->
5 <css.remote.dir></css.remote.dir>
6 <css.remote.server.id>css</css.remote.server.id>
7 <!-- Insert the url address of the FTP server for CSS files -->
8 <css.remote.url></css.remote.url>
9
10 <!-- Settings for image files -->
11 <images.local.dir>${basedir}/src/main/webapp/images</images.local.dir>
12 <!-- Insert the remote target directory path for images -->
13 <images.remote.dir></images.remote.dir>
14 <images.remote.server.id>images</images.remote.server.id>
15 <!-- Insert the url address of the FTP server for image files -->
16 <images.remote.url></images.remote.url>
17
18 <!-- Settings for War files-->
19 <war.local.dir>${basedir}/target</war.local.dir>
20 <!-- Insert the remote target directory path for war file -->
21 <war.remote.dir></war.remote.dir>
22 <war.remote.server.id>war</war.remote.server.id>
23 <!-- Insert the url address of FTP server for war file -->
24 <war.remote.url></war.remote.url>
25</properties>
The deployment process can be triggered by executing command: mvn install in the project’s root directory (The directory, which contains the pom.xml file). The outcome is following:
- CSS files are copied from the configured local directory to the specified remote directory.
- Images are copied from the configured local directory to the specified remote directory.
- War file is constructed, and copied from the configured local directory to the specified remote directory.
Final Words
It should now be clear, how the Maven Wagon plugin can be used to copy static and dynamic files to different hosts. However, the current configuration is far from perfect. To be honest, it is not ready for production usage. Different Maven profiles for development, testing and production usage should be added, before I would recommend the usage of this configuration in serious development. The master plan behind this is following:
At this point the only way to run the example project from working war file (styles and images are included in the war file), is to comment out the configuration section of the Maven War plugin declaration. The lack of profiles is also the reason explaining, why the upload goal of the Maven Wagon plugin is triggered by install lifecycle phase. When using different profiles, Maven could be configured in such manner that no files would be copied to remote servers and excluded from the war file, when the development profile is enabled. Also, separating test and production profiles would make it possible to copy files to different remote servers depending on the enabled profile. Of course this would also allow us to determine the url addresses, which are needed to access static content, based on the used profile.
In any case, this improvement is left as an exercise for the reader.
Disclaimer: The example project has been tested with JDK 1.6.0_20, Maven 2.2.0 and OS X 10.6.4. Browse the source code of the example project.