Introduction
Maven is a high-level, intelligent project management, build and
deployment tool from the Apache project. There is nothing that Maven does
that Ant cannot do. Ant gives the ultimate power and flexibility in build
and deployment to the developer. Why do you need Maven then? Maven adds a
layer of abstraction above Ant (and uses Jelly). Maven can be used to
build any Java application, but in this article we will investigate the
applicability of Maven from a J2EE standpoint. J2EE build and deployment
as we know it today is pretty much standardized. Every enterprise has some
variations, but in general it is all the same: deploying EARs, WARs, and
EJB-JARs. Maven captures this intelligence and lets you achieve the build
and deployment in about 5-6 lines of Maven script compared to dozens of
lines in an Ant build script. In other words, Maven simplifies a
developer's life. Everybody loves things to be simple right?
If you think of Ant as a modular language like C, then Maven can be
compared to an object oriented language like C++ or Java. Maven plugins
are like the standard JDK libraries. The C language, albeit a giant leap
from assembly languages, falls short of addressing the complexities of
enterprise projects. Java on the other hand, being object oriented, forces
the developer to program in a certain way and reduces complexities in
large enterprise projects by letting you manage dependencies by breaking
them into chunks via CRC - Class, Responsibility and Collaboration - which
is a big paradigm shift from C. There are some tasks that C alone can do,
but for the majority of enterprise programming Java will suffice.
Every comparison between C and Java listed above applies to Ant and Maven.
Ant lets you do any variations you want, but requires a lot of scripting.
Maven on the other hand mandates certain directories and file names, but
it provides plugins to make life easier. The restriction imposed by Maven
is that only one artifact is generated per project (A project in Maven
terminology is a folder with a project.xml file in it). If you are
thinking "My EAR artifact itself consists of multiple artifacts, I am out
of luck, I cannot use Maven", it is time to take a closer look. A Maven
project can have sub projects. Each sub project can build its own
artifact. The topmost project can aggregate the artifacts into a larger
one. This is synonymous to jars and WARs put together to form an EAR.
Maven also provides inheritance in projects. I will address these topics
in the following sections. At the end of this article you will be able to
build J2EE project artifacts using Maven.
Maven simplifies build enormously by imposing certain fixed file names and
acceptable restrictions like one artifact per project.
First there was make. But make was not cross platform. Then came Ant. Ant
lets you do cross-platform builds in a systematic manner. It is very
elegant compared to make. However as you may have already realized, build
tasks in a project are pretty standard. The tricky part is partitioning,
compiling and packaging classes, resources and descriptors into the right
deployment artifacts like jars, WARs and EARs and managing dependencies on
the right version of libraries - both internal and third party ones. After
a while you will find yourself copying Ant script snippets from one place
to another and modifying them. Then there is the universal problem of
classpath. We all have different versions of different libraries installed
in different locations on the development workstations. Nobody can be sure
if the Ant build copied the right version of the library into the
deployment artifact at all. We all have seen the classic developer shrug -
"It works on my machine"! Ant does little to address this problem. Enter
Maven.
What is it that makes Maven an attractive option for today's enterprises?
The basic tenet of Ant build scripts is copying, renaming and deleting
files under various conditions into various deployable artifacts - which
are again treated as files on your computer by the build script. Maven
hides the fact that everything is a file and forces you to think and
script to create a deployable artifact such as an EAR, that has a
dependency on a particular version of a third party library residing in a
shared remote (or local) enterprise repository, and then publish your
library into the repository as well for others to use. No more classpath
issues. No more mismatch in libraries. This sounds like a very real and
practical solution to our everyday problems. It also gives you the power
to embed Ant scripts within Maven scripts if absolutely essential.
In the words of Jason van Zyl, an important contributor to the Maven
source code base, "the intent of Maven is to make intra-project
development highly manageable in the hopes of providing more time for
cross-project development. You might call it cross-project pollination or
the sharing of project development knowledge; this is what Maven attempts
to encourage".
Maven installation
You can download the 1.0 Release Candidate from
http://maven.apache.org/builds/release/1.0-rc1/.
The version of Maven available at the time of this writing was 1.0-beta
10. Everything in this article refers to the 1.0-beta 10. It is feature
complete for 1.0 pending any defects. Unzip the archive onto your
machine. It creates a directory called maven-1.0-beta-10. Before you go
any further, set the MAVEN_HOME environment variable to this directory.
Also add the MAVEN_HOME/bin to the PATH environment variable. Make sure
you have set the JAVA_HOME appropriately. Go to the command line and type
maven -g. If you see a long list of output, your installation has
succeeded.
Maven basics
The basic concept of Maven is a project. In Maven terms, any directory
that has a project.xml in it is a project. When the sub-directories
underneath have their own project.xml, they are projects on their own too.
Another concept in Maven is that of a repository. The repository holds the
artifacts on which your project depends. There are two kinds of
repository: local and remote. Both of them can be declaratively set.
Unless specified otherwise, the local repository is created in a special
directory called ".maven/repository". In Windows, this directory is
created in C:\Documents And Settings. For example, if your project depends
on commons-logging version 1.0.2, you can specify the dependency in
project.xml and when maven is executed, it will copy the appropriate
commons-logging jar file from the remote repository to the local
repository and then use it to build your project's artifact. The maven
repository folder has subfolders for each library. For instance, there is
a sub folder for commons-logging. Beneath the commons-logging folder there
is another subfolder called jars. This jars folder has the commons logging
jar files suffixed by version number. Jars are not the only type of
artifacts supported in the repository. You can have EARs and WARs too.
The role of the repository is immediately obvious. Instead of each project
having its own copies of third party libraries, the repository helps
developers across projects to share the libraries. Each project can also
in turn generate its artifacts and publish it into the remote repository.
The process of publishing a jar into the repository is called "install" in
Maven lingo. This install process helps organizations to share internal
artifacts across projects in a standard manner. This also holds the basis
for continuous integration among inter-dependent projects. Continuous
Integration is a concept that was developed by Martin Fowler and
developers of Cruise Control. Individual projects can continue to build
and publish the release and snapshot artifacts to corporate repositories.
Daemon processes running on dedicated machines can schedule integration
builds, deploy to execution platforms (application servers) and run
automated tests to verify the build status. Figure 1 shows the role of
project.xml, repository, goals and plugins in Maven build. The grey
colored rectangles are provided by you. The green colored rectangles are
provided by Maven. The pink colored rectangle shaded is the output - the
deployment artifacts from your project. Custom plugins and maven.xml are
optional. The rest of the inputs are mandatory.
Figure 1 Overview of Maven build elements
project.xml details
The project.xml is divided into four main parts namely, Project Management
Section, Project Dependency Section, Project Build Section and Project
Reports Section. Listing 1 shows the outline of a typical project.xml. The
mandatory items are shown in bold.
Listing 1 Outline of project.xml
01 <?xml version="1.0"?>
02 <project>
03 <pomVersion>3</pomVersion>
04 <groupId>Sample-Maven-Project</groupId>
05 <id>sample-project</id>
06 <currentVersion>1.1</currentVersion>
07 <name>Sample Maven Project</name>
08
09 <!-- Project Management section goes here -->
10
11 <!-- Project Dependency section goes here -->
12
13 <!-- Project Build section goes here -->
14
15 <!-- Project Reports section goes here -->
16
17 </project>
* Line 02 - Root element defining the project.
* Line 03 - Project Object Model (POM) Version. This tag is unused but
needed.
* Line 04 - A directory with this name is created in Maven repository
to hold the artifacts of projects sharing the group id.
* Line 05, 06 - The id and version is used to create the artifact name
as <id>-<version>.jar
* Line 07 - Name of the Project
The Project Management Section is shown in detail in Listing 2 and has
general information on the organization, its web site, project web site,
location of SCM, deployment and issue tracking site, developer list,
mailing lists to name a few. Most of the items in this section is
boilerplate and are optional. Maven allows inheritance in the project.xml.
Every organization can have an enterprise wide template which can be
extended for projects. Each subproject will simply fill in the relevant
section of this file. Most of the project management section elements get
defined in the enterprise wide template and top-level project template.
The only mandatory element is the organization name.
Listing 2 Project Management Section in project.xml
01 <organization>
02 <name>Foobar Travels</name>
03 <url>http://www.foobar.com</url>
04 <logo>http://www.foobar.com/logo.jpg</logo>
05 </organization>
06
07 <inceptionYear>2003</inceptionYear>
08 <package>foobar.blah.*</package>
09 <logo>http://www.foobar.com/project-logo.jpg</logo> |
10 <description>Project description goes here</description>
11 <shortDescription>Short Description</shortDescription>
12 <url>http://www.foobar.com</url>
13 <issueTrackingUrl>http://jira.foobar.com</issueTrackingUrl>
14 <siteAddress>http://staging.foobar.com</siteAddress>
15 <siteDirectory>/etc/staging</siteDirectory>
16 <distributionDirectory>/etc/builds</distributionDirectory>
17
18 <repository>
19 <connection>cvs:pserver:anon@foobar.com:/foo</connection>
20 <url>http://scm.foobar.com</url>
21 </repository>
22
23 <mailingLists>
24 <mailingList>
25 <name>Dev List</name>
26 <subscribe>subscribe-dev@foobar.com</subscribe>
27 <unsubscribe>unsubscribe-dev@foobar.com</unsubscribe>
28 </mailingList>
29 ...
30 ...
31 </mailingLists>
32
33 <developers>
34 <developer>
35 <name>Srikanth Shenoy</name>
36 <id>shenoy</id>
37 <email>srikanth@srikanth.org</email>
38 </developer>
39 ...
40 ...
41 </developers>
* Lines 01-05 - Organization details
* Line 08 - Top level package for the project
* Line 09 - Project Logo
* Line 12 - Project web site
* Line 14 - The site where the project is hosted
* Line 15 - Physical location of project deployment
* Line 16 - Physical location where the project distributions are
available
* Lines 18-21 - SCM to access the project source
* Lines 23-31 - Mailing list for the project
* Lines 33-41 - Developers in the project
Figure 2 Sample Maven Project Organization
The Project Build Section describes the location of source, test and
resource files. This section is generally defined at the organization
level to standardize templates for all projects in the organization, or at
the main project level to standardize the templates for all underlying sub
projects respectively. Listing 3 shows the project build section. All the
information about project organization shown in Figure 2 is defined here.
The project organization can be changed and so can the project.xml. All
the elements in this section are optional. However, note that if this
section is not specified, no build ever gets done. Once the build is
complete, Maven automatically runs the tests specified in the unit test
section.
Listing 3 Project Build Section in project.xml
01 <build>
02 <nagEmailAddress>srikanth@srikanth.org</nagEmailAddress>
03 <sourceDirectory>${basedir}/src/java</sourceDirectory>
04 <unitTestSourceDirectory>${basedir}/test/java</unitTestSourceDirectory>
05 <unitTest>
06 <includes>
07 <include>**/*Test.java</include>
08 </includes>
09 </unitTest>
10
11 <resources>
12 <resource>
13 <directory>${basedir}/src/conf</directory>
14 <includes>
15 <include>*.properties</include>
16 </includes>
17 </resource>
18 </resources>
19 </build>
* Line 02 - Email address to send notification about the build status
* Line 03 - Folder containing the source files for the project. The
source can be java, jsp and so on.
* Line 04 - Directory containing the unit test files for the project.
* Lines 05-09 - The test file name pattern to run after the build is
completed
* Lines 11-19 - Resources to be copied in case a jar is created.
Once the build is done, different kinds of reports and documentation can
be generated to report the status of the build/release. The target
audience of the reports can vary from other developers in the same project
to sponsors, stakeholders or users from other projects. For instance, the
javadoc and java cross reference (jsr) reports target the programmers,
jdepend reports are of interest to the architect. File and developer
activity reports might be of interest to the Configuration Manager. The
reports are meant to effectively communicate and collaborate with the team
and the stakeholders about the project's status. Listing 4 shows the
Project Reports Section in the project.xml. Do not worry about the
specifics of each of the entries. You can pick them up as you use Maven.
Listing 4 Project Reports Section in project.xml
<reports>
<report>maven-changes-plugin</report>
<report>maven-jdepend-plugin</report>
<report>maven-checkstyle-plugin</report>
<report>maven-pmd-plugin</report>
<report>maven-junit-report-plugin</report>
<report>maven-clover-plugin</report>
<report>maven-changelog-plugin</report>
<report>maven-file-activity-plugin</report>
<report>maven-developer-activity-plugin</report>
<report>maven-file-activity-plugin</report>
<report>maven-license-plugin</report>
<report>maven-linkcheck-plugin</report>
<report>maven-jxr-plugin</report>
</reports>
The final section covers the Project Dependency and is the key to every
project. Consider Listing 5 that shows the dependencies for the Sample
Maven Project. The first dependency states that this project depends on a
jar file in a folder named BeanUtils. The name of the jar file is
commons-beanutils-1.5.jar [according to the id-version.jar convention
described earlier]. The folder BeanUtils exists in the .maven/repository
folder.
Listing 5 Project dependency Section in project.xml
<dependencies>
<dependency>
<groupId>BeanUtils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>castor</groupId>
<artifactId>castor</artifactId>
<version>0.9.4.3</version>
</dependency>
</dependencies>
Maven Hands-On
Now that we have sufficient understanding of the project.xml file let us
get on try something out. At the time of this writing Maven 1.0 beta 10 -
a feature complete version for the 1.0 release is generally available,
which we will use in the course of this article. After downloading Maven
from maven.apache.org, unzip the archive into your local directory. Set
the JAVA_HOME variable to point to the JDK installation and MAVEN_HOME to
point to the Maven directory. Also add the MAVEN_HOME/bin to the PATH
environment variable. With these settings, you are ready to use Maven.
Create a directory structure as shown in Figure 2. Then create a simple
MyApp.java class without any dependencies outside JDK libraries. Create a
project.xml with the information supplied in Listing 1 through 5. Since
your sample project does not depend on external entities, the only
exceptions is that you will have an empty dependencies section like this:
<dependencies/>. Go to the command line and cd to the Sample-Maven-Project
directory and type in
C:\Sample-Maven-Project> maven java:compile
You will see that all the java files in src and test directory are
compiled. A maven temp directory called target is created directly under
C:\Sample-Maven-Project, which contains all the compiled class files. As I
have stated earlier Maven's power lies in its plugins. You might not know
it, but you have just used one of its plugins. When you typed maven
java:compile you signaled Maven to use the "java" plugin and attain the
"compile" goal. Maven uses the project.xml and the repository to execute a
"goal" on the project. The generic format of the maven command is
maven <plugin name>:<goal name>
A plugin is a logical collection of goals written using Jelly - an XML
based scripting language. The conditional constructs from Jelly control
the execution of ready-made ant scripts for the pre defined tasks. Each
such task is called a Goal. Although I do not cover Jelly in this article
a sample Jelly snippet will make things clear as to how it wraps an Ant
script. This snippet is from the Maven java plugin. The plugin internally
invokes the <ant:javac> to compile the java code.
<j:when test="${sourcesPresent == 'true'}">
<ant:javac destdir="${maven.build.dest}"
excludes="**/package.html"
debug="${maven.compile.debug}"
deprecation="${maven.compile.deprecation}"
target="${maven.compile.target}"
optimize="${maven.compile.optimize}">
<ant:src>
<ant:path refid="maven.compile.src.set"/>
</ant:src>
</j:when>
A plugin may have a default goal. In such a case you just have to type in
maven <plugin name>. In this case the compile goal in java plugin has the
knowledge to go to the src and test folders specified in the project.xml
and recursively compile all the java files. Similarly a jar plugin has a
default goal of "jar" - i.e. to jar the class files after compiling. The
jar plugin invokes java:compile or something to that effect to compile the
java classes first and then creates the jar file from the classes and
automatically generates the manifest. A plugin thus captures the common
practices into a reusable "library" saving you from reinventing the wheel
and cutting and pasting ant scripts. Now let us go ahead and execute the
jar:install goal. Type the following on the command line
C:\Sample-Maven-Proejct>maven jar:install
When the jar:install goal is attained, a jar file with name
sample-project-1.1.jar is created and is copied into the C:/Documents And
Settings/<login-id>/.maven/repository/Sample-Maven-Project/jars folder.
This is how you publish artifacts as mentioned earlier in this section and
is the basis for continuous integration.
You can see all the goals of Maven by typing the command maven -g at the
command line.
Using Maven in J2EE Projects
In the previous section you saw how to use Maven to set up a project
template, compile source files, create a jar and publish the artifact into
the repository. There is much more to Maven than that. We will cover
inheritance in Maven POM, setting up a Sample J2EE project, creating WARs,
EJB-JARs, dependency jars and ultimately creating EARs. However let's
first look at another important concept in Maven.
In Maven, a project can produce only one artifact. The top-level project
can have multiple sub projects and they in turn can have siblings too. But
at the end, each of them can only produce one artifact. This makes a lot
of sense if you consider how the Ant scripts are used to create multiple
artifacts from a project - which gets confusing over time - to say the
least. Typically, projects have a monolithic (or maybe a couple) source
tree(s), from which all the dependency jars, WARs and EARs are created
using different build scripts. At the end, the message of application
partitioning is lost and classes are created in different packages without
consideration to the classloader mechanism, making it even harder to keep
build scripts up to date. Maven discourages this practice at the outset.
Of course Maven gives you flexibility to override this practice. There are
ways to produce multiple artifacts but that goes against the very spirit
of Maven and has to be avoided unless absolutely essential.
Customizing Maven with maven.xml
Until now you saw how to compile java classes in a maven project and
create jar files out of it. What if you wanted to customize Maven
behavior? You can create a file called maven.xml in the same folder where
project.xml resides. In this file, you can customize goals, define and pre
and post goals.
Listing 6 shows a sample maven.xml. We are achieving quite a bit in this
file. Look at the default goal for the project. The default goal is set to
foobar-dist. A goal is equivalent of an Ant target. The default goal is
the same as the default target in Ant. What this means is that the default
goal is executed when you go to the directory containing this file and
simply type maven. The foobar-dist goal is simply a wrapper on top of
another goal called war:install. The "install" goal in the war plugin
creates a war and publishes it into the repository. The "attainGoal"
instructs maven to achieve the goal described by the name attribute.
The preGoal element instructs Maven to execute the defined tasks in the
preGoal before achieving the goal specified in its name attribute. This
can get confusing at times. Note that the preGoal name (java:compile) is
different from the default goal (foobar-dist). The default goal for this
project installs a war file. However the war plugin has to compile the
java files in the project before creating the actual WAR archive. The
preGoal specified is linked to this java:compile goal rather than the
foobar-dist goal. In this case, the pre goal executes a Xdoclet maven plug
in. We will use Xdoclet Maven plugin later in this article to generate
tlds (tag library definitions) from the actual tag classes.
A postGoal can be specified for any of the implicit goals achieved as part
of the default goal or for the default goal itself. In Listing 6, a
postGoal is specified for the war:war goal, which gets executed just
before war:install goal. The postGoal specifies that a certain directory
have to be created after the WAR is built. Notice the use of standard ant
tasks within the postGoal. In fact any of the ant tasks can be embedded
within the goal, preGoal or postGoal. This is what makes Maven flexible.
It is also very easy to abuse this flexibility by compiling and creating
multiple artifacts using Ant tasks within Maven. In other words, you would
have written Ant scripts under the hoods of Maven. That practice is
discouraged and should be used if there is no other option. If you find
yourself repeating the steps of putting ant tasks into Maven goals, what
you need is a custom plugin.
Listing 6 Sample maven.xml
<project default="foobar-dist" xmlns:m="jelly:maven">
<preGoal name="java:compile">
<attainGoal name="xdoclet:webdoclet"/>
</preGoal>
<goal name="foobar-dist">
<attainGoal name="war:install" />
</goal>
<postGoal name="war:war">
<mkdir dir="${test.result.dir}"/>
<echo>Creating directories</echo>
</postGoal>
</project>
A custom plugin is written in a file called plugin.jelly using the Jelly
scripting language and bundled along with project.xml and other files into
a jar and copied into the C:/Documents And
Settings/<login-id>/.maven/plugins directory. This plugin is then
available to all of your processes using Maven. We will customize the war
plugin later in this article since it lacks some of the features we need.
Figure 3 Inter-Project relationships for Foobar Travels
Let us now jump to applying Maven to J2EE projects. The most common J2EE
artifact generated and deployed is the EAR. The EAR itself consists of
dependency jars, ejb jars, wars and third party libraries. In other words,
EAR is an aggregate artifact. For Foobar Travels, we will use the same
project template as shown in Figure 2. Figure 3 shows the inter-project
relationship and hierarchy. The master project template is defined at the
top project level. A simplified master template is shown in Listing 7 only
with the essential elements. A real one will have most of the items
described in the section Maven basics. The key thing to note is that the
dependencies are not declared in this template. This is because the master
project.xml is just that - a template for other sub projects to extend.
The only thing we have not touched upon from Listing 7 is the
${pom.artifactId}. I will explain this shortly in the context of extending
the template. Figure 4 shows the structure of the EAR file generated by
this project. You will find that the EAR content organization closely
resembles the Maven project shown in Figure 3. In most of the J2EE project
you would not deviate from a deployment artifact as in Figure 4. And when
you stick to the usual route, Maven "out-of-the-box" build scripts work as
is.
Figure 4 The EAR structure for Foobar Travels
Listing 7 Master Project Definition Template
<project>
<pomVersion>3</pomVersion>
<id>foobar-online</id>
<groupId>Foobar-Travels</groupId>
<currentVersion>2.0</currentVersion>
<name>Foobar Online Project</name>
<organization>
<name>Foobar Travels</name>
</organization>
<package>foobar.*</package>
<distributionDirectory>
/foobar/dist/${pom.artifactId}/
</distributionDirectory>
<dependencies/>
<build>
<sourceDirectory>${basedir}/src/java</sourceDirectory>
<resources/>
</build>
</project>
To summarize Figure 4, we have to build the following artifacts from the
project shown in Figure 3.
1. foobar-services-2.0.jar - The dependency jar
2. reservationejb-2.0.jar - The Reservation EJB and create its Manifest
file with Manifest Class-path pointing to the services jar file.
3. personejb-2.0.jar - The Person EJB and create its Manifest file with
Manifest Class-path pointing to the services jar file.
4. foobar-web-2.0.jar - The Web application for Foobar Travels and
create its Manifest file with Manifest Class-path pointing to the
personejb, reservationejb and service jars
5. application.xml - the J2EE application deployment descriptor.
6. Build the EAR by including all of the above artifacts and also
including the castor jar file since the services jar depends on it at
runtime.
Building the dependency jars
Listing 8 shows the project definition for Services - a subproject in
Maven. This subproject creates the foobar-services-2.0.jar file - a
dependency library used by both ejbs and the web tier. Note that the
definition extends from the master template in Listing 7. By extending
from the parent, it inherits all the properties. It defines the dependency
elements to indicate the dependencies on the J2EE APIs (not defined in the
parent) and overrides the id, name and description defined in the parent.
Listing 8 Services Sub-Project Definition
<project>
<extend>${basedir}/../project.xml</extend>
<id>foobar-services</id>
<name>Foobar Services Framework</name>
<package>foobar.service.*</package>
<description>Services JAR project.</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
Now let us get back to the ${pom.artifactId} mentioned earlier (Listing
7). This lets you introduce polymorphism into Maven. At runtime, the value
of ${pom.artifactId} for the Services sub-project is its id -
foobar-services. Hence the value of the distribution directory for the
Services project is /foobar/dist/foobar-services. In other words the pom
can capture the variables and abstract them in a standard manner and
establish a procedural approach instead of the chaotic one.
Now let us introduce you to the maven.xml for the Services Project.
Technically speaking, maven.xml is not needed since you can always go to
this sub project and execute maven jar:install. However as we will see
later, having a maven.xml will help us eliminate manual execution of Maven
from every sub-project. The maven.xml for the Services project looks as
follows.
<project default="foobar-dist" xmlns:m="jelly:maven">
<goal name="foobar-dist">
<attainGoal name="jar:install" />
</goal>
</project>
Building the EJB jars
After having built the dependency jars let us turn our focus to building
the ejb jars. This is no different, since there is already a ejb plugin
and you can use the ejb:install goal to create and publish the ejb jars.
However we would also like to remind you that the ejb jar (loaded by the
EJB class loader) depends on the Services built in the previous step (See
Figure 4). What this means for you is that the ejb manifest classpath
should point to the foobar-services-2.0 jar. Listing 9 shows the
project.xml for the Reservation EJB jar. Other ejb project definitions are
similar.
NOTE: In the downloadable example supplied with this article, no EJBs are
included. The reason is to have even non-ejb developers using Tomcat to
see Maven in action. However this section describes what it takes to build
EJBs with Maven.
As usual the ejb project depends on the J2EE jars. However since it also
depends on the foobar-services-2.0.jar (loaded by the EAR class loader),
it has to add that jar file name to its manifest classpath. You can
achieve this by setting the property ejb.manifest.classpath to be true.
The Maven variable, pom.currentVersion stands for the current version of
the project. In our case, it is 2.0. This setting indicates that the
version of services project on which the ejb project depends is the same
as the current version of the ejb project. This version is set on the
parent project template. For future releases, the version number need not
be changed for every dependency in every project. Just change it in the
Master project template and it takes effect everywhere. Ah, the beauty of
inheritance!
Listing 9 Reservation EJB Project Definition
<project>
<extend>${basedir}/../project.xml</extend>
<id>reservationejb</id>
<name>Foobar Reservation Components</name>
<package>foobar.reservation.*</package>
<description>Reservation Components project</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>Foobar-Travels</groupId>
<artifactId>foobar-services</artifactId>
<version>${pom.currentVersion}</version>
<properties>
<ejb.manifest.classpath>true</ejb.manifest.classpath>
</properties>
</dependency>
</dependencies>
</project>
The discussion of building the ejb jars is incomplete without mentioning
the maven.xml file. This file is not straight forward as in the services
project. Listing 10 shows the maven.xml for the Reservation EJB project.
There are a few things happening here. First, the foobar-dist goal is used
as a wrapper for attaining the goal ejb:install. One of the first goals to
be achieved during the install is init, when the file system and other
resources are initialized and then the ejb classes are compiled. The
compiler expects to find the home, local and remote interfaces, failing
which it will throw a compiler error. This is where XDoclet comes into
play.
Listing 10 maven.xml for the Reservation EJB Project
<project default="foobar-dist" xmlns:m="jelly:maven"
xmlns:ant="jelly:ant">
<goal name="foobar-dist">
<attainGoal name="ejb:install" />
</goal>
<preGoal name="ejb:init">
<attainGoal name="xdoclet:ejbdoclet"/>
</preGoal>
<postGoal name="ejb:install">
<ant:property name="maven.ejb.install.dir"
value="${maven.repo.local}/${pom.artifactDirectory}/ejbs"/>
<ant:mkdir dir="${maven.ejb.install.dir}"/>
<ant:copy file="${maven.build.dir}/${maven.final.name}.jar"
tofile="${maven.ejb.install.dir}/../
jars/${maven.final.name}.jar"/>
</postGoal>
</project>
XDoclet is an open source project hosted on the SourceForge. By using EJB
Doclet specific tags in the Bean class (the implementation class), you
signal the XDoclet to generate the home, local and remote interfaces. You
will also provide the jndi names, and other information using similar
tags. On parsing the file, the XDoclet will also generate appropriate
deployment descriptors. Now let us get back to see how it fits in here.
XDoclet provides Maven plugins for generating the above-mentioned
artifacts. By specifying the ejbdoclet as the preGoal for ejb:init, you
will generate home, local and remote interfaces and xml deployment
descriptors just in time for the compilation.
Now let us look at the postGoal for ejb:install. As you know, the
ejb:install generates the ejb jar as the artifact and puts it in the
repository in the location "C:/Documents And
Settings/<login-id>/.maven/repository/Foobar-Online/jars". Suppose that
you want the ejbs to be in a separate directory called ejbs instead of the
jars, you have to do it after the ejb:install is finished with its
business. That's why it is a postGoal. For those familiar with Ant, the
postGoal is nothing but a collection of Ant tasks. Again, a couple of
Maven specific properties are used. Don't try to remember all of the Maven
defined properties at once. Over time, these will become second nature.
You will notice there are two distinct categories of properties - those
with their name beginning with pom and the rest of them. The properties
with their names beginning with pom are the individual elements in the
project.xml. For instance, the currentVersion element defined in
project.xml has a property with the name pom.currentVersion and so on. The
project.xml is loaded to create the POM model by Maven. POM model
attribute values are evaluated by using the $ sign before the attribute
names. As more plugins are added new properties emerge. This is going to
be a challenge. You may not find adequate documentation of these
properties. The only way to find out is by opening the scripts for each of
these plugins in plugin.jelly file. Luckily there aren't very many
properties that you will have to know.
In your project, if you find yourself writing the same pregoals and
postgoals for every ejb subproject for example, it is time to roll your
own plugin.
Building the WAR
After the ejb jar, it is wartime. We will accomplish quite a bit in this
section. First we will introduce you to the project.xml. Then we will add
a minor missing functionality into the war plugin. Then we will show you
how to use XDoclet to generate tlds for a sample tag. Then we will end the
section with maven.xml.
Listing 11 shows the project.xml for the Foobar web application. The
actual project definition has much more dependencies and is downloadable.
Here we are showing only the relevant portions to illustrate the concepts.
The important part is the dependencies section. The web application
depends on the J2EE APIs provided by the container at runtime as well as
compile time. However there is no need to bundle the J2EE API with the WAR
since it is the servlet container's responsibility. In addition the web
application depends on Struts 1.1 jar file. The property setting
<war.bundle>true</war.bundle> indicates that struts.jar has to be bundled
with the WAR. Whenever the war plugin is instructed to bundle the jars, it
puts them under the WEB-INF/lib directory of the WAR. Compare this to
manually copying the jars into the WEB-INF/lib directory in Ant. The WAR
also depends on the Foobar Services jar file, foobar-services-2.0.jar.
However we do not want to copy this jar into the WEB-INF/lib since it is a
dependency library and shared by both the web tier and the ejbs. (Recall
that any jars and classes residing in WEB-INF/lib and WEB-INF/classes are
loaded by the WAR class loader.) Hence we set the war.bundle property as
false for foobar-services jar file. You get all this functionality out of
the box with Maven war plugin.
Listing 11 maven.xml for the Web Project
<project>
<extend>${basedir}/../project.xml</extend>
<id>foobar-web</id>
<name>Foobar web application</name>
<package>foobar.webapp.*</package>
<description>Foobar Web project.</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>jakarta-struts</groupId>
<artifactId>jakarta-struts</artifactId>
<version>1.0.2</version>
<properties>
<war.bundle>true</war.bundle>
</properties>
</dependency>
<dependency>
<groupId>Foobar-Travels</groupId>
<artifactId>foobar-services</artifactId>
<version>${pom.currentVersion}</version>
<properties>
<war.bundle>false</war.bundle>
<war.manifest.classpath>true</war.manifest.classpath>
</properties>
</dependency>
</dependencies>
</project>
However the Maven war plugin lacks one feature. It does not have the
capability to set the manifest classpath. Without this feature you cannot
expect to use dependency libraries. Hence I decided to add this feature to
the war plugin. Only two simple changes were required to achieve this. I
decided to illustrate this change to show how intuitive and easy it is to
write plugins in Jelly or customize the ones that already exist. Open the
plugin.jelly for the war plugin from C:/Documents And
Settings/<login-id>/.maven/plugins/maven-war-plugin-<version>-[SNAPSHOT].
These changes have to be done in the goal named war (The actual WAR -
deployment archive, is built in a goal named as war in the war plugin).
Add the following code in that goal.
<j:forEach var="dep" items="${pom.dependencies}">
<j:if
test="${dep.getProperty('war.manifest.classpath')=='true'}">
<j:set var="maven.war.classpath"
value="${maven.war.classpath} ${dep.artifact}"/>
</j:if>
</j:forEach>
This code iterates over each of the dependencies listed in the project.xml
(identified by pom.dependencies) and checks if the war.manifest.classpath
property is set to true. If so, then it appends that artifact name to a
property called maven.war.classpath.
The second change is needed when the manifest file is written out. The
manifest file is created by the ant plugin by executing a goal named
manifest. The manifest creation code is shown below. The line in bold is
the line I inserted to set the manifest classpath attribute. This uses the
previously set maven.war.classpath.
<ant:manifest>
<ant:attribute name="Built-By" value="${user.name}" />
<ant:attribute name="Class-Path" value="${maven.war.classpath}"/>
<ant:section name="${pom.package}">
<ant:attribute name="Specification-Title"
value="${pom.artifactId}" />
<ant:attribute name="Specification-Version"
value="${pom.currentVersion}" />
<ant:attribute name="Specification-Vendor"
value="${pom.organization.name}" />
</ant:section>
</ant:manifest>
That was simple wasn't it! Hopefully this will inspire you to write your
own plugins when needed, instead of repetitively adding tasks into
preGoals and postGoals.
Now I will show you how to use the XDoclet plugin to generate the tld
automatically. I will be using XDoclet version 1.2 Beta3. Consider the
declaration in the tld file for a tag say MyTag.
<tag>
<name>mystuff</name>
<tag-class>foobar.webapp.MyTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>locale</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
It is okay to have a readymade tld for frameworks such as Struts. However
when you are developing your own tags either by customizing the existing
tags or from scratch, it is natural that they change and evolve constantly
during development. It can be time consuming to manually synchronize the
tag code and its declaration in the tld. XDoclet is designed to ease such
burdens. In the source code for the MyAppTag, add @jsp.tag name="myapptag"
body-content="JSP" in its class comments section. For each of the tag
attributes, add a @jsp.attribute on the getter method for that attribute.
For instance, add @jsp.attribute required="false" rtexprvalue="false" to
the comments on the getMyMessage method to represent that myMessage is a
tag attribute. Next you invoke the webdoclet goal from the xdoclet plugin
in maven.xml as shown in Listing 12. XDoclet also gives you provision to
generate the web.xml too. Let us look at the maven.xml for the Web project
in its entirety.
Listing 12 maven.xml for the Web Project
<project default="foobar-dist" xmlns:m="jelly:maven"
xmlns:ant="jelly:ant">
<goal name="foobar-dist">
<attainGoal name="war:install" />
</goal>
<preGoal name="war:init">
<attainGoal name="xdoclet:webdoclet"/>
</preGoal>
</project>
NOTE: You have to add the XDoclet web module in the dependency section for
the project.xml as follows.
<dependency>
<id>xdoclet+web-module</id>
<version>1.2b4</version>
</dependency>
In addition, override the following properties in the plugin.properties
file for XDoclet under the Maven plugins folder
maven.xdoclet.webdoclet.deploymentdescriptor.0=false
maven.xdoclet.webdoclet.jsptaglib.0.destDir=
${maven.build.dir}/${pom.artifactId}/WEB-INF/tld
The first setting disables web.xml generation and the second setting sets
the generated tld file location. In this article you will just be
generating the tld file, but not generating the web.xml, nor the <taglib>
entry for the tld file in web.xml. As a matter of fact, XDoclet does not
seem to have the Maven equivalent for the following Ant script that adds
the <taglib> to web.xml.
<deploymentdescriptor servletspec="2.3" destdir="${WEBINF}" >
<taglib uri="myapptaglibs" location="WEB-INF/tld/myapp.tld" />
</deploymentdescriptor>
Another thing that I noticed in the XDoclet plugin is that the file name
for the tag library definitions (tld) has to be provided in
plugin.properties as follows.
maven.xdoclet.webdoclet.jsptaglib.0.filename=myapp.tld
Otherwise the tld file is generated with the default name of taglib.tld.
This does not seem right. Instead it should be set in the build script.
For instance, in Ant this is done as follows
<jsptaglib jspversion="1.2" destdir="${WEBINF}/tld"
shortname="basic" filename="myapp.tld"/>
Ideally I would like to set this like this:
<dependency>
<id>xdoclet+web-module</id>
<version>1.2b4</version>
<properties>
<jsptaglib.filename>myapp.tld</jsptaglib.filename>
</properties>
</dependency>
This is not a Maven defect. It just is an example showing that it takes
time for third party vendors to catch up.
Building the EAR
Up until now you have seen how to create each of the individual artifacts
that go into the EAR. Finally we have reached the last part - building the
EAR itself. You might imagine that since the EAR is the only artifact from
the project as a whole, it should be built from the project definition at
the top of the hierarchy, i.e. the Foobar-Travels folder. However that is
not the case. The EAR is built as an artifact from a subproject called ear
(See Figure 3). Why this anomaly? First of all, the project.xml at the
Foobar-Travels project level is a template for other subprojects to
extend. If it were to specify the dependencies to build the EAR, then it
would have resulted in a cyclic dependency - a chicken and egg situation -
when the template is extended by other subprojects. If the template were
to be defined elsewhere, probably at the organization level, then the
project.xml in Foobar-Travels folder could have produced the EAR but then
it would not be the template for the rest of the subprojects to extend.
Listing 13 shows the project.xml for the ear project. In the listing, you
will not see every library on which the ear is dependent upon, just a few
relevant ones that need some explanation.
Listing 13 project.xml for the ear project
01 <project>
02 <extend>${basedir}/../project.xml</extend>
03 <id>foobar-travels</id>
04 <name>Foobar EAR</name>
05 <description>Sample EAR project.</description>
06 <shortDescription>Foobar EAR project</shortDescription>
07 <dependencies>
08 <dependency>
09 <groupId>j2ee</groupId>
10 <artifactId>j2ee</artifactId>
11 <version>1.3.1</version>
12 </dependency>
13 <dependency>
14 <groupId>xerces</groupId>
15 <artifactId>xerces</artifactId>
16 <version>1.4.4</version>
17 <properties>
18 <ear.bundle>true</ear.bundle>
19 </properties>
20 </dependency>
21 <dependency>
22 <groupId>${pom.groupId}</groupId>
23 <artifactId>reservationejb</artifactId>
24 <version>${pom.currentVersion}</version>
25 <type>ejb</type>
26 <properties>
27 <ear.bundle>true</ear.bundle>
28 </properties>
29 </dependency>
30 <dependency>
31 <groupId>${pom.groupId}</groupId>
32 <artifactId>foobar-web</artifactId>
33 <version>${pom.currentVersion}</version>
34 <type>war</type>
35 <properties>
36 <ear.bundle>true</ear.bundle>
37 <ear.appxml.ear.context-root>
38 foobar-online
39 </ear.appxml.ear.context-root>
40 </properties>
41 </dependency>
42 </dependencies>
43 </project>
* Line 25 Type = ejb indicates that this is a ejb jar, This is set in
application.xml
* Line 34 Type = war indicates that this is a war, This is set in
application.xml
* Lines 37-39 Sets the context root for the web application, This is
set in application.xml
The first dependency that you will find is on J2EE itself. This library is
not bundled since the container at runtime provides it. The second
dependency is on the xerces xml library - a representative of the
dependency library (including our very own services jar). Libraries such
as these may not be provided by the application server and have to be
bundled with the ear. Then comes the ejb jar itself. The type=ejb sets the
application.xml appropriately. Similarly, the web application is bundled
by specifying the dependency and type=war. The above project definition
automatically creates application.xml for the ear. By using ear:install as
the goal, the ear is also copied into the maven repository. The generated
application.xml is shown in Listing 14. Obviously, setting the type has
had its effect. Also note the web application context-root setting. The
short description from the project.xml is used as the display name for the
EAR. When Maven starts the execution of project.xml for the EAR it sees
the dependencies and then proceeds to create the artifacts for the
dependencies before creating the EAR itself.
Listing 14 Generated application.xml
<application>
<display-name>Foobar EAR project</display-name>
<module>
<java>xerces-logging-1.4.4.jar</java>
</module>
<module>
<ejb>reservationejb-2.0.jar</ejb>
</module>
<module>
<web>
<web-uri>foobar-web-2.0.war</web-uri>
<context-root>foobar-online</context-root>
</web>
</module>
</application>
We haven't looked at the maven.xml for the EAR project yet. It turns out
to be quite trivial in that it just executes the ear:install goal.
<project default="foobar-dist">
<goal name="foobar-dist">
<attainGoal="ear:install"/>
</goal>
</project>
Another file that you need to provide is project.properties. By default
auto-generation of application.xml is turned off. To force
auto-generation, you can set the property in project.properties as
follows:
maven.ear.appxml.generate=true
and place the file in the ear folder under Foobar-Travels directory.
Using the reactor
We have covered all the files in building a J2EE project, except one. This
file that we have been intentionally postponing till the end is the
maven.xml that accompanies the master project template. In the last
subsection on building EAR, I stated "When Maven starts the execution of
project.xml for the EAR it sees the dependencies and then proceeds to
create the artifacts for the dependencies before creating the EAR itself".
I lied! This is not completely true. The project definition for EAR is
just like any other project.xml. For every dependency stated in that file,
it will search the maven repository for the appropriate jar file. What
this means is that you will have to run maven individually from dependency
library projects, ejb projects, web application projects and finally the
ear project in that order to execute jar:install, ejb:install, war:install
and ear:install respectively. Now, that can be too cumbersome if not
impossible in a large project. Maven offers the reactor as a solution to
this problem.
Reactor is a tool for executing dependent multi-project builds. Given a
set of project.xmls, the reactor determines the correct order of execution
based on the dependencies listed in the respective project.xmls. More
news: The reactor can be declared using Jelly scripts in the maven.xml
file itself. This is what we will have in the maven.xml accompanying the
master project template. The maven.xml in the Foobar-Travels folder (See
Figure 3) is shown in Listing 15.
Listing 15 maven.xml using the reactor
<project default="foobar-buildall" xmlns:m="jelly:maven">
<goal name="foobar-buildall">
<m:reactor basedir="${basedir}"
includes="*/project.xml"
goals="foobar-dist"
banner="Building"
ignoreFailures="false"/>
</goal>
</project>
The foobar-buildall goal is written as a reactor. The script for
foobar-buildall in Listing 15 translates to simple English as "Starting
from the base directory from where maven is executed, go to every
subfolder and execute the goal identified by foobar-dist in every
project.xml and stop on failure". When you go to the base directory
(Foobar-Travels) and type in the command maven (since foobar-buildall is
the default goal), the reactor figures out the dependency by reading all
the project.xmls in the subdirectories and creates the artifacts in the
required order.
Conclusion
This article has explained how to use Maven effectively in a J2EE project
instead of plain old Ant scripts and bring about some order and modularity
to the otherwise chaotic world of build and deployment. Maven is something
that you should consider when looking for options to build and deploy in
your project. But I've have only scratched the surface of what constitutes
Maven. I hope this article has given you the confidence to build your J2EE
projects with Maven and sparked your curiosity to learn more about it.
About the Author
Srikanth Shenoy is a Technical Architect at Objectseek Inc.
(http://www.objectseek.com). He specializes in the architecture, design,
development, and deployment of large J2EE and EAI projects. He has helped
clients in the manufacturing, logistics, and financial sectors to realize
the Java's "write once, run anywhere" dream. He is a Sun Certified
Enterprise Architect and co- author of the upcoming book Practical Guide
to J2EE Web Projects. You can reach him at srikanth@srikanth.org.
Resources
* Download the Accompanying Source Code for this article.
-----------------------------------------
Get Free Email @BizHat.com
Report Spam: http://bizhat.com/contact.php