Apache Maven 2 Effective Implementation

By Brett Porter , Maria Odea Ching
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Maven in a Nutshell

About this book

By building up a sample application, this book guides developers painlessly through building a project with Maven. This book shows you how to combine Maven with Continuum and Archiva, which will allow you to build an efficient, secure application and make developing in a team easy.

You may already be aware of the pitfalls of 'integration hell' caused by changed code being submitted to repositories by a number of developers. When you implement Continuum in your build, you can easily perform continuous integration, avoiding timely rework and reducing cost and valuable time. You will be able to use Maven more efficiently in a team after you learn how to set up Archiva, Apache's repository manager.

It's easy to quickly get to work with Maven and get the most out of its related tools when you follow the sequential coverage of the sample application in this book. A focus on team environments ensures that you will avoid the pitfalls that are all too common when working in a team. Soon, by learning the best practices of working with Maven, you will have built an effective, secure Java application.

Publication date:
September 2009
Publisher
Packt
Pages
456
ISBN
9781847194541

 

Chapter 1. Maven in a Nutshell

In this chapter, we will take a comprehensive look at the fundamentals of Maven, by example. Moreover, we will also look at the associated terminology that we will be using in this book. If you are already a frequent Maven user, you may only need to skim through this chapter to refresh yourself on some of the basic principles.

For those who are new to Maven; other books and the Maven documentation have covered some of this content in more detail already—so this is the only review we will spend on the basics of Maven. Beyond this chapter, we will assume that you are confident in updating the Maven project without step-by-step instructions so that we can quickly get into more interesting examples.

 

A whirlwind tour


The best way to get to know Maven is by seeing it in action. In this section, we will create a simple project and explore the different ways in which you can use Maven in it.

Installing Maven

Of course, first we will need to install Maven. If you haven't done this already, you can download and install the latest version by following the instructions from http://maven.apache.org/download.html. The samples in this book have been checked against Maven 2.2.1.

Luckily, this is a very simple process. It has only two steps, which any user should be familiar with:

  1. Unzip the Maven distribution to the location you would like to install it, such as /usr/local or C:\Program Files. The distribution stores everything under a subdirectory, for example apache-maven-2.2.1. We will refer to this location as M2_HOME, so for example this would be /usr/local/apache-maven-2.2.1.

  2. Add the bin subdirectory of M2_HOME to your PATH variable.

You should verify the installation by running the following command:

$ mvn --version

A successful result will look similar to the following results from my machine:

Maven version: 2.2.1
Java version: 1.5.0_13
Default locale: en_AU, platform encoding: MacRoman
OS name: "mac os x" version: "10.5.4" arch: "i386" family: "unix"

Maven is now ready to use.

Tip

There is one situation where you will need to go through an additional configuration step. As Maven downloads much of its functionality from the Internet, and stores it locally, you will need a direct Internet connection. If you would prefer to set up a permanent, shared storage area in your own network (or on your machine) up front, look at how to install a repository manager in Chapter 2, Staying in Control with Archiva. Once populated, this can allow you to work without an Internet connection whenever necessary.

If you are behind a firewall and need to use an HTTP proxy, you will need to edit the <M2_HOME>/conf/settings.xml file, following the template for instructions on how to add the HTTP proxy configuration. This file can also be copied to your home directory, under <USER_HOME>/.m2/settings.xml, if you would prefer the settings to be used when you install a different version of Maven.

Tip

Troubleshooting

If you have any further problems with Maven failing as it attempts to download these components as you work through this chapter, check out the Appendix A, Troubleshooting Maven later in this book.

Creating a new project from an archetype

Maven makes it very simple to start a new project with archetypes. An archetype is a template project for a particular type of module, which ranges from a simple JAR or WAR module to a more complete template application for many popular frameworks. Archetypes extend the Maven concept of building on conventions by allowing them to be shared in reusable chunks. In Chapter 11, Archetypes, we will discuss how you can create and publish your own archetypes to establish reusable conventions for starting modules and projects in your own organization.

In this chapter, we will create a simple Java web application from an archetype to learn how to apply Maven's functionality to a sample project.

To start your sample project, run the following command from an empty directory to invoke Maven's archetype plugin:

$ mvn archetype:generate

Tip

You may have seen a command similar to archetype:create used in examples elsewhere. This is an older, now deprecated, form of the archetype goal which can be replaced by a corresponding archetype:generate goal.

If this is the first time you have run the archetype plugin, you will notice that Maven spends some time downloading the archetype functionality as well as the other Java libraries that it uses. These files are stored in your local repository for re-use in the future.

The local repository consists of both a local cache of downloaded libraries and a place to store your own projects as they are built, before they are published for other users. By default, the local repository is stored in the .m2/repository subdirectory of your home directory, which can be deleted at any time if necessary.

The local repository acts as an intermediary between your builds and a Maven remote repository, which is a canonical shared storage area for builds that is commonly hosted on an HTTP server, but can also be accessed using a local filesystem—FTP, SSH, and more.

One particular remote repository that is of note is the Maven central repository. Included by default in all Maven builds, and it contains not only Maven's plugins and their required dependencies but also a large host of releases of other open source projects.

On your machine, there is only one single local repository. You can connect to any number of remote repositories for a single build if needed. It is important in all Maven projects to carefully select the remote repositories to be used and to manage your own remote repositories for sharing artifacts among projects. This will be discussed more in detail in Chapter 7, Maven Best Practices.

Once the downloads finish from the earlier archetype command, you will be prompted (with a long list!) to select an archetype to generate the project from. A similar but abbreviated list is shown below:

Choose archetype:
1: internal -> appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate, Spring and JSF)
[...]
15: internal -> maven-archetype-quickstart ()
16: internal -> maven-archetype-site-simple (A simple site generation project)
17: internal -> maven-archetype-site (A more complex site project)
18: internal -> maven-archetype-webapp (A simple Java web application)
[...]
Choose a number:  (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44) 15: 

