Maven 2.0: Enterprise Peace of Mind Is All Part of the Package

uilding enterprise Java applications is a challenging process in which you build, package, and deploy multiple components to a variety of environments. And these components will frequently depend on one another and on third-party libraries and frameworks. Furthermore, you must also manage static resources, configuration files, and deployment descriptors, as well as produce packages that are compliant with J2EE specifications. You may also want all of this tested and plugged into a continuous integration build system. Every enterprise application built will address these issues to some extent, and most will do so differently.

Maven tackles this challenge head-on by abstracting tasks and processes commonly performed across applications. By following some recommended practices, and by supplying some descriptive information about your project, you can enable Maven to take care of many of the activities involved in building your application. As an example, one best practice that Maven recommends is a common directory structure for your project. If you follow this structure then Maven can automatically package your application for you. Of course you can deviate from the layout recommended by Maven but you will then have to provide Maven with additional configuration information.

Central to Maven is the concept of a Project Object Model (POM). The POM consists of meta-data describing the project to be built. This provides a standard way of describing important aspects of your project such as versioning, dependencies, resources, and much more. This allows you, for example, to list your project’s dependencies in an XML file and have Maven take care of downloading these dependencies, bundling them in your distributions, and making them available at compile time and runtime.

The 2.0 version of Maven, scheduled for release in late August, is a complete rewrite of the successful 1.x version. This new version adds many useful features such as integrated multi-project support, transitive dependency management, dependency scoping, a defined life-cycle, a new plugin API, and improved site generation. As a full review of the capabilities of Maven is beyond the scope of this article, please consult the Related Resources section for more information about Maven.

Author’s Note: At the time of publication Maven 2.0 Beta 1 has not yet been released though it is expected soon. The code provided (see left column) works with Alpha 3 as well as the latest code base.

The remainder of this article will focus on the features of Maven new to the 2.0 release and will walk you through building a Java application with Maven 2.0 from the ground up.

What You Need
Versions of software and any other technologies necessary to implement the techniques discussed in the article:

Integrated Multi-Project Support
Multi-project support is important when building enterprise Java applications because the complexity of enterprise systems usually requires multiple components working collaboratively to achieve the end goal. Component packaging is core to the J2EE specification. An enterprise application may be packaged as an EAR file comprising multiple WARs, EJB-JARs, and utility JARs, and these components may be interdependent. For example, a servlet packaged in a WAR may use the services provided by an EJB in an EJB-JAR.

Multi-project support in Maven addresses not only the packaging of components in a J2EE-compliant manner but also analyzes these dependencies (through the metadata contained in the POM) and builds the components in the correct order so that artifacts that are depended upon are built before the ones that depend on them.

In Maven 1.0 multi-project support was not inherent to the build system but was instead provided by the multi-project plugin. In Maven 2.0, however, multi-project support is built into the core Maven engine. This eases development by explicitly describing subprojects in the POM, by eliminating the need to define multi-project settings in a properties file, and by not requiring the multi-project plugin to be explicitly invoked.

Project Archetypes
Part of what makes Maven so powerful is its ability to facilitate the use of best practices. Maven facilitates this by providing an archetype plugin that can create bare bone projects for you from project templates. For example, if you are writing a Web application Maven can create a base project layout that you can build upon. Because most projects built using Maven integrate best practices, much of the build process can be automated.

Greeter Application&#151’Iteration 1
My first example will use the archetype plugin to create an application comprising multiple components. This simple J2EE application will say hello to users when they navigate to your Web site. You will expose your greeting logic in a service packaged in a JAR and your presentation logic in a servlet packaged in a WAR. This simple architecture is illustrated in Figure 1.

Figure 1. Greeter Architecture: The greeter application is a simple illustration of how multiple components may be related in an enterprise application.

Using the archetype plugin building these components is simple. First create a top-level directory named “greeter” to contain your projects. Next you will use the archetype plugin to create two projects, one for the WAR and the other for the JAR. To do so execute these commands from your top-level directory:

