The Maven 2 POM demystified

The evolution of a project model

1 2 3 Page 2
Page 2 of 3

Project information

POM information is only as useful as the plug-in that uses it. With this in mind, we will not drill down into the elements of this section in much detail. These elements are mostly self-explanatory and frankly uninteresting beyond their use for reporting plug-ins. The idea that they are used for reporting, however, does not automatically qualify them as build settings. The project information elements are merely used as part of a build process and not actively involved in configuring it. I know—it's a fine line. This batch of information, although not explicitly required to perform most builds, is designed to communicate to other humans some general information about the project. As illustrated in Figure 8, here is where the POM author can define the project's human-readable name, description, homepage (URL), inception year, licenses, the organization in charge of development, as well as the developers and contributors (with a little data about them too).

Figure 8. General project information

A big piece of the POM's bulk is this human-readable information. If you learn to use it, your documentation readers will thank you. I would never claim that this information is not important, just not worth looking into in an overview.

Build settings

Build settings is where the POM gets interesting, where we get to the real meat of it.

Figure 9. Project build settings

Half of Maven's power lies within the two elements build and reporting. "But there are four elements under build settings" (as shown in Figure 9), I hear you say. Quite so, and we shall quickly cover those two first: the packaging and properties elements.

Packaging

The packaging element describes to Maven what default goals to bind under the lifecycle and offers a hint of the project's type. If not specified, then packaging will default to jar. The other valid types are: pom, maven-plugin, ejb, war, ear, rar, par, and ejb3. These values are each associated with a default list of goals to execute for each corresponding build lifecycle stage for a particular packaging structure. For example, the jar packaging type executes the jar:jar goal during the build lifecycle's package phase, where the ejb packaging type executes ejb:ejb. For those who simply must know everything, these types are defined under the Maven core project's Plexus component settings. Each is a role-hint for the org.apache.maven.lifecycle.mapping.LifecycleMapping role.

Properties

The properties element is used throughout a POM and Maven plug-ins as a replacement for values. When a property is set, you may use the property name as a value in the form of ${name}, which will be replaced by the set value at runtime. Consider the following:

 <properties>
  <env.name>tiger</env.name>
</properties>

Now, wherever the property is used within the POM as ${env.name}, Maven will replace that string with the value tiger.

There are many other ways to set properties—through the command line, through the Maven settings.xml files, through filters. Most of these approaches are preferred over using the properties element. The simple reason being: if you insert a value into the POM, why use a property over the value itself? I find it best to avoid this approach except for testing purposes, for setting defaults, or for advanced use-cases (plug-ins injecting properties, or very rarely, long and oft-used static variables).

Build

In the simplest terms, the build element contains information describing how a project's build is to proceed when executed. It contains all sorts of useful information, such as where the source code lives or how plug-ins are to be configured. Much of this information is inherited from the super POM. Like most everything in the POM, you may override these defaults (though not generally recommended). Like elsewhere in the POM that expects paths, all directories not beginning with "/" or some other absolute path delimiter are relative to ${baseDir}—the directory where the POM lives. The build conventions defined by the super POM are show below:

 

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

... </project>

Besides setting a project's source and target directories, the build section configures plug-in executions, adds extensions to them, mucks with the build lifecycle, and has a role to play in POM inheritance via the dependencyManagement element.

Although Maven 2 is good at defining defaults, plug-ins should attempt to guess only so much information. At a certain point, they must be configured explicitly, which is mostly done through the configuration element. One of the more common plug-ins configured is the compiler plug-in. The maven-compiler-plugin defaults to compile Java code as J2SE 1.3-compliant, and must be configured for any other setting. For a project using Java SE 5, the plug-in would be configured as follows:

 <project>
  ...
  <build>
    <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>
  </build>
  ...
</project>

Note: You are not actually required to set groupId for Maven plug-ins with the groupId of org.apache.maven.plugins. These are considered to be special core plug-ins by Maven (just like org.apache.maven.plugins need not be specified when calling goals from the command line).

Although plug-in configuration is useful in and of itself, it does not afford you the granularity of control that is always required. Most plug-ins have multiple goals that you may execute—each with differing configurations. Moreover, you may wish to bind a goal to a particular phase (you cannot bind a whole plug-in to a phase). Enter the executions element: to give goal control to the POM designer.

For example, let's make our build echo the java.home property during the validate phase of the build lifecycle, just to be sure it is has been set correctly. Although we could write our own plug-in in Java and bind it to the validate phase, it would be far easier to use the antrun plug-in's run goal, bind it to the validate phase, and configure the plug-in to echo the property:

 <project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>echohome</id>
            <phase>validate</phase>
            <goals>
               <goal>run</goal>
            </goals>
            <configuration>             <tasks>
                <echo>JAVA_HOME=${java.home}</echo>
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

In the above code, we specified the antrun:run goal to be bound to validate, giving it a configuration to echo a task. In the POM, all XML within a configuration element is free text; it is up to the plug-in to decide what to do with the configuration data. This is a mixed blessing, however. It is a good thing in that it gives power and versatility to Maven 2 plug-in writers. The downside is that plug-in users are responsible for understanding the configuration quirks of each plug-in that they use. Some plug-ins and goals are simple, having one or two configuration elements; some are more complex like antrun:run, which has the entire Ant library at its disposal.

It is a powerful matter to manipulate plug-ins as you see fit. But you undoubtedly will run across cases where the configuration element must be duplicated for every POM in a set of projects. The Maven 2 developers foresaw this issue and thus created the pluginManagement element in the build section. This element contains its own plugins element—the difference being that plug-in settings defined under this section will be passed onto this POM's children in much the same way that dependencyManagement manages its children's dependencies configurations. Maven 2 will then pass the above configuration mess to any of this POM's children, who may then flag their desire to use the [antrun:run {execution: echohome}] settings by asking to use the plug-in, like so:

 <project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  ...
</project>

The last major elements under the build element are resource and filter. Resources are not (usually) code. They are not meant to be compiled, but are items used for build and reporting settings, or bundled within your project—for example, configuration files or class file stubs used in code generation.

Filters, on the other hand, are a way in which the POM can externalize properties to another file (rather than setting them via the properties element mentioned above). Each *.properties filter follows standard java.util.Properties file layout: a new-line separated list of name=value pairs. These properties may then be applied to resource files (or any other plug-in that uses properties, for that matter).

As an example of how these elements may be used together, let's look at a Plexus configuration file. Plexus is an Inversion of Control container along the same vein as PicoContainer, Nano, and Spring—upon which Maven is based. Like most IoC solutions, Plexus requires a configuration file to specify component configurations to the container. A valid Plexus JAR requires that the configuration.xml file live under the META-INF/plexus directory. We could just as easily place this file within src/main/resource/META-INF/plexus; however, we instead give Plexus resources their own directory at src/main/plexus. Although it is the job of the configuration.xml file to contain a component's concrete implementation data, we instead replace the implementation string with a key whose real value is defined within a components.properties filter file.

The configuration.xml stub with the implementation key looks as follows:

 <component-set>
  <components>
    <component>
      <role>org.codehaus.plexus.component.configurator.ComponentConfigurator</role>
      <implementation>${compConfig.impl}</implementation>
    </component>
  </components>
</component-set>

We create a filter file named components.properties in the same directory that maps the ${compConfig.impl} property to a value:

 compConfig.impl=org.codehaus.mojo.ruby.plexus.component
   .configurator.RubyComponentConfigurator

This file's value will be included in the POM as a filter and will replace the configuration.xml file's property of the same name.

Finally, the POM will contain the following:

 <project>
  ...
  <build>
    <filters>
      <filter>src/main/plexus/components.properties</filter>
    </filters>
    <resources>
      <resource>
        <targetPath>META-INF/plexus</targetPath>
        <filtering>true</filtering>
        <directory>src/main/plexus</directory>
        <includes>
          <include>configuration.xml</include>
        </includes>
      </resource>
    </resources>
  </build>
  ...
</project>

Besides including files explicitly, we may instead have excluded *.properties files to similar effect. After running the process-resources phase of the build lifecycle, the resource will move to the build output directory with the property filtered out:

     <implementation>org.codehaus.mojo.ruby.plexus.component.configurator.
       RubyComponentConfigurator</implementation>

And, yea, the world is as it should be. For a look at what more filters and resources can do, take a look at the Maven 2 project's own quick start guide.

Reporting

Maven defines more than the default build lifecycle. One of the more impressive ideas comes from the site generation lifecycle. Certain Maven plug-ins can generate reports defined and configured under the reporting element—for example, generating Javadoc reports. Much like the build element's ability to configure plug-ins, reporting commands the same ability. The glaring difference is that rather than fine-grained control of plug-in goals within the executions block, reporting configures goals within reportSet elements. And the subtler difference is that plug-in configurations under the reporting element work as build plug-in configurations, although the opposite is not true (build plug-in configurations do not affect reporting).

Possibly the only item under the reporting element that would not be familiar to someone who understood the build element is the Boolean excludeDefaults element. This element signifies to the site generator to exclude reports normally generated by default. When a site is generated via the site build cycle, a "Project Info" section is placed in the left-hand menu, chock full of reports, such as the Project Team report or Dependencies list report. These report goals are generated by maven-project-info-reports-plugin. Being a plug-in like any other, it may also be suppressed in the following, more verbose, way, which effectively turns off project-info reports:

1 2 3 Page 2
Page 2 of 3