Among the many choices, the default is the quick start archetype that is used in Maven's getting started guide. This is a good choice for a minimal Maven project or an archetype from which new modules can be built (particularly for JAR projects).

However, in this example, we will be using the simple Java web application. So go ahead and enter the number corresponding to that archetype (at the time of writing it was 18) and press Enter.

Tip

A number of projects take advantage of Maven's archetype mechanism to deliver a template project for their framework. A sample of these can be seen at http://docs.codehaus.org/display/MAVENUSER/Archetypes+List. One in particular to note is Appfuse (http://appfuse.org/), which assembles ready-to-use stacks of popular combinations of open source web application frameworks, and is based on Maven's archetype technology. More information on these can be found in Chapter 11, Archetypes.

Each Maven project needs some coordinates to allow it to be identified by other Maven projects. These are called the group ID, artifact ID, and version (sometimes referred to as the GAV).

  • Group ID: An identifier for a collection of related modules. This is usually a hierarchy that starts with the organization that produced the modules, and then possibly moves into the increasingly more specialized project or sub-projects that the artifacts are a part of. This can also be thought of as a namespace, and is structured much like the Java package system.

  • Artifact ID: The Artifact ID is a unique identifier for a given module within a group.

  • Version: The version is used to identify the release or build number of the project.

The archetype plugin prompts you for these coordinates to add to the project after you have chosen the type of archetype to use. We can use the following values for our sample web application:

Define value for groupId: : com.effectivemaven.chapter01
Define value for artifactId: : simple-webapp
Define value for version: 1.0-SNAPSHOT: : 1.0-SNAPSHOT
Define value for package: : com.effectivemaven.chapter01 

The final prompt for a package is the Java package that will be used for source code, as this is a sample Java web application. It is not the packaging type that you want the project to take as that is provided by the archetype itself.

Note

You may have noticed that the version is referred to as a snapshot. In Maven, there are two types of versions: releases and snapshots (those that end in -SNAPSHOT). A snapshot should always be used by your projects during development as it has a special meaning to Maven to indicate that development is still occurring and that the project may change. A release is assumed never to change, so release versions (such as 1.1, 2.0, or 3.0-beta-5) should only be used for a single state of the project when it is released, and then updated to the next snapshot.

After entering the values above, you will be asked to confirm that the values are as you intended, and then the project will be created. If you see this highly desirable banner, then congratulations—you've created a Maven project!

[INFO] ------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------

The created project will be stored in the simple-webapp subdirectory and should look as shown next:

`-- simple-webapp
    |-- pom.xml
    `-- src
        `-- main
            |-- resources
            `-- webapp
                |-- WEB-INF
                |   `-- web.xml
                `-- index.jsp

As far as web applications go, that is as simple as it gets. The src/main/webapp directory contains the basic web application, while pom.xml specifies the information Maven needs to know. This directory structure has followed the Maven convention for web applications, which means that only a minimal amount of configuration will be needed to construct a functional build. If you open pom.xml, you will see the following:

<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>com.effectivemaven.chapter01</groupId>
 <artifactId>simple-webapp</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>simple-webapp Maven Webapp</name>
 <build>
  <finalName>simple-webapp</finalName>
 </build>
</project>

As you can see, the archetype has substituted in the coordinates that it prompted for earlier. It has also added the war packaging type to indicate that the build structure for the project is a web application. A default name has been added, which you would typically change to something which describes the project better.

This project file is sufficient to perform a large number of operations. For example, it can be used to assemble the web application archive or run the web application on a development server. Later, when we add some Java source code to the project, the shown configuration is enough for Maven to compile and include it in the web application, and even produce a source cross reference and Javadoc, if requested.

This is possible because Maven's declarative project model defines a number of well-known properties that the whole build can reuse for a number of different purposes. For this reason, the project file is referred to as the POM, which stands for Project Object Model.

The POM contains important pieces of information about the project, which include:

  • The Maven coordinate of the project for reuse by other Maven projects

  • The project name, description and license

  • Project resource information such as the location of source control, issue tracking, and continuous integration

  • The developers, contributors and organizations participating in the project

The POM will also include information about how the project should be built, such as:

  • The source directory layout

  • Dependencies on other projects

  • Build requirements (by means of Maven plugins) and configuration

Maven projects use the concept of inheritance. This means that repetition can be avoided by sharing information in a common ancestor of a project. Such parents of the project are POMs themselves.

Nevertheless, let's take one step back—what does Maven mean by "a project"? In software development, the term project is very ambiguous—it could mean just one part of a larger application, a plan for one release of a particular subset of an application, or it could be the entire application itself.

In Maven, the term project refers to a unit of work. That is, it takes one particular set of sources and produces a build artifact identified by the project's coordinate. While a conventional project has a single build artifact, it is possible to attach more artifacts that are alternate derivatives of the same sources in some limited ways. It is also worth noting that a POM can be both an artifact in itself, and a piece of metadata associated with a particular artifact file.

While there are a number of fields in the project file that can be used to describe your project, or change defaults such as the location of source code, Maven defines a number of conventions that reduce the amount of configuration needed to get a standard project up and running. For example, this section is inherited into every Maven project as the default build paths:

<build>
 <directory>${project.basedir}/target</directory>
 <outputDirectory>
  ${project.build.directory}/classes
 </outputDirectory>
 <finalName>
  ${project.artifactId}-${project.version}
 </finalName>
 <testOutputDirectory>
  ${project.build.directory}/test-classes
 </testOutputDirectory>
 <sourceDirectory>
  ${project.basedir}/src/main/java
 </sourceDirectory>
 <scriptSourceDirectory>
  src/main/scripts
 </scriptSourceDirectory>
 <testSourceDirectory>
  ${project.basedir}/src/test/java
 </testSourceDirectory>
 <resources>
  <resource>
   <directory>
    ${project.basedir}/src/main/resources
   </directory>
  </resource>
 </resources>
 <testResources>
  <testResource>
   <directory>
    ${project.basedir}/src/test/resources
   </directory>
  </testResource>
 </testResources>

Various other settings are also pre-configured, including additional paths needed for certain packaging types such as the web application source directory we saw previously. Together these form the base of Maven's project conventions.

While each setting can be easily customized in an individual Maven project, the strength of following the conventions is that the build remains simpler and more familiar to someone new to a specific project.

For more information on what is available in the POM, refer to the POM Reference documentation at http://maven.apache.org/pom.html. We will also revisit the recommended practices for what information to include in the POM in Chapter 7, Maven Best Practices.

Building the project

The vast majority of the time that Maven is run, it is to build a project. So let's see how to do that for the simple web application we have created.

In Maven, the build is run using a predefined, ordered set of steps called the build lifecycle. The individual steps are called phases, and the same phases are run for every Maven build using the default lifecycle, no matter what it will produce. The build tasks that will be performed during each phase are determined by the configuration in the project file, and in particular the selected packaging.

Some of the most commonly used lifecycle phases in the default lifecycle are:

  • validate—checks build prerequisites

  • compile—compiles the source code identified for the build

  • test—runs unit tests for the compiled code

  • package—assembles the compiled code into a binary build result

  • install—shares the build with other projects on the same machine

  • deploy—publishes the build into a remote repository for other projects to use

The following is a complete list of phases available in Maven 2.2:

validate, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, test, prepare-package, package, integration-test, verify, install, deploy.

The phases are designed to be run in sequence so that they can depend on the results of the previous phases. For example, choosing to run the test phase will first run validate, compile, and the other intermediate phases to ensure that the compiled code is up-to-date before running the tests.

To see the build lifecycle in action, run the following command from the directory that contains pom.xml:

simple-webapp$ mvn package

Again, if this is the first time you have built this project, Maven may download a few more JAR files to the local repository. A few seconds later, it should complete successfully, with an output that looks similar to the following:

[INFO] Scanning for projects...
[INFO] -------------------------------------------------------
[INFO] Building simple-webapp Maven Webapp
[INFO]    task-segment: [package]
[INFO] -------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] No sources to compile
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [surefire:test]
[INFO] No tests to run.
[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[simple-webapp] in [/Users/brett/code/01/simple-webapp/target/simple-webapp]
[INFO] Processing war project
[INFO] Copying webapp resources[/Users/brett/code/01/simple-webapp/src/main/webapp]
[INFO] Webapp assembled in[78 msecs]
[INFO] Building war: /Users/brett/code/01/simple-webapp/target/simple-webapp.war
[INFO] -------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] -------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Aug 30 22:20:59 EST 2008
[INFO] Final Memory: 7M/14M
[INFO] -------------------------------------------------------

As you can see, the build has gone through a number of tasks, such as [compiler:compile], [war:war], and so on. These tasks are known as goals, and they are attached to the build lifecycle to give the build substance. Behind the scenes, Maven knows when to run all of these different goals in the build lifecycle based on the project type. In the case of a web application, it may have resources to include, Java source code to compile, and tests to compile and execute before finally being packaged. Even though the project doesn't currently contain all of these elements, Maven runs through the goals so that they are able to check their own input and process them if they are found.

In the last step of the build above, the war:war goal is run to package up the web application, including the resulting output from the earlier goals, as well as the content in the default location of src/main/webapp. The web application archive is produced in the target directory, which is Maven's default working directory. There you will find both the target/simple-webapp subdirectory that contains the exploded, or unpacked, web application contents ready to be packaged, and the target/simple-webapp.war file that contains those files after the packaging process.

Note

Notice that the filename simple-webapp.war omits the version number. As a best practice, Maven appends the version number to the archive by default, but in this case the project file specified a finalName that did not include the version number. This is handy for web applications as it is common to use the filename as the context path when it is dropped into a servlet container.

By laying out a standard pattern for builds through the build lifecycle, Maven is able to achieve a number of objectives. They are as follows:

  • Developers do not need to re-learn a new process each time they move to a new project, as the phases are the same across Maven projects.

  • It is easy to incorporate new tools into the lifecycle by binding them to certain phases, without any knowledge of which other tools and plugins have already been incorporated.

The separation of the build order from the selection of goals is important in incorporating new tools without repeating configuration or having to introduce new variables. Take source generation for example—the Compiler plugin knows that it can take a list of source directories from the project model and compile them into the work directory. However, it cannot know all of the various source generation mechanisms that it might need to run first to ensure that the sources are available to compile. Instead, we bind any source generation goals to the generate-sources phase of the lifecycle, which is guaranteed to occur before the compile phase, and insert any created sources into the project model for compilation.

In addition to the default build lifecycle we have seen above, it is worth noting that Maven has two other lifecycles built-in:

  • Clean lifecycle: The command mvn clean is often used to remove Maven's work directory target. However, as some plugins can generate information that does not reside in the work area, it is possible for them to bind to the clean lifecycle to clean up after themselves as well. To help with ordering, the complete list of phases in the lifecycle is pre-clean, clean, and post-clean.

  • Site lifecycle: While much of the site generation is coordinated by Maven's reporting mechanism, it is also possible to bind plugins to be executed as part of the site generation lifecycle. The phases available are pre-site, site, post-site, and site-deploy.

Reusing builds

Previously, we ran the command mvn package. If you just needed to produce a web application to test manually, this would be sufficient. However, what if you need to include it in a Java EAR file, or to build a self-contained distribution that includes the web application along with a servlet container? More commonly, what if you were building a JAR project instead that needed to be used by another Java project?

In Maven, instead of passing relative links to other projects and files in the build, projects share their build products in the form of build artifacts, with the mechanism for sharing these artifacts being the Maven repositories that we touched on earlier. We have already seen that Maven downloads remote dependencies to the local repository, and we can see here that this is also where Maven places artifacts to share with other builds that you run yourself from the same machine. Note that the local repository is not intended for sharing among multiple users, however, when it comes to publishing a build for other developers or projects to use, a remote repository is used instead.

To install the web application we have created already into the local repository, run the command in the folder that contains the pom.xml file:

simple-webapp$ mvn install

Tip

This is the command (or perhaps mvn clean install) that you will run in almost all cases while building Maven projects.

First, notice that thanks to the build lifecycle this runs everything that mvn package did first—it is not necessary to run both. However, this time, it runs an additional step:

[INFO] [install:install]
[INFO] Writing transformed POM using encoding: UTF-8
[INFO] Installing /Users/brett/code/01/simple-webapp/target/simple-webapp.war to /Users/brett/.m2/repository/com/effectivemaven/chapter01/simple-webapp/1.0-SNAPSHOT/simple-webapp-1.0-SNAPSHOT.war

As you can see, the web application has been copied into the local repository. This file is now available as a dependency to other projects.

Note

You might notice that the filename now includes a version again—this is always the case for files in the local repository as it follows a predefined format to allow Maven to access the files again later. The format starts with the group ID split into paths, followed by the artifact ID and version as separate paths, and finishing with the filename consisting of the artifact ID, version, and extension for the given packaging. Therefore, the finalName configuration we used earlier is only suited for manipulation of a file within that project's build.

It is worth noting that Maven has one step beyond install, called deploy. This does lead to some confusion, as it does not refer to deployment of an application into a development or production server, but rather uploading the build product into a remote repository. While this can be used as a location to deploy your applications into a server from, that is not the direct purpose of Maven's deploy step.

We will look at remote repository deployment later in the book. For now, let's focus on improving the project we have been constructing.

Adding dependencies

In any non-trivial project, it is unlikely (and unwise!) for the programmer to write every single piece of code that it will use and bundle it all up in one large all-or-nothing build. This is particularly true for a web application where generally only the web-based functionality is part of the project, with business logic contained in separate libraries. For this reason, one of the first features of Maven you will find yourself using is the dependency mechanism.

A dependency is simply a way of expressing that the current project requires another project in order to build or run in some way, using the Maven coordinates to locate it. The dependency may be another project you are building at the same time, another project you have already built on the current machine (shared from the local repository), or a project built by another team member or a third party (downloaded from a remote repository).

Let's see how this works with a simple example using Java source code. For this example, create a source file called src/main/java/com/effectivemaven/chapter01/ExampleAction.java with the following content:

package com.effectivemaven.chapter01;

import org.slf4j.*;

public class ExampleAction {
    final Logger logger =
        LoggerFactory.getLogger(ExampleAction.class);
    public boolean execute() {
        logger.info( "Example action executed" );
        return true;
    }
}

This is a very trivial example, but you will notice that the third party library SLF4J (http://slf4j.org) is used. With this code added, we will try compiling the project. In this case, we just run until the compile step, which includes the same steps as in the earlier calls to package and install, but stops once the sources are compiled.

simple-webapp$ mvn compile

However, you will notice that this time, the build fails with an error:

[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /Users/brett/code/01/simple-webapp/target/classes
[INFO] -------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] -------------------------------------------------------
[INFO] Compilation failure

/Users/brett/code/01/simple-webapp/src/main/java/com/effectivemaven/chapter01/ExampleAction.java:[3,0] package org.slf4j does not exist

/Users/brett/code/01/simple-webapp/src/main/java/com/effectivemaven/chapter01/ExampleAction.java:[6,10] cannot find symbol
symbol  : class Logger
location: class com.effectivemaven.chapter01.ExampleAction
/Users/brett/code/01/simple-webapp/src/main/java/com/effectivemaven/chapter01/ExampleAction.java:[7,8] cannot find symbol
symbol  : variable LoggerFactory
location: class com.effectivemaven.chapter01.ExampleAction

Here, compilation has failed because the SLF4J library has not been added to the build. Maven makes including this very easy, by adding the dependency to the project file pom.xml:

 [...]
 <dependencies>
  [...]
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.5.0</version>
  </dependency>
 </dependencies>

Its Maven coordinates identify the dependency—the group ID org.slf4j, the artifact ID slf4j-api, and the version 1.5.0. Due to its Java heritage, Maven defaults to looking for JAR files, so the type does not need to be given (though for other types it can be). This is enough to retrieve and utilize the SLF4J library, and as we will see, much more.

Tip

What if I don't know the Maven coordinates?

The above case may seem simple, but it might be hard to guess the values to use if you didn't know them! While occasionally libraries will publish the Maven coordinates to use in their documentation and examples, often you will need to find out what they are yourself. In Chapter 2, Staying in Control with Archiva, we will learn how to use Archiva to search for the right library based on free text, a missing Java class, or a particular JAR file that you already have.

With the dependency in place, let's try to compile the project again:

simple-webapp$ mvn compile

If all things have gone well, SLF4J will be downloaded and the build will succeed. The generated class files will be produced in the target/classes subdirectory.

This is very useful, but adding the dependency has done more than just let us compile the project. Try packaging the artifact again:

simple-webapp$ mvn package

This process will follow the same steps as it did before, but with the addition of the new class file and the dependency to the resulting web application. Firstly, the compiled sources are placed in target/simple-webapp/WEB-INF/classes (in addition to the main target/classes area). Secondly, the dependency added has been copied to target/simple-webapp/WEB-INF/lib. This is because the Maven WAR plugin now knows that it will be required to run the web application by reading the project's dependencies. Of course, these files are also packaged into the resulting WAR file.

We will see more examples of how the dependency definition is reused by other plugins throughout this chapter.

It is also worth noting that while this pattern is the most common way to express dependencies, Maven has a number of sophisticated ways to manage which dependencies are used, how they are used, and what version should be used. The SLF4J dependency we used is very simple, but other dependencies will have dependencies of their own. When you use such dependencies in the future, you will notice that Maven resolves these transitive dependencies as well.

Some of these concepts will be covered in the dependency management section of Chapter 7, Maven Best Practices.

Adding functionality through plugins

The basic model presented above will construct a functional project for a number of different built-in packaging types. However, most projects use a variety of technologies that require additional steps at build time. In addition, there are a vast array of development specific tools that have been integrated with Maven using its plugin mechanism.

We saw how plugins are used several times throughout the introductory walkthrough. In some cases, they are explicitly declared in the POM, sometimes they are implied through the default build for a given packaging type, or, in the case of the archetype plugin they are called directly from the command line without a corresponding project.

A plugin is a self-contained piece of reusable functionality for incorporating into the lifecycle of one or more Maven projects. There are two main types of plugin in Maven: build plugins, used for all tasks related to the build process of the project; and reporting plugins, used for obtaining a visual representation of the state of the project.

Plugins are typically written in Java, but it is possible to write them in a number of interpreted languages, or other languages that will run on the JVM. Through external execution, it has even been possible to write plugins in other languages such as C#.

Maven obtains plugins in the same way as it retrieves other project dependencies; by downloading their artifact and associated dependencies from a remote repository.

The first time you may encounter this is if your code targets Java 5. In another of its conventions, Maven's Java compilation defaults to producing code that is compatible with running on older versions of Java. This means that if you use source code that uses the new features such as generics, you will get compilation failures.

Tip

The Maven developers are currently discussing upgrading the default source and target versions to Java 5, so in a future version of Maven the following example may work without the additional configuration.

You can try this for yourself by adding a line to the ExampleAction.java class created in the previous example:

    private java.util.List<String> values;

If you compile this source code, the following error will appear:

/Users/brett/code/01/simple-webapp/src/main/java/com/effectivemaven/chapter01/ExampleAction.java:[9,26] generics are not supported in -source 1.3
(try -source 1.5 to enable generics)
    private java.util.List<String> values;

Therefore, it is necessary to pass the -source and -target parameters to the Java compiler. The goal that was running when the error occurred was compiler:compile, so we know that the plugin we are concerned with is the compiler plugin, and could look up its configuration page online (this particular plugin is listed at http://maven.apache.org/plugins/maven-compiler-plugin/). There you will find a reference for available configuration options for the compiler:compile goal, as well as examples for this specific example and others.

Let's add the compiler plugin within the build section of pom.xml as suggested on that page:

<build>
 <finalName>simple-webapp</finalName>
 <plugins>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>2.0.2</version>
   <configuration>
    <source>1.5</source>
    <target>1.5</target>
   </configuration>
  </plugin>
 </plugins>

As you can see, a plugin declaration is much like a dependency declaration—the Maven coordinates of the plugin are used. In fact, the plugins are retrieved in an identical way to any dependencies you use, including being stored in your local repository.

Tip

Specifying plugin versions

Even though you will see many examples where this is not the case, it is always a good idea to specify the version of the plugin you are using to ensure the build will be reproducible later! Without a version number, Maven will look for the latest available version, and should the plugin's behavior change then your build might change unexpectedly too.

The addition of the plugin achieves the following in a Maven build:

  • Makes all the goals in the plugin available to run from the command line for the project. However, note that some plugins that are designed to run standalone, such as the archetype plugin we used earlier, can be run without needing to be added to a project.

  • Makes any new packaging and dependency types for the project available (this requires that you declare the project with the <extensions>true</extensions> tag as well).

  • Allows the definition of additional goals to run in the default build.

  • Allows the configuration of the goals within the plugin.

It is this last feature that we have taken advantage of with the compiler plugin. The specified source and target will be used by the compiler:compile goal in the plugin, and passed on to the Java compiler that is executed.

With the configuration declared in the way that it has been, it will actually be passed on to all goals that are used from that plugin—for example, the compiler:testCompile goal that is automatically used to compile Java test source code will take the same parameters. For this reason, you will notice that we don't encounter the same compiler error we received earlier when Java 5 annotations are used in the next section on running unit tests.

Being able to configure plugins that are already used opens up a number of variations on how the project can be built, but what about adding entirely new pieces of functionality to the default build?

This is achieved with the executions section of a plugin definition. This allows the addition of a goal or goals with a given configuration to a particular stage of the build.

As an example, let's look at how to add a rule enforcement to the build. Let's say that because we chose to use SLF4J already, we want to make sure that none of the dependencies added in the future add the Commons Logging implementation into the web application via their transitive dependencies.

To do this, we must add the enforcer plugin into the plugins section of pom.xml:

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-enforcer-plugin</artifactId>
 <version>1.0-beta-1</version>
 <executions>
  <execution>
   <id>enforce-dependencies</id>
   <goals>
    <goal>enforce</goal>
   </goals>
   <configuration>
    <rules>
     <bannedDependencies>
      <excludes>
       <exclude>
         commons-logging:commons-logging
       </exclude>
      </excludes>
     </bannedDependencies>
    </rules>
   </configuration>
  </execution>
 </executions>
</plugin>

Here, there are three parts of the execution to note. Firstly, there is an identifier id given. This is not mandatory when there is only one execution (it will use the default identifier in this case), but it is always recommended. It is used to give a visual clue to the execution that is running when multiple executions for a single plugin are provided. It also acts as a key for Maven's inheritance mechanism if a subproject wants to modify the configuration of the execution.

Secondly, a list of goals is given. In this case, we only have one goal to add—enforce. However, it is possible to list multiple goals from the same plugin if applicable.

Finally, we have the configuration for the goals. In case of the enforcer plugin, this is a list of rules to execute. In the above example, we have a single rule, called bannedDependencies, which as the name suggests takes a list of dependencies to ensure never occur within the project's dependency tree. The configuration given will be applied to all goals in this execution, and will be combined with the configuration that applies to the whole plugin, as shown in the previous section. This allows you to configure just a subset of goals, or all uses of a particular setting, as appropriate.

Note

It is only possible to declare a plugin once in a given project file. If you need to run a goal a number of times, with or without different configuration, you must include multiple executions within the same plugin definition. This will also allow you to place multiple executions of a goal in different phases of the build lifecycle if necessary.

You may have noticed that in the configuration, the library is referred to as commons-logging:commons-logging. It is common to refer to dependencies using the format groupId:artifactId or even groupId:artifactId:version when you are only able to use a single string. We will use this convention throughout the rest of this book to simplify the text.

If we now proceed to build the project, the following line will appear towards the start of the build thanks to the enforcer plugin:

[INFO] [enforcer:enforce {execution: enforce-dependencies}]

Because we are not using Commons Logging, there will be no error and the build will continue as before. However, if you'd like to test that the plugin works correctly, try adding commons-logging:commons-logging:1.1.1 as a dependency and build the project again.

At this point, you might be wondering how Maven decided to run the goal at the beginning, as the plugin didn't specify anything about when to run the goal. As it turns out, running in the validate phase (at the beginning of the build) is the default in the enforce goal, as it usually makes sense to enforce rules before wasting any time processing the rest of the build. Not all goals specify a default, however, and regardless you may wish to modify when the goal runs. If that is the case, you would add the phase parameter to the execution, and all goals in that execution would then be run in that stage of the build instead of the default.

For example, to check the dependencies only after packaging, you could add the following:

<execution>
 <id>enforce-dependencies</id>
 <phase>package</phase>
 ...

The order that executions appear in the POM is retained, as the execution of the goal will occur after any goals already in the package phase, including those earlier in the current project file, inherited from a parent project, or declared in the default lifecycle for the current packaging type.

Tip

It's worth stating here that there was no intent to pick on Commons Logging with the previous example! Though it may have had a past reputation for being hard to handle in many class loading scenarios in the past, recent versions seem to have improved that situation. Nonetheless, our example simply points out that multiple logging frameworks are often not what you have intended for a single web application, and the enforcer plugin is a useful way to ensure this remains the case.

One additional note: if this particular example were to be used in a real project, should a library you introduce require Commons Logging and you choose to exclude it in favor of SLF4J, you will need to include the jcl-over-slf4j dependency from SLF4J instead.

Adding resources

In addition to source code, Maven uses the concept of resources. A resource is a file that will be accessed by the code at runtime but is not required for compilation, and is typically packaged in the artifact that is built. Common examples are static configuration files and localization resources.

The standard build lifecycle for a web application already includes the [resources:resources] command to copy any present resources from the default location of src/main/resources into the final web application. Let's try that with the sample application by creating src/main/resources/Application.properties as follows:

first.greeting=Hello, World!

Now, run the build again:

simple-webapp$ mvn package

We will now see that the properties file is present in both target/classes and target/simple-webapp/WEB-INF/classes. How the resource is packaged depends on the type of packaging. In Java, a resource will always be accessible from the root of the class loader at runtime. As such, it will be packaged in the root of a JAR file, or in the WEB-INF/classes directory of a web application.

Other types of content resources, such as web application content, are specified by different resource paths in an individual plugin's configuration, rather than using the resources within the build section of the POM.

Resources can also be filtered by substituting values into the files under specific build conditions. This can be useful for centralizing the specification of certain values but comes with a similar number of pitfalls and common misuses that will be discussed in Chapter 7, Maven Best Practices.

Running tests

You may have noticed as builds have run that Maven tried to execute the goal surefire:test and found no tests to run. Unit testing is a fundamental concept of Maven, built-in to the default lifecycle for all code project types, and run on the majority of builds to encourage the practice of not only writing unit tests, but writing fast unit tests.

There are two steps needed to add unit tests to this project:

  1. Add a dependency for the test library you have chosen to use.

  2. Add the test source code to your project in the src/test/java subdirectory.

Let's add a simple test case to illustrate this. Firstly, we will add the dependency on the test library. You may see many Maven projects (and particularly those created from basic archetypes) using JUnit 3.8.1—undoubtedly, the most widely used testing framework today (even in comparison to newer versions of JUnit!). However, in this book, we will be using TestNG (http://testng.org) as the test library. The dependency declaration for TestNG looks like this:

<dependency>
 <groupId>org.testng</groupId>
 <artifactId>testng</artifactId>
 <version>5.8</version>
 <classifier>jdk15</classifier>
 <scope>test</scope>
</dependency>

You must have noticed the two new entries in this declaration. At the end was the scope of the dependency, which was set to test. This is an important value to set as a hint to the Maven dependency mechanism for a few reasons. The scope is used to control which dependencies are passed to each plugin so that they get the most appropriate set for the function they are performing. You will notice if you rebuild the application that TestNG does not end up in the web application like SLF4J did, because the WAR plugin does not bundle testing dependencies. Likewise, if you try to use TestNG in your main application source code, it will fail. This is because the compiler plugin does not use testing dependencies to build the compilation classpath.

Note

Not all IDEs support this level of separation between testing dependencies and build dependencies, even when configured with Maven integration. If you find a build is succeeding in your IDE and failing in Maven due to a missing library, you may need to review the scopes.

You may have noticed earlier that the scope wasn't specified for SLF4J as the default is compile, which makes the dependency available to all plugins. If that naming seems confusing, think of the primary scopes (compile, runtime, and test) as a funnel: you will need all the dependencies you compile with to run the application, and all the dependencies you run with to test the application, but the reverse is not true.

The other new entry was a classifier. While a single Maven project is designed to handle one set of source files and to produce one output, it can be necessary to produce multiple files that are closely related to the same project. In the case of TestNG, different versions that are suitable for JDK 1.4 projects and JDK 5+ projects are made available, but they are based on the same source project (that is, testng-5.8.pom is the Maven project downloaded in either case).

Now, with the dependency in place, let's add a simple test case in src/test/java/com/effectivemaven/chapter01/ExampleActionTest.java:

package com.effectivemaven.chapter01;

import org.testng.annotations.*;

public class ExampleActionTest {
  private ExampleAction action = new ExampleAction();

  @Test
  public void executionSucceeds() {
    assert !action.execute();
  }
}

We have again taken advantage of Maven conventions here—by naming the class with Test suffix the Maven testing plugin (called Surefire) will use it to run test cases instead of considering it as just a test support class. Of course, these can all be configured using configuration for the given plugin as demonstrated in the previous section.

Let's try running this test:

simple-webapp$ mvn test

This will run the whole build including the testing stage.

Unfortunately, the build will fail rather spectacularly with an error in the middle, similar to this:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

We didn't think far enough ahead here—we chose to compile against slf4j-api, which was a good choice, but we didn't add a required SLF4J logging implementation to use when the code is executed. What we have seen here is the SLF4J library complaining about not being provided with an implementation to use.

To avoid this error, add the org.slf4j:slf4j-simple:1.5.0 dependency, with a scope of runtime to the Maven project file.

Tip

Logging libraries

Here, we chose to add the logging implementation with a scope of runtime, which will mean that it is bundled in the web application of this sample application as well as being available for the tests. This may or may not be what you desire, as some servlet containers will provide logging libraries for web applications already.

Additionally, in the case of logging for an application library that will be reused by others, the runtime scope will pass on your choice of implementation and so again should be used with care.

For examples such as this, and particularly logging libraries, take care with the scope you select. If you would like to defer the decision on which implementation to use, just use the test scope to provide that implementation to the test cases without passing it on to the final build product or other projects that depend on the current project.

With the new dependency in place, we will try running the tests again. You should still get a failure, but this time, a little more like you might have expected:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
221 [main] INFO com.effectivemaven.chapter01.ExampleAction - Example action executed
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.5 sec 
<<< FAILURE!

Results :

Failed tests: 
  executionSucceeds(com.effectivemaven.chapter01.ExampleActionTest)

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

Here we see that the test (which was written to fail) has triggered TestNG to fail the Maven build. However, this output doesn't say exactly where or what the failure was. For this, you would look in the target/surefire-reports directory. Among other files, TestNG will have generated both—TestSuite.txt (a text representation of the test results) and index.html (a browsable version that may be easier to navigate, especially if there are a large number of tests or errors).

Tip

Other test frameworks will store their results in the same directory, but possibly in different filename formats. For example, JUnit 3.8.1 will generate text files for each test class, example com.effectivemaven.chapter01.ExampleActionTest.txt.

The result file should point out the failure, which in this case is that the condition on the assertion should be reversed:

  public void executionSucceeds() {
    assert action.execute();
  }

With this change made, we can run the tests again to see that the build has finally succeeded.

The example code and test case here may have been a little simplistic, but it does show that it is easy to add unit tests to a Maven project. With this small amount of configuration in place, all that is required is to write test cases and place them in the right location to have Maven run them automatically. We haven't seen it yet, but this has also laid the groundwork for more advanced development scenarios, such as measuring test code coverage.

Getting help

Sometimes, it is necessary to inspect what is going on inside Maven when you need to make a configuration change or are troubleshooting a problem. In these cases, some assistance can be provided by the help plugin.

To see the available goals, run the following:

$ mvn help:help

The selection available covers a variety of needs in telling you about your build or your environment.

For example, one of particular use is to determine which version of a particular plugin is actually being used. This information can be obtained through a command such as this:

$ mvn help:describe -Dplugin=compiler

The plugin variable given is the short name, or the prefix, for the plugin shown in the goal names as it is executed or would use from the command line. If this is not specific enough, you can use the full coordinate of the plugin:

$ mvn help:describe -DgroupId=org.apache.maven.plugins \
    -DartifactId=maven-compiler-plugin

The resulting output will show the basic information available for the plugin, which in this example would be:

[INFO] Plugin: 'org.apache.maven.plugins:maven-compiler-plugin:2.0.2'
-----------------------------------------------
Group Id:  org.apache.maven.plugins
Artifact Id: maven-compiler-plugin
Version:     2.0.2
Goal Prefix: compiler

It isn't always convenient to visit the Maven web site to access the reference documentation for a plugin to find out the correct way to configure a particular plugin and so another use of the help:describe goal is to get that information directly from the plugin:

$ mvn help:describe -Dplugin=compiler -Ddetail

Some of the other goals of the help plugin are described in Appendix A, Troubleshooting Maven, later in this book.

Enhancing the development process

The examples so far have shown how to use Maven to automate the build process. However, the information that is already available can also be of use to other types of plugins, which can be of assistance throughout the development process.

While being able to build and install the web application from this chapter into the local repository is useful, deploying that into a running server after every change would be tedious. Many IDEs offer easier ways to manage this step, but you may wish to reuse the setup from Maven instead.

A particularly relevant example for a web application is the Jetty plugin. Jetty (http://jetty.mortbay.org/) is a small, fast, and fully compliant Java servlet container that is particularly suited to being embedded in other applications. We will use Jetty's Maven plugin to run the web application in a Jetty container.

First, we need to add the Jetty plugin to the list of plugins in the project so that the goals will be available from the command line. At the time of writing, the Maven coordinate for the plugin is org.mortbay.jetty:maven-jetty-plugin:6.1.14 (though in version 7.x and above it is expected to change the artifact ID to jetty-maven-plugin). Once that is added, starting the server with the web application is simply a matter of running the following command:

simple-webapp$ mvn jetty:run

The server will start on port 8080, and you can immediately browse the application at http://localhost:8080/simple-webapp/.

The web application is pre-configured from the defaults and values from the project file. This means that you can edit the content under src/main/webapp, and see it reflected in the running web application without a restart.

The Jetty plugin has a large number of configuration options to help you get more out of this environment, which can be found in the Jetty documentation. Because it is using the Maven project model, it can also easily be configured to watch for changes to the source code and dependencies in Maven's local repository, and then recompile and reload the web application on the fly. It is also possible to configure the Jetty server for more complete development options, for example to add JDBC data sources.

Tip

Even if you are deploying to a different servlet container, it is still useful to be able to test quickly from the Maven environment using Jetty. However if this is the case, be sure to also test against your target servlet container. We will examine this topic in Chapter 4, Application Testing with Maven.

Viewing detailed project information

We have seen many ways to reuse the information stored in the Maven project, but one of the most powerful has yet to be explored. Maven has a documentation rendering framework and large set of plugins that will not only present information about the current project, but will also allow the integration of information from a number of external tools into a one-stop-shop project developer's mini-site.

To get a taste of this now, try the following command:

simple-webapp$ mvn site

For the basics, no further configuration is needed as the values from the Maven project model are reused. You can now review the content by opening target/site/index.html in a browser.

Working effectively with Maven sites, reports, and related practices will be examined in more detail in Chapter 5, Reporting and Checks.

Tip

As you may have noticed, this site is rough. Poorly thought out, default Maven project sites can do more harm than good! Be sure to take care in considering what information you are trying to convey and where, then use Maven to make it easier to keep up to date and structure it, rather than relying on it to do all the work.

Multiple module builds

When we defined a Maven project earlier, we saw that it was described as a unit of work, typically producing a single output from a single source. However, many software projects are larger than that!

Maven was designed with this situation in mind, and so larger software projects can be composed of multiple Maven projects. This in itself is still referred to as a project, with the other projects that it is composed of referred to as its modules.

When multiple projects are built at once in Maven, the builder is referred to as the reactor. Whether building a multi-module project or a series of separate projects, the reactor collects the projects to be built and ensures the correct order is used based on any dependencies declared on other projects within the same reactor build.

We won't extend our simple web application to illustrate this at this stage, as we will see much more detail on Maven's support for multi-module projects in Chapter 3, Building an Application Using Maven.

What if I need to convert an existing project?

The example we have seen so far has been reasonably complete despite having to provide very little information, because of the pre-defined behavior of the Maven plugins and the conventions used.

That may make it easier to get started with a new project, but what if you wanted to convert to Maven from an existing project? Such a project probably doesn't follow the conventions already, or may have custom or slightly altered behavior to that provided by the default Maven plugins.

There is unfortunately no single way to convert a project from Ant or some other build system to Maven because they frequently use very different approaches.

A typical approach may look something like this:

  1. Ensure that the current build behaves like a good "Maven citizen" by publishing builds into a remote repository and starting to consume its dependencies from there.

  2. Restructure the existing build into modules and paths that match Maven conventions to simplify the Maven build.

  3. Build upon those conventions and the existence of a list of dependencies to produce a concurrent Maven-based build.

  4. Gradually migrate any remaining functionality until the Maven-build becomes the only one needed.

Before starting a conversion of any size however, it is best to familiarize yourself with how Maven works on a similar but new project, as this will help identify the best places to adopt Maven's standard practices in a build conversion, and where it will be easiest to retain custom behavior.

 

Summary


We have now seen a very quick, but comprehensive, introduction to the building blocks that all Maven projects use, and should be confident in putting it to use in a project.

In this chapter, we looked at how to create a new project or module and build some typical Java artifacts. We then looked at how to put it into use with various plugins, and the way to get more out of the Maven project model by generating a site and reports. We then covered some terminology and "theory" of how Maven works, and learned some tips for getting help when it is needed.

In essence, this is all you will ever need to know about Maven to use it well—everything else is about reapplying the same techniques to different applications and learning about the specifics of various plugins.

While some topics, notably profiles, project inheritance, the various management sections of the POM and multi-module builds, were not revised in this chapter, they will be covered in far greater depth in later chapters.

Now that we are confidently using Maven, one of the most important pieces of software anyone will use in a Maven-based infrastructure is a repository manager. Hence, in the next chapter Deng will introduce Apache Archiva, and explain how it addresses some fundamental concepts of repository management.

About the Authors

  • Brett Porter

    Brett Porter is a software developer from Sydney, Australia with a passion for development tooling, and automation. Seeking a more standardized and reproducible solution to organize, build, and deploy a number of software projects across teams, he discovered an early beta of Maven 1.0 in 2003, and has been heavily involved in the development of the project since. He is a member of the Apache Maven Project Management Committee, and has conducted presentations and training on Maven and related tooling at several conferences and events. He founded the Archiva project in 2005. Brett is also a member of the Apache Software Foundation.

    Brett is currently VP, Product Development at G2iX, in charge of the Maestro division. He and his team seek to make developers more efficient by offering support and services for development and automation tools including Apache Maven, Apache Continuum, Apache Archiva, and Selenium.

    Brett was co-author of the book Better Builds with Maven, the first book to be written about the Maven 2.0 release in 2005, and has been involved in reviewing Maven: A Developer's Notebook and Java Power Tools.

    Browse publications by this author
  • Maria Odea Ching

    Maria Ching grew up in a small town called Daet, then moved to Manila when she went to college. She took up Computer Studies at De La Salle University and graduated in 2005. She started using open source tools from her first job after graduating and from then on, got interested in everything open source. When she came to work for Exist, she got assigned in a project doing a lot of development work in open source projects, specifically Maven, Continuum, and Archiva. Back then, Continuum and Archiva (formerly named Maven Repository Manager) were still sub-projects of Maven. Eventually, she became a committer, then a PMC member of Maven. In 2008, Continuum and Archiva became top-level projects at the ASF and Deng was elected as PMC Chair of Archiva. She is still currently serving as PMC Chair of the project and as PMC members of Continuum and Maven.

    Browse publications by this author