m2 archetype:create -DgroupId=com.devx.greeter -DartifactId=greeter-app
–DpackageName=com.devx.greeterm2 archetype:create -DgroupId=com.devx.greeter -DartifactId=greeter-web
–DpackageName=com.devx.greeter -DarchetypeArtifactId=maven-
archetype-webapp

The archetype plugin creates the directory structure illustrated in Figure 2.

Now I’ll move on to the application service. Create the GreeterService class in the greeter-app project:

/greeter/greeter-app/src/main/java/com/devx/greeter/GreeterService.javapackage com.devx.greeter;public class GreeterService{    public String sayHello()    {        return "Bonjour, tout le monde!";    }}
Figure 2. Greeter Directory Layout: The archetype plugin has created skeleton projects upon which you will build. Notice the standard directory layout.

Navigate to the greeter-app directory and run the ‘m2 install’ command. Presto! Maven compiles your classes, executes your unit tests, packages everything into a JAR, and installs it into your local repository where it will be available to other projects. You should see “BUILD SUCCESSFUL” printed to the console, indicating that everything ran successfully.

Next, you’ll build the Web application that will display this friendly greeting to your users. In order to do this write a simple servlet and add it and its deployment descriptor to the greeter-web project.

/greeter/greeter-web/src/main/java/com/devx/greeter/GreeterServlet.javapackage com.devx.greeter;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class GreeterServlet extends HttpServlet{    protected void doGet(HttpServletRequest request,            HttpServletResponse response) throws ServletException, IOException    {        PrintWriter writer = new PrintWriter(response.getOutputStream());        GreeterService service = new GreeterService();        writer.println("");        writer.println(service.sayHello());        writer.println("");        writer.close();    }}/greeter/greeter-web/src/main/webapp/WEB-INF/web.xml    Archetype Created Web Application            greeter        com.devx.greeter.GreeterServlet              greeter        /greeter   

But how do you make the service you packaged a JAR accessible to your servlet? You must declare the greeter-app JAR as a dependency to the WAR’s POM, as illustrated below.

/greeter/greeter-web/pom.xml    4.0.0    com.devx.greeter    greeter-web    war    1.0-SNAPSHOT                        servletapi            servletapi            2.3                            com.devx.greeter            greeter-app            1.0-SNAPSHOT                            junit            junit            3.8.1            test                        greeter-web    
Figure 3. Greeter Output: With very little effort you’ve created a J2EE-compliant application consisting of two projects with interdependencies.

Now navigate to the greeter-web directory and run the ‘m2 install’ command. This time Maven builds a WAR and even bundles the service JAR that was copied to the local repository when you built that project. But what if you don’t want to have to separately build these two artifacts? You can create a parent project that defines these two projects as sub modules. When building the parent project Maven is smart enough to build these subprojects in the correct order (reverse dependency order). All we need to do to accomplish this is to create a new POM in the greeter top directory as illustrated below.

/greeter/pom.xml    4.0.0    com.devx.greeter    1.0-SNAPSHOT    greeter    pom            greeter-app        greeter-web    

To build the complete application, navigate to the greeter top directory and run ‘m2 install’. Notice that Maven builds the JAR before building the WAR. Deploy the resultant WAR (/greeter/greeter-web/target/greeter-web.war) to your favorite Web container and test it out (see Figure 3).

Transitive Dependencies
It is common for your project’s dependencies to in turn have dependencies of their own. HttpUnit, for instance, depends on six other libraries. To use HttpUnit in Maven 1.x you would have to add to your project’s POM not only HttpUnit but also the six libraries on which HttpUnit in turn depends. Maven 2.0 recursively analyzes the POMs of your project’s dependencies and automatically includes their dependencies in your project. Transitive dependencies will greatly simplify managing project dependencies, as you will see shortly.

