
In this chapter, we create a Multi-module project which combines the example from the two previous chapters. The simple-weather code developed in Chapter 4, Customizing a Maven Project will be combined with the simple-webapp project defined in Chapter 5, A Simple Web Application to create a web application which retrieves and displays weather forecast information on a web page. At the end of this chapter, you will be able to use Maven to develop complex, multi-module projects.
The multi-module project developed in this example consists of
modified versions of the projects developed in Chapter 4, Customizing a Maven Project and Chapter 5, A Simple Web Application, and we are not
using the Maven Archetype plugin to generate this multi-module project.
We strongly recommend downloading a copy of the example code to use as a
supplemental reference while reading the content in this chapter. This
chapter's example project may be downloaded with the book's example code
at http://www.sonatype.com/book/mvn-examples-1.0.zip
or http://www.sonatype.com/book/mvn-examples-1.0.tar.gz.
Unzip this archive in any directory, and then go to the
ch06/ directory. In the ch06/
directory you will see a directory named
simple-parent/ which contains the multi-module
Maven project developed in this chapter. In the
simple-parent/ project directory you will see a
pom.xml and the two submodule directories
simple-weather/ and
simple-webapp/. If you wish to follow along with
the example code in a web browser, go to http://www.sonatype.com/book/examples-1.0
and click on the ch06/ directory.
A multi-module project is defined by a parent POM
referencing one or more submodules. In the
simple-parent/ directory you will find the parent
POM (also called the top-level POM)
in simple-parent/pom.xml.
Example 6.1. simple-parent Project POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 6 Simple Parent Project</name> <modules> <module>simple-weather</module> <module>simple-webapp</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
Notice that the parent defines a set of Maven coordinates: the
groupId is org.sonatype.mavenbook, the
artifactId is simple-parent, and the
version is 1.0. The parent project
doesn't create a JAR or a WAR like
our previous projects, instead it is simply a POM which
refers to other Maven projects. The appropriate packaging for a project
like simple-parent which simply provides a Project
Object Model is pom. The next section in the
pom.xml lists the project's submodules. These modules
are defined in the modules element and each
module element corresponds to a subdirectory off of the
simple-parent/ directory. Maven knows to look in
these directories for pom.xml files, and it will add
submodules to the list of Maven projects included in a build.
Lastly, we define some settings which will be inherited by all
submodules. The simple-parent build configuration
configures the target for all Java compilation to be the Java 5
JVM. Since the compiler plugin is bound to the lifecycle
by default, we can use the pluginManagement section do to this.
We will discuss pluginManagement in more detail in later chapters, but
the separation between providing configuration to default plugins and actually binding plugins
is much easier to see when they are separated this way. The dependencies element adds JUnit 3.8.1 as a
global dependency. Both the build configuration and the dependencies are
inherited by all submodules. Using POM inheritance allows you to add
common dependencies for universal dependencies like JUnit or Log4J.
The first submodule we're going to look at is the
simple-weather submodule. This submodule contains all
of the class that take care of interacting with and parsing the Yahoo!
weather feeds.
Example 6.2. simple-weather Module POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Chapter 6 Simple Weather API</name> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project>
In simple-weather's pom.xml
file we see this module referencing a parent POM using a set of Maven
coordinates. The parent POM for simple-weather is
identified by a groupId of
org.sonatype.mavenbook, an artifactId of
simple-parent, and a version of
1.0. Note that we do not need to redefine the groupId and version, as
these are inherited from the parent.
Example 6.3. The WeatherService class
package org.sonatype.mavenbook.weather;
import java.io.InputStream;
public class WeatherService {
public WeatherService() {}
public String retrieveForecast( String zip ) throws Exception {
// Retrieve Data
InputStream dataIn = new YahooRetriever().retrieve( zip );
// Parse Data
Weather weather = new YahooParser().parse( dataIn );
// Format (Print) Data
return new WeatherFormatter().format( weather );
}
}
The WeatherService class is defined in
src/main/java/org/sonatype/mavenbook/weather and it
simply calls out to the three objects defined in Chapter 4, Customizing a Maven Project. In this chapter's example, we're creating a
separate project which contains service objects that are referenced in the
web application project. This is a common model in enterprise Java
development, often a complex application consists of more than just a
single, simple web application. You might have an enterprise application
which consists of multiple web applications, and some command-line
applications. Often you'll want to refactor common logic to a service
class which can be reused across a number of projects. This is the
justification for creating a WeatherService class,
by doing so, you can see how the simple-webapp project
references a service object defined in
simple-weather.
The retrieveForecast() method takes a
String containing a zip code. This zip code
parameter is then passed to the YahooRetriever's
retrieve() method which gets the
XML from Yahoo! Weather. The XML
returned from YahooRetriever is then passed to the
parse() method on
YahooParser which returns a
Weather object. This Weather
object is then formatted into a presentable String
by the WeatherFormatter.
The simple-webapp module is the second submodule
referenced in the simple-parent project. This web
application project depends upon the simple-weather
module and it contains some simple servlets which present the results of
the Yahoo! weather service query.
Example 6.4. simple-webapp Module POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>simple-webapp Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-servlet_2.4_spec</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> </plugin> </plugins> </build> </project>
This simple-weather module defines a very simple
servlet which reads a zip code from an HTTP request, calls the
WeatherService shown in Example 6.3, “The WeatherService class”, and prints the results to the
response's Writer.
Example 6.5. simple-webapp WeatherServlet
package org.sonatype.mavenbook.web;
import org.sonatype.mavenbook.weather.WeatherService;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class WeatherServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String zip = request.getParameter("zip" );
WeatherService weatherService = new WeatherService();
PrintWriter out = response.getWriter();
try {
out.println( weatherService.retrieveForecast( zip ) );
} catch( Exception e ) {
out.println( "Error Retrieving Forecast: " + e.getMessage() );
}
out.flush();
out.close();
}
}In WeatherServlet, we instantiate an instance
of the WeatherService class defined in
simple-weather. The zip code supplied in the request
parameter is passed to the retrieveForecast()
method and the resulting test is printed to the response's
Writer.
Finally, to tie all of this together is the
web.xml for simple-webapp in
src/main/webapp/WEB-INF. The
servlet and servlet-mapping elements
in this web.xml map the request path
/weather to the
WeatherServlet.
Example 6.6. simple-webapp web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>simple</servlet-name> <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class> </servlet> <servlet> <servlet-name>weather</servlet-name> <servlet-class>org.sonatype.mavenbook.web.WeatherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>simple</servlet-name> <url-pattern>/simple</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>weather</servlet-name> <url-pattern>/weather</url-pattern> </servlet-mapping> </web-app>
With the simple-weather project containing all
the general code for interacting with the Yahoo! weather service and the
simple-webapp project containing a simple servlet, it
is time to compile and package the application into a
WAR file. To do this, you will want to compile and
install both projects in the appropriate order; since
simple-webapp depends on
simple-weather, the simple-weather
JAR needs to be created before the
simple-webapp project can compile. To do this, you will
run mvn clean install command from the
simple-parent project.
~/examples/ch06/simple-parent$ mvn clean install
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO] Simple Parent Project
[INFO] simple-weather
[INFO] simple-webapp Maven Webapp
[INFO] ----------------------------------------------------------------------
[INFO] Building simple-weather
[INFO] task-segment: [clean, install]
[INFO] ----------------------------------------------------------------------
[...]
[INFO] [install:install]
[INFO] Installing simple-weather-1.0.jar to simple-weather-1.0.jar
[INFO] ----------------------------------------------------------------------
[INFO] Building simple-webapp Maven Webapp
[INFO] task-segment: [clean, install]
[INFO] ----------------------------------------------------------------------
[...]
[INFO] [install:install]
[INFO] Installing simple-webapp.war to simple-webapp-1.0.war
[INFO]
[INFO] ----------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ----------------------------------------------------------------------
[INFO] Simple Parent Project ............................... SUCCESS [3.041s]
[INFO] simple-weather ...................................... SUCCESS [4.802s]
[INFO] simple-webapp Maven Webapp .......................... SUCCESS [3.065s]
[INFO] ----------------------------------------------------------------------
When Maven is executed against a project with submodules, Maven first loads the parent POM and locates all of the submodule POMs. Maven then puts all of these project POMs into something called the Maven Reactor which analyzes the dependencies between modules. The Reactor takes care of ordering components to ensure that interdependent modules are compiled and installed in the proper order.
The Reactor preserves the order of modules as defined in the POM unless changes need to be made. A helpful mental model for this is to picture that modules with dependencies on sibling projects are "pushed down" the list until the dependency ordering is satisfied. On rare occasions, it may be handy to rearrange the module order of your build -- for example if you want a frequently unstable module towards the beginning of the build.
Once the Reactor figures out the order in
which projects must be built, Maven then executes the specified goals for
every module in a multi-module build. In this example, you can see that
Maven builds simple-weather before
simple-webapp effectively executing mvn clean
install for each submodule.
When you run Maven from the command line you'll frequently want to
specify the clean lifecycle phase before any other
lifecycle stages. When you specify clean, you make
sure that Maven is going to remove old output before it compiles and
packages an application. Running clean isn't
necessary, but it is a useful precaution to make sure that you are
performing a "clean build".
Once the multi-module project has been installed with mvn
clean install from the parent project,
simple-project, you can then change directories into
the simple-webapp project and run the Run goal of the
Jetty plugin.
~/examples/ch06/simple-parent/simple-webapp $ mvn jetty:run
[INFO] ----------------------------------------------------------------------
[INFO] Building simple-webapp Maven Webapp
[INFO] task-segment: [jetty:run]
[INFO] ----------------------------------------------------------------------
[...]
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: simple-webapp Maven Webapp
[...]
[INFO] Webapp directory = ~/examples/ch06/simple-parent/\
simple-webapp/src/main/webapp
[INFO] Starting jetty 6.1.6rc1 ...
2007-11-18 1:58:26.980::INFO: jetty-6.1.6rc1
2007-11-18 1:58:26.125::INFO: No Transaction manager found
2007-11-18 1:58:27.633::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
Once Jetty has started, load http://localhost:8080/simple-webapp/weather?zip=01201 in a browser and you should see the formatted weather output.

