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.
-
problem with maven:reactor
2006-02-13 06:15:27 Ravikumar.Maddi [View]
-
dependency format
2004-08-07 02:06:02 elendal [View]
-
site CSS
2004-08-07 02:01:37 elendal [View]
-
My method
2004-08-07 01:27:27 trajano [View]
-
Almost a maven convert
2004-08-05 06:08:56 tlaurenzo1 [View]