Although the transitive dependency mechanism is extremely helpful in simplifying the build process, it brings with it a few challenges. For example, what about cyclical dependencies in the dependency graph, and what if there are version conflicts in some of these dependencies? Maven 2.0 provides dependency mediation techniques, conflict management schemes, and reporting options to address these challenges. More information on these advanced features of transitive dependencies can be found on the Maven 2.0 Web site.

Dependency Scoping
When building enterprise applications you often need different libraries for compiling, running, and testing. For example, you may want to include the HttpUnit testing library at test time but exclude it from the runtime classpath. In Maven 2.0 this ability to restrict which classpath a dependency is added to is referred to as dependency scoping.

There are four dependency scopes in Maven: compile, test, runtime, and provided. Compile-scoped dependencies are available on all classpaths, runtime-scoped dependencies on the runtime and test classpaths, and test-scope dependencies only on the test compilation and execution classpaths. The provided scope is intended for dependencies such as servletapi that are necessary for compilation but are expected to be provided by a container. Dependencies with a provided scope are excluded from artifact packaging. The scope for your dependencies is specified along with the dependency in your POM.

Greeter Application?Iteration 2
I want to enhance the greeter application from the last section by writing an HttpUnit test for the GreeterServlet, which will allow you to test both transitive dependencies and dependency scoping. I’ll start by adding HttpUnit as a test-scoped dependency to my greeter-web project. To accomplish this, add the following to the greeter-web’s POM:

/greeter/greeter-web/pom.xml (cont.)...    httpunit    httpunit    1.6    test...

Now you can add the GreeterServletTest class to the test source directory of the greeter-web project:

/greeter/greeter-web/src/test/java/com/devx/greeter/GreeterServletTest.javapackage com.devx.greeter;import java.io.IOException;import java.net.MalformedURLException;import junit.framework.TestCase;import org.xml.sax.SAXException;import com.meterware.httpunit.TextBlock;import com.meterware.httpunit.WebResponse;import com.meterware.servletunit.ServletRunner;import com.meterware.servletunit.ServletUnitClient;public class GreeterServletTest extends TestCase{    private ServletUnitClient client;    public GreeterServletTest() throws IOException, SAXException    {        ServletRunner sr = new ServletRunner();        sr.registerServlet("greeter", "com.devx.greeter.GreeterServlet");        client = sr.newClient();    }    public void testGreeting() throws MalformedURLException, IOException, SAXException    {        WebResponse response = client.getResponse("http://localhost/greeter");        String greeting = ((TextBlock[]) response.getTextBlocks())[0].getText();        assertEquals("Incorrect greeting returned", "Bonjour, tout le monde!", greeting.trim());    }}

To rebuild your application run ‘m2 install’ from the top-level directory. HttpUnit and its dependencies will be automatically discovered, downloaded, and added to the test classpath. Your new unit test will be executed before the WAR is packaged. Open the generated WAR file and notice that HttpUnit and its dependencies are not included in the artifact. Now change the scoping of HttpUnit to ‘compile’ and run it again. Notice the additional JARs added to the WEB-INF/lib directory.

Maven knew which dependencies to download for HttpUnit because they were specified in the POM for HttpUnit; you can see this at http://www.ibiblio.org/maven2/httpunit/httpunit/1.6/httpunit-1.6.pom . Not all libraries stored in the mirrored remote repositories have their dependencies defined. When adding a library to your applications it is always a good idea to check the POM of the library in question.

Defined Lifecycle
Most projects follow a similar compile, test, and deploy build cycle. Maven 2.0 encapsulates these similarities in the form of a defined lifecycle, which consists of a series of stages. The basic stages are:

  • generate-sources
  • compile
  • test
  • package
  • integration-test
  • install
  • deploy

(For a complete list of these phases see the Maven 2.0 Web site.)

During each phase you will have various tasks that you would like performed. For instance, in the generate-sources stage you might like to run XDoclet to generate some code. And in the integration-test phase you might like to deploy an EAR to an application server and run integration tests against it.

How do you control which tasks get executed in each phase of the lifecycle? The answer is by binding plugin goals to lifecycle phases. First, you can bind the execution of a plugin goal to a particular lifecycle phase by specifying the plugin, phase, goal, and additional configuration in your POM. So, for example, you could bind the deploy-ejb goal of the JBoss plugin to the integration-test lifecycle phase in order to deploy an EAR prior to testing.

Because most types of projects share many of the same basic tasks, each packaging type (JAR, WAR, etc.) specifies a default set of plugin bindings. As an example, the default plugin bindings for a JAR project are illustrated in Table 1. As the build progresses through each of the phases the plugin goals associated with that phase are executed.

Table 1. Default JAR lifecycle plugin bindings.
By default, the goals will be executed in their corresponding lifecycle phase for a JAR project.

Lifecycle Phase Plugin:Goal
process-resources resources:resources
Compile compiler:compile
process-test-resources resources:testResources
test-compile compiler:testCompile
Test surefire:test
Package jar:jar
Install Install:install
Deploy deploy:deploy

Plugin API
I have discussed how Maven aims to abstract similarities in the build process across projects. Whenever possible you should use the standard core Maven functionality and plugins to achieve the desired results. But there are times when a custom solution just cannot be avoided. In these cases you will need to write a custom plugin and bind it to one of the lifecycle stages.

In Maven 2.0 plugins can be written in Java or in one of several scripting languages (currently Marmalade is the only scripting language supported but support for others is being developed). Maven plugins are basically command objects. To create a Maven plugin you write a Java class that implements the Mojo interface (which stands for Maven POJO). This interface defines a single-execute method with no parameters and no return value and some logging methods. You must also write a plugin descriptor that provides additional information about your plugin, including lifecycle phase binding and parameters. Parameters defined in this descriptor will be injected into member variables in your plugin at runtime by Maven.

Maven simplifies this process by providing tools to generate the plugin descriptor and to package your plugin for you. All you need to do is annotate your plugin class and Maven will take care of the rest.

Greeter Application?Iteration 3
The best way to understand Maven plugins is to walk through the steps involved in creating one. In this section, you will create a plugin that writes a few useful pieces of project metadata to a properties file that will be read by your application at runtime. This properties file will be bundled in your WAR and will be used by the greeter servlet at runtime to display the version of the application. First, use the archetype plugin to create a simple plugin under your project root:

m2 archetype:create -DgroupId=com.devx.greeter -DartifactId=greeter-
plugin –DpackageName=com.devx.greeter -DarchetypeArtifactId=maven-
archetype-mojo

Next, delete the sample MyMojo class and replace it with a class named PomWriterMojo that will indirectly implement the Mojo interface indirectly by subclassing the AbstractMojo class. This abstract super class will provide default implementations of the log methods defined in the Mojo interface.

Also, you will annotate your class to indicate how you want this plugin used in the build process. You want your properties file created during the generate-sources lifecycle phase, so you’ll annotate your plugin class accordingly. During this phase your properties file will be created and placed in the target project’s output directory. This will make it available for unit testing, packaging, and deployment as subsequent lifecycle phases are executed.

/greeter/greeter-plugin/src/main/java/com/devx/greeter/PomWriterMojo.javapackage com.devx.greeter;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStream;import java.util.Properties;import org.apache.maven.plugin.AbstractMojo;import org.apache.maven.plugin.MojoExecutionException;/** * @goal write * @phase generate-sources * @description Build a version.txt file to provide information at runtime */public class PomWriterMojo extends AbstractMojo{    ...}

Before implementing the actual logic of your plugin, you need to determine where you will get the project metadata from. Instead of having your plugin go out and retrieve this information from Maven, instead ask the Maven to inject it into your class. You’ll specify what data needs to be injected by annotating the properties you want to contain this information. The annotation will specify an expression, which will be evaluated by Maven, and the results of the evaluation will be injected into the property. Expressions can reference configuration parameters and system properties but they generally reference POM elements and look similar to “${project.build.resources}”.

The annotated properties look as follows:

/greeter/greeter-plugin/src/main/java/com/devx/greeter/PomWriterMojo.java (cont.)public class PomWriterMojo extends AbstractMojo{    /**     * @parameter expression="${project.version}"     * @required     * @readonly     */    private String version;    /**     * @parameter expression="${project.build.outputDirectory}"     * @required     * @readonly     */    private String outputDirectory;}

Lastly, you can use the data that has been injected into your plugin to perform the task for which your plugin was written. Namely, to write the version of the project to a file named pom.properties in the output directory.

/greeter/greeter-plugin/src/main/java/com/devx/greeter/PomWriterMojo.java (cont.)public class PomWriterMojo extends AbstractMojo{    ...    public void execute() throws MojoExecutionException    {        Properties projectProperties = new Properties();        projectProperties.put("version", version);        try        {            File targetDirectory = new File(outputDirectory);            if (!targetDirectory.exists())            {                targetDirectory.mkdirs();            }            File propertiesFile = new File(outputDirectory + "/pom.properties");            propertiesFile.createNewFile();            OutputStream propertiesStream = new FileOutputStream(propertiesFile);            projectProperties.store(propertiesStream, "Properties from Maven POM");            propertiesStream.close();        }        catch (IOException e)        {            getLog().error(e);            throw new MojoExecutionException("Unable to open properties file for write");        }    }}

To take advantage of your new plugin, declare it in the plugins section of the POM of your greeter-web project. Additionally, you need to add this plugin as a dependency to that project and to the modules section of the parent POM. This will cause your plugin to be built prior to being used by the greeter-web project and, once again, illustrates multi-project support at work.

/greeter/greeter-web/pom.xml (cont.)...                    com.devx.greeter            greeter-plugin            1.0-SNAPSHOT             maven-plugin                        greeter-web                                    com.devx.greeter                greeter-plugin                1.0-SNAPSHOT                                                            write                                                            /greeter/pom.xml    4.0.0    com.devx.greeter    1.0-SNAPSHOT    greeter    pom            greeter-app        greeter-web        greeter-plugin    
Figure 4. Greeter Output: The PomWriter plugin is invoked before the WAR is built and stores project meta-data for the servlet to access at runtime.

Finally, you just want to use your generated POM properties file to display the version number of your servlet in its greeting.

/greeter/greeter-web/src/main/java/com/devx/greeter/GreeterServlet.javapackage com.devx.greeter;import java.io.IOException;import java.io.PrintWriter;import java.util.Properties;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class GreeterServlet extends HttpServlet{    protected void doGet(HttpServletRequest request,            HttpServletResponse response) throws ServletException, IOException    {        PrintWriter writer = new PrintWriter(response.getOutputStream());        GreeterService service = new GreeterService();        writer.println("");        writer.print("Servlet version " + getProjectVersion() + " says: ");        writer.println(service.sayHello());                writer.println("");        writer.close();    }    private String getProjectVersion()    {        String version;        try        {            Properties pomProperties = new Properties();            pomProperties.load(GreeterServlet.class                    .getResourceAsStream("/pom.properties"));            version = pomProperties.getProperty("version");        }        catch (IOException e)        {            version = "unknown";        }        return version;    }}

The new greeting displays the greeter-web’s version from its POM (see Figure 4).

Maven 2.0 is a powerful tool to organize your projects and to simplify your build process. By following a set of recommended practices and providing some information describing your project, you can let Maven do most of the heavy lifting in your build process. Its recommended practices also promote consistency across projects. And the Maven plugin API will continue to encourage innovative development of plugins useful in enterprise development.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: