ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Maven: Trove of Tips
Pages: 1, 2

Maven Properties

Another way to customize your Maven project is to use the properties of Maven and its plugins. Whereas goals describe how and in what order scripts are executed, properties customize the environment, sources, and targets. Goals and properties go hand in hand, and you will find the description of goals along with the properties on a plugin web site such as that of the .war plugin.



Note: I would recommend that you only change properties when absolutely necessary, and keep the pre-set properties of Maven whenever possible. This will reduce your workload and keep various projects similarly structured.

Maven properties are first set by a list of properties files, in which the last definition wins (please check out Maven's web site for more documentation). Afterwards, plugins can set values but are not allowed to override them, meaning your settings will prevail. Since this also applies to your extension script, I recommend setting values in a properties file. Finally, any properties set with the -D option when Maven is started will override any previously set values.

Maven Tips and Tricks

This should be enough introduction for us to start looking into various tips and tricks how to use Maven extensively, so let's begin.

Extending a Given Goal

To test class loaders, I had to create multiple archives with the same fully qualified class name but with different code. This meant my code could not be compiled in one step, as Maven does by default. The goal to run the test is test:test, and so I needed to create the archive first. To do this, maven.xml contains a piece of code that looks like this:

<preGoal name="test:test">
   <attainGoal
      name="build-test-archives"/>
</preGoal>
<postGoal name="test:test">
   <attainGoal name="do-cleanup"/>
</postGoal>
<!--AS NOTE: Build the various archives
       needed for the unit tests -->
<goal name="build-test-archives">
   <!-- Do the magic here -->
</goal>
...

To make things easier to read, I normally create private goals instead of putting the code into the preGoal tag, but that is up to you. Inside of the private goal, I compile the code and create the archives. This is basically good old Ant scripting:

<ant:mkdir
   dir="${maven.build.dir}/test-classes/base"/>
<ant:javac 
destdir="${maven.build.dir}/test-classes/base"
   debug="on"
   srcdir="${basedir}/test/src/java.base"
>
   <ant:classpath>
      <ant:pathelement
path="${maven.build.dest}"/>
      <ant:pathelement
path="${maven.build.dir}/../../util/target/classes"/>
   </ant:classpath>
</ant:javac>
<ant:mkdir
   dir="${maven.build.dir}/test-archives"/>
<ant:jar
   jarfile=
"${maven.build.dir}/test-archives/test.base.jar"
   basedir=
"${maven.build.dir}/test-classes/base"
>
   <ant:manifest>
      <ant:attribute
name="Built-By" value="${user.name}"/>
      <ant:attribute
name="Created-By" value="maven"/>
      <ant:attribute
name="Package" value="${pom.package}"/>
   </ant:manifest>
   <ant:include name="**/*.class"/>
   <ant:exclude
name="**/ReferenceClass.class"/>
</ant:jar>

Using Maven's Reactor for Multiple Projects

With JDoppio, I wanted to do the opposite of JBoss' habit of making everything so tightly integrated that sub-projects cannot be used by themselves. Therefore, I wanted to create sub-projects that could be built without building the entire project, but still able to build the entire project in one step. Maven's reactor tag enables the developer to call a specified goal on every project found, identified by a POM, and so new projects can be added later on without breaking the build. The reactor is defined in the maven.xml file as part of another goal:

<goal name="jdoppio:clean"
      description="Clean all JDoppio
         components" prereqs="clean">
   <maven:reactor basedir="${basedir}"
          includes="**/project.xml"
          goals="clean:clean"
          banner="Cleaning:"
          ignoreFailures="false" />
</goal>

This goal calls the clean:clean goal on every POM found in the project's subdirectories, so that the next time the project is built, it will be created from scratch. Note: because the prerequisite of the goal itself is the goal clean, the main project's own clean goal is executed first. There is also a multi-project plug in available, but this is for generating a project web site composed of multiple sub-projects.

Dependency on Local Archives

The sub-projects would not be of much help if they couldn't be used within other sub-projects. This can be done in multiple ways. A project could copy the generated file from another subproject. Or it could copy its generated archives to a directory in the main project. In my case, I used the local repository because with that, I could specify the dependency in the POM with an explicit version. The only drawback is that the sub-projects that another project depends upon must be built beforehand locally at least once, otherwise it will fail. The dependency looks like this:

<dependency>
   <groupId>jdoppio</groupId>
   <artifactId>jdoppio.loader</artifactId>
   <version>0.1</version>
</dependency>

This dependency states that an archive is looked for in jdoppio/jars with the name jdoppio.loader-0.1.jar. To have an archive like this available in the local repository, the subproject POM must look like this:

<project>
...
   <id>jdoppio.loader</id>
   <name><what ever you like></name>
   <groupId>jdoppio</groupId>
...

Marking Dependencies for a Particular Usage

A nice feature of Maven is that its POM is accessible from a Jelly script, and so it is possible to loop through the dependencies and check them out. In the .war plugin it is possible to indicate that an archive has to be added to the .war file (into the /WEB-INF/lib directory) by adding a war.bundle property like this:

<dependency>
   <groupId>jdoppio</groupId>
   <artifactId>jdoppio.loader</artifactId>
   <version>0.1</version>
   <properties>
      <war.bundle>true</war.bundle>
   </properties>
</dependency>

I was facing the problem of copying some of the archives in the POM's dependencies list to library directory of a test server installation, to run the server standalone. So the script loops through the dependencies, looks for a particular property, and checks the type of the dependency, and when everything is okay, it will copy it:

<!-- Loop through all artifacts -->
<j:forEach var="lib"
      items="${pom.artifacts}">
   <j:set var="dep"
      value="${lib.dependency}"/>
   <!-- Check if a certain property is set
   correctly and that the archive is of the
   correct type -->
   <j:if
test="${dep.getProperty('server.lib')=='true'}">
      <j:if test="${dep.type =='jar'}"> 
         <!-- Now we can copy this archive -->
         <ant:copy
todir="${maven.build.dir}/test-server/lib"
file="${lib.path}"/>  
      </j:if> 
   </j:if>
</j:forEach>

Looping Through a Directory

After being told that JAXB archives can be freely distributed, I needed to add them into the project directory and add them to a class path, so that JAXB could generate the XML binding classes and compile these classes afterwards. One of the Jelly's Ant tags is a File Scanner, which allows you to obtain an iterator over a list of files and then use them within your maven.xml file. In the Maven tag library, the tag addPath allows you to add a path to an existing path. So here I loop over the /lib directory and add them to a class path. Note: I ran into a problem with using dots in the name of the class path which could only be resolved after omitting the dots (I guess Maven is treating this as an attribute).

<!-- Note the capital S because it seems to be
   case sensitive here -->
<ant:fileScanner var="ownLibs">
   <ant:fileset dir="${basedir}/lib">
      <ant:include name="*.jar"/>
   </ant:fileset>
</ant:fileScanner>
<!-- A file scanner provides and iterator so we
        this one in the forEach to loop over the
        directory -->
<j:forEach var="entry"
items="${ownLibs.iterator()}">
   <!-- Create a path to be added afterwards
           to the dependency class path -->
   <ant:path id="libEntryPath"
      path="${entry}"/>
   <maven:addPath
id="maven.dependency.classpath"
refid="libEntryPath"/>
</j:forEach>

Also note that I use the maven.dependency.classpath property to add the archives. The reason to do so is that the Java plugin is using this as the class path to compile the project's Java classes, which is necessary because the project is using JAXB classes such as JAXBException. This is an example of customizing Maven rather than scripting the compilation yourself, which saves you time and money. In the case of using your own path, you need to create it up front with <and:path id="<Your Name>"/>, because the path has to exist when used in addPath.

I converted this code into a Maven plugin that can be downloaded from my weblog. This plugin comes with the current JAXB archives and does not need any additional archives to be downloaded.

Checking the Presence of a File or Directory

In the Catalina service, I wanted to automate the extraction of the Catalina distribution, but only do so if this had not already been done. So I needed to figure out whether the directory already existed:

<-- Create a property with a default value so
       that property is set even when directory is
       not available -->
<j:set var="catalinaPresent"
   value="false"/>
<-- Line below is wrapped to fit the page -->
<util:available file=
"${maven.build.dir}/../../../../
server/target/test-server/catalina"
>
   <-- Directory is available not set the
      property value to true -->
   <j:set var="catalinaPresent"
      value="true"/>
</util:available>
<-- If property is not set to true so we need
   to extract Catalina -->
<j:if
   test="${catalinaPresent != 'true'}">
   <ant:unzip
...

Project Web Sites

Maven can create a project web site with quite a number of reports in it -- that's the reason so many project web sites look similar. Again by default Maven is using its own template to create the web site, but this can be customized by providing a navigation.xml file in the /xdocs directory of your project. In my case, I wanted to include the sub-projects as well as additional information to the project web site. This file can look like this:

<project name="JDoppio">
   <title>JDoppio: The Next Generation
Application Server</title>
   <body>
      <links>
         <item name="Maven"
href="http://maven.apache.org/"/>
      </links>
      <menu name="Definition">
         <item name="Download"
href="download.html"/>
...
      </menu>
      <menu name="Projects">
         <item name="Utility Project"
href="./multiproject/jdoppio.util/index.html"/>
...
      </menu>
   </body>
</project>

First, I added a link back to Maven as a tribute to them. Then I created a menu, Definition, with links to other web pages, such as the download instruction page shown here. Afterwards, in the Projects menu, I added a link to a subproject, Util, so that the user can easily navigate to the project web site of this subproject. Within the /xdocs directory, all pages are written in the xdoc format with an .xml extension and the XDoc plugin will generate HTML pages from them. In JDoppio I am using the Multi-Project plugin to generate the entire project web site, because the project is composed of multiple sub-projects. Every subdirectory is placed into the common /multiproject directory and therefore, a link to a subproject in navigation page is just relative to the /multiproject directory.

Documentation and Help

The best documentation and help for Maven I've found so far are the Maven plugins. They are a great source of ideas on how to use Maven, even though it is sometimes tough to distinguish between the old and new styles. It also helps to check out the tag library documentation on the Jelly web site if you need to know more about Jelly and its tags. The Maven Jelly tags can be found on the Maven web site. You could also search the web for projects using Maven, like my JDoppio project, and check out how they are building their projects.

Conclusion

I like Maven because it lets me focus on what I want to accomplish and because I can avoid dealing with nasty build scripts that Ant makes me write over and over again. So far, I have not run into any hurdles that would make me question my decision to use Maven. Nevertheless, I do recommend starting slowly with Maven and letting your knowledge and confidence grow with it. I would like it if Ant would provide a Maven task, so that developers could gradually migrate their projects from Ant to Maven.

Finally, my last tip: let Maven run the project and limit yourself to steering Maven instead of controlling it. In Ant, developers control the build process with their scripts, but in Maven, there is a building process already in place, so it is much easier to go with it. Most of the time, the solution is just around the corner and only needs to be utilized the right way.

Have fun!

Andreas Schaefer is a system architect for J2EE at SeeBeyond Inc., where he leads application server development.


Return to ONJava.com.