ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Building J2EE Projects with Maven

by Vincent Massol
09/07/2005

Maven is a formidable tool for simplifying the construction of J2EE applications. If you're currently using an Ant build or some other build tool you'll be able to reduce your build script by several folds, thus saving maintenance time. But perhaps even more importantly, you'll get as a result a nicely-structured directory organization that follows building best practices. We'll assume you have general Maven knowledge and that you already know how to create and build a simple JAR project. For more general knowledge on Maven see the Maven website, Maven: A Developer's Notebook, and the Mavenbook.org website.

Note: You'll need to use Maven 1.1 beta 2 or greater to try out the build examples in this article. Do not try them with Maven 1.0.2, as the examples use new features available only in plugins released with Maven 1.1 beta 2 and above.

Generating J2EE Artifacts

In order to get you some real-life experience in building a J2EE application we'll be exploring how to build the Java Petstore and the xPetstore J2EE applications. The xPetstore is different in that it's using XDoclet to generate lots of J2EE files including Home, Local, and Remote interfaces, in addition to deployment descriptors. To make it easy to follow we've made all the source code available under an Apache License at www.mavenbook.org/viewcvs/mdn/code/j2ee/ (the sources can also be checked out from a Subversion repository at www.mavenbook.org/svn/mdn/code/j2ee.

During the course of this article you'll learn to generate J2EE artifacts (EJB JARs, WARs, EARs) with Maven.

Overall Directory Organization

When you're building with Maven you need to think in terms of artifacts (i.e. what files your build produces). A typical full-fledged J2EE application will produce EJB-JARs, possibly one or several WARs, and an EAR. For each artifact produced you'll need to create a Maven project in your source tree. Figure 1 shows a general directory structure for a build that produces several applications (EARs) containing JARs, WARs, and EJBs.

figure 1
Figure 1. General directory structure for a J2EE Maven project

The Java Petstore and the xPetstore application currently have an Ant build and they're not organized in this manner. We've reorganized them for the purpose of this article into the directory structures shown in Figures 2 and 3.

figure 2
Figure 2. Java Petstore directory structure for our Maven build showing what artifact each subproject produces

figure 3
Figure 3. xPetstore directory structure for our Maven build showing what artifact each subproject produces

Building an EJB-JAR

Let's start by building the address EJB-JAR module from the Java Petstore. The recommended directory structure is to put the sources that make the runtime jar in src/main (see figure 4). The java sources go in src/main/java and the resources, like deployment descriptors, go in src/main/resources. Java test sources go into src/test/java. As you can see in figure 4, there are no tests in the Java Petstore source code. We'll examine how to run unit tests later on when we'll build the xPetstore project, which has some tests.

figure 4
Figure 4. A typical directory structure for an EJB-JAR project

Let's now examine the content of the project.xml file. It has all the usual definition found in any Maven project but the part specific to this EJB-JAR project are in two places: in the <build> section and in the <dependencies> one. In the build section, we need to specify the location of our sources:

  <build>
    <defaultGoal>ejb:install</defaultGoal>
    <sourceDirectory>
      src/main/java
    </sourceDirectory>
    <unitTestSourceDirectory>
      src/test/java
    </unitTestSourceDirectory>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
    </resources>
    <unitTest>
      <includes>
        <include>**/*Test.java</include>
      </includes>
      <resources>
        <resource>
          <directory>
            src/test/resources
          </directory>
        </resource>
      </resources>
    </unitTest>
  </build>

Notice also the use of the <defaultGoal> tag. This element is used to tell Maven what goal it should run if you just execute the "maven" command in this project root directory, without specifying any goal.

Note: In Maven 1.0.2 this was achieved by creating a custom maven.xml file and defining a default attribute in the top-level <project> tag. This has been deprecated in Maven 1.1.

This address module actually depends on another module that produces a JAR artifact named xmldocuments-1.4.jar. It also requires a J2EE JAR to be able to compile the EJBs. Thus you need to add the following two dependencies to the project.xml:

  <dependencies>
    <dependency>
      <groupId>com.sun.j2ee.blueprints</groupId>
      <artifactId>xmldocuments</artifactId>
      <version>1.4</version>
      <properties>
        <ejb.manifest.classpath>
          true
        </ejb.manifest.classpath>
      </properties>
    </dependency>
    <dependency>
      <groupId>geronimo-spec</groupId>
      <artifactId>geronimo-spec-ejb</artifactId>
      <version>2.1-rc4</version>
    </dependency>
  </dependencies>

Tip: The J2EE JAR provided by Sun has a license that prevents it from being distributed on the main Maven remote repository on ibiblio. In addition, this J2EE JAR weighs several tens of megabytes. Luckily the Geronimo project provides a readily distributable J2EE JAR that also has the advantages of being lightweight and being available on ibiblio. Use it whenever you have a J2EE project to compile.

Maven: A Developer's Notebook

Related Reading

Maven: A Developer's Notebook
By Vincent Massol, Timothy M. O'Brien

One interesting feature of the EJB plugin is that it generates a Manifest file for your EJB JAR automatically. When your EJB JAR needs to add a dependency on an external library (as it is the case above for xmldocuments) you simply specify it by "tagging" the corresponding dependency with an ejb.manifest.classpath property. In our case the generated Manifest.mf file contains the Class-Path entry specifying the dependent JAR:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: Apache Maven
Built-By: Vincent Massol
Package: com.sun.j2ee.blueprints.address
Class-Path:  xmldocuments-1.4.jar
[...]

Just before executing the build, let's have a look at the project.properties file. This is where you customize Maven plugins. It has three entries:

Let's now build the address project by issuing the "maven" command in the address/ directory (note: code lines have been truncated to 50 characters):

C:\dev\maven-petstore1.4\modules\address>maven 
[...]

ejb:ejb:
ejb:ejb-jar-internal:
    [echo] Building ejb address-1.4
    [jar] Building jar: C:\dev\maven-petstore1.4\
modules\address\target\address-1.4.jar

ejb:ejb-client-internal:
    [jar] Building jar: C:\dev\maven-petstore1.4\
modules\address\target\address-1.4-client.jar

ejb:install:
    [echo] Installing...
Uploading to com.sun.j2ee.blueprints/ejbs/
address-1.4.jar:
.................... (9K)
Uploading to com.sun.j2ee.blueprints/poms/
address-1.4.pom:
.................... (1K)
    [echo] Installing...
Uploading to com.sun.j2ee.blueprints/ejbs/
address-1.4-client.jar:
.................... (8K)
Uploading to com.sun.j2ee.blueprints/poms/
address-1.4.pom:
.................... (1K)

Maven has run the ejb:install goal (specified in our defaultGoal element in the POM) and built both the EJB-JAR and the client jar. It has also published them to your local Maven repository so that they are now available as dependencies for other projects. If you simply want to build the artifacts without installing them, simply specify the ejb:ejb goal (or ejb for short). If you wish to upload them to a Maven remote repository, use ejb:deploy (in that case you'll also need to specify some other properties, which we'll see later on in this article). See the EJB plugin reference documentation for more options.

Note: You may wonder how to include the generated EJB client jar in another project, say a WAR. This is a new feature of Maven 1.1, which has added a new dependency type called "ejb-client." For example, to include the address 1.4 EJB client jar you would add the dependency:

<dependency>
  <groupId>com.sun.j2ee.blueprints</groupId>
  <artifactId>address</artifactId>
  <version>1.4</version>
  <type>ejb-client</type>
</dependency>

We'll demonstrate this later in this article when we build the Java Petstore WAF WAR.

Building an EJB-JAR Using XDoclet

Using XDoclet javadoc annotations is a very good way to reduce EJB 2.0 clutter and to keep source and configuration all together. The xPetstore project is a port of the Java Petstore project using XDoclet annotations. Let's find out how to build an EJB-JAR project using XDoclet with Maven. More specifically, we'll build the business/module introduced earlier with Maven (see Figure 3). Figure 5 shows the directory structure of the business/project.

figure 5
Figure 5. A typical directory structure for an EJB-JAR project using XDoclet

As you can see, the main difference from Figure 4 is that there is only one file for the Customer EJB. This is because this CustomerEJB.java file contains the XDoclet annotations that will generate at build time the CustomerLocal, CustomerLocalHome, CustomerCMP and even CustomerValue and CustomerUtil classes, as shown in Figure 6.

figure 6
Figure 6. Example of classes and descriptors produced by XDoclet at build time

So how do we tell Maven to run XDoclet in our sources? The XDoclet project has a nice Maven XDoclet plugin that can be used for this.

Tip: The XDoclet plugin is not part of the Maven distribution so it needs to be installed in the Maven installation of everyone wanting to build your EJB-JAR project. Luckily Maven provides a nice way to automatically install a plugin as part of your project's build. It is achieved by defining a dependency on the XDoclet plugin. Add the following to the business/project.xml file:

<dependencies>
  <dependency>
    <groupId>xdoclet</groupId>
    <artifactId>maven-xdoclet-plugin</artifactId>
    <version>1.2.3</version>
    <type>plugin</type>
  </dependency>
</dependencies>

Now when you execute Maven it'll automatically download and install the XDoclet plugin if it's not already present in your local Maven installation. Neat, no?

To use the XDoclet plugin we need to tell it to run before Maven compiles the source code. This is achieved by declaring a preGoal on the java:compile goal in a business/maven.xml file:

<project>
  <preGoal name="java:compile">
    <attainGoal name="xdoclet:ejbdoclet"/>
  </preGoal>
</project>

Then, of course, we need to configure the XDoclet plugin to tell it what to generate. This is achieved by setting properties in the business/project.properties file. Here's a portion of the configuration we need for the xPetstore:

maven.xdoclet.ejbdoclet.fileset.0=true
maven.xdoclet.ejbdoclet.fileset.0
  .include=**/*EJB.java

maven.xdoclet.ejbdoclet.entitybmp.0=true
maven.xdoclet.ejbdoclet.entitycmp.0=true
maven.xdoclet.ejbdoclet.entitypk.0=true

maven.xdoclet.ejbdoclet.remoteinterface.0=true
maven.xdoclet.ejbdoclet.remoteinterface.0
  .pattern={0}Remote

maven.xdoclet.ejbdoclet.homeinterface.0=true
maven.xdoclet.ejbdoclet.homeinterface.0
  .pattern={0}RemoteHome

maven.xdoclet.ejbdoclet.localhomeinterface.0=true
maven.xdoclet.ejbdoclet.localinterface.0=true

maven.xdoclet.ejbdoclet.session.0=true

maven.xdoclet.ejbdoclet.packageSubstitution.0=
  true
maven.xdoclet.ejbdoclet.packageSubstitution.0
  .packages=ejb
maven.xdoclet.ejbdoclet.packageSubstitution.0
  .substituteWith=interfaces
[...]

All those properties are actually directly derived from the <ejbdoclet> Ant task. For example, the above is the equivalent of:

<ejbdoclet [...]>
  <fileset [...] includes="**/*EJB.java"/>
  <packageSubstitution 
      packages="ejb" 
      substituteWith="interfaces"/>
  <remoteinterface pattern="{0}Remote"/>
  <homeinterface  pattern="{0}RemoteHome"/>
  <localinterface/>
  <localhomeinterface/>
  <entitypk/>
  <entitycmp/>
  <session/>
[...]
</ejbdoclet>

Note: This is where we realize that XML is a much more concise syntax than the properties file for configuration. This is a lesson that has been learned in Maven 2 as there is no more properties file. All the project definition and plugin configuration is now done in the POM as one XML file.

Make sure you have a <defaultGoal> of ejb:install defined in business/project.xml. You can now build the EJB-JAR by simply typing "maven":

C:\[...]\modules\business>maven
[...]
java:compile:
xdoclet:ejbdoclet:
    [ejbdoclet] Generating EJB deployment 
descriptor (ejb-jar.xml).
[...]
    [ejbdoclet] Generating CMP class for 
'xpetstore.domain.customer.ejb.CustomerEJB'.
[...]
    [ejbdoclet] Generating Local Home interface 
for 'xpetstore.domain.customer.ejb.CustomerEJB'.
[...]
    [ejbdoclet] Generating Local interface for
'xpetstore.domain.customer.ejb.CustomerEJB'.
[...]
    [ejbdoclet] Generating Util class for 
'xpetstore.domain.customer.ejb.CustomerEJB'.
[...]
    [ejbdoclet] Generating Value Object class:
'xpetstore.domain.customer.ejb.CustomerEJB-->
 xpetstore.domain.customer.model.CustomerValue'.
[...]

ejb:ejb:
ejb:ejb-jar-internal:
    [echo] Building ejb xpetstore-business-3.1.3
[...]

Building a WAR

Let's build the Java Petstore waf-web WAR project that we've introduced in Figure 2. WAF stands for Web Application Framework and it's the equivalent of Struts. WAF is a blueprint recommendation from Sun. When we convert the Java Petstore Ant build into a Maven one, we divide the WAF project into several dependent subprojects (Figure 7):

figure 7
Figure 7. WAF Maven subprojects

The waf-controller-ejb subproject is an EJB state machine (it's the extension of the MVC pattern on the server side). The interesting part is that it generates an EJB JAR and an EJB client JAR on which we need to depend on for the waf-web WAR. The other subprojects simply generate standard JARs.

Let's have a closer look at the waf-web directory structure (Figure 8):

figure 8
Figure 8. WAF Web Maven project showing a typical WAR project structure

As you can see, we have located the WAR's web files (JSPs, images, HTML pages, etc) in the src/main/webapp directory. This follows our strategy of putting all the sources that make the main artifact in src/main. The web.xml files goes in src/main/webapp/WEB-INF along with whatever other files you may want to put there (Taglib definitions, etc). Note that for the sake of clarity we've filtered out lots of JSPs and XML files in Figure 8. There are normally more than what you can see, but that doesn't affect our build exercise.

In the case of this waf-web project we have no java source files. If you had any you would put them in src/main/java and define that location in your project.xml, as explained earlier. The java classes will then find their way automatically to the WAR's WEB- INF/classes directory. We'll show this in the section about building a WAR with XDoclet below.

We're going to use the Maven plugin WAR to build our WAR. We need to configure it to tell it that the WAR's web files are located in src/main/webapp. This is done in the project.properties file:

maven.war.src = ${maven.src.dir}/main/webapp

Note: You may wonder why the WAR plugin is not configured by default to look into the recommended place. The reason is historical. In the early days the recommended location for sources was src/java (without the main/part) and thus all plugins have been configured to look there for their files. Thus, for example, the WAR plugin looks for its web files in src/webapp. The reason it hasn't been changed is simply to preserve compatibility. Changing the default will break hundreds, if not thousands, of users who are using the default location. However this is now changed in Maven2.

Let's have a look at the project.xml file. The only special part is the <dependencies> section. Not only does it list the artifacts the waf-web project depends on, it also tells the Maven WAR plugin what artifacts to include in the WAR's WEB-INF/lib directory:

<dependencies>
  <dependency>
    <groupId>com.sun.j2ee.blueprints</groupId>
    <artifactId>waf-util</artifactId>
    <version>${pom.currentVersion}</version>
    <properties>
      <war.bundle>true</war.bundle>
    </properties>
  </dependency>
[...]
  <dependency>
    <groupId>com.sun.j2ee.blueprints</groupId>
    <artifactId>waf-controller-ejb</artifactId>
    <version>${pom.currentVersion}</version>
    <type>ejb-client</type>
    <properties>
      <war.bundle>true</war.bundle>
    </properties>
  </dependency>
  </dependencies>
  <build>
    <defaultGoal>war:install</defaultGoal>
  </build>

Every time we want to tell the WAR plugin to include a specific dependency in the WAR, we tag it using:

    <properties>
      <war.bundle>true</war.bundle>
    </properties>

Notice also the use of the ejb-client type in the dependency on the waf-controller-ejb above. This is the way to tell Maven that we want to depend on the ejb-client jar and not on the main ejb-jar (which we would depend on by specifying an "ejb" type instead of "ejb-client").

Last, as we're lazy (Remember that a lazy programmer is a good programmer!) we specify, as usual, the default goal to run using the <defaultGoal> element. This means that we can now simply type "maven" and the war:install goal is executed:

C:\[...]\modules\waf\waf-web>maven
[...]

war:init:

war:war-resources:

    [mkdir] Created dir: 
[...]\waf-web\target\waf-web
    [mkdir] Created dir: 
[...]\waf-web\target\waf-web\WEB-INF
    [copy] Copying 28 files to 
[...]\waf-web\target\waf-web
    [copy] Copying 1 file to 
[...]\waf-web\target\waf-web\WEB-INF

[...]

war:webapp:
    [echo] Assembling webapp waf-web
[...]

war:war:
    [echo] Building WAR waf-web
    [jar] Building jar: 
[...]\waf-web\target\waf-web.war

war:install:
    [echo] Installing...
Uploading to com.sun.j2ee.blueprints/wars/
waf-web-1.4.war:
.................... (86K)
Uploading to com.sun.j2ee.blueprints/poms/
waf-web-1.4.pom:
.................... (2K)

The Maven WAR plugin has several other properties that can be configured. You can, for example, choose to generate the web.xml file by your own means and tell the plugin. This is done by creating a pregoal on war:war in which you do the generation and then set the maven.war.webxml property to point to the generated file. Those other more exotic settings are explained in the plugin's reference documentation.

Building a WAR Using XDoclet

Let's now build the xPetstore's modules/presentation WAR using XDoclet. If you're not using XDoclet you can safely skip this section. The directory structure is shown in Figure 9:

figure 9
Figure 9. xPetstore's presentation WAR subproject directory structure

The principle is the same as what we saw in the section about building an EJB with XDoclet: you create a custom maven.xml file in which you create a preGoal on java:compile, where you call the xdoclet:webdoclet goal:

  <preGoal name="java:compile">
    <attainGoal name="xdoclet:webdoclet"/>
  </preGoal>

Notice that we have java sources located in src/main/java. When we build the project they'll find their way to the generated WAR's WEB-INF/classes directory. Web files are located in src/main/webapp, as seen in the previous lab. Again this is configured in project.properties:

maven.war.src = ${maven.src.dir}/main/webapp

XDoclet can generate several files useful for a web application. More specifically it can generate the following:

As with the EJB project above, you need to provide the information contained in these generate file in your source code: in your Servlet classes, Filter classes, Tag classes, Struts Action classes, Struts Form classes, etc. For example, the following kind of javadoc annotation can be found in the SignOnFilter.java file:

[...]
 * @web.filter
 *  name="signon"
 *  display-name="xPetstore Struts Signon Filter"
 * @web.filter-mapping
 *  servlet-name="action"
[...]

Then we need to configure the XDoclet Maven plugin so that it generates what we want. This is done by setting properties in the project.properties file:

maven.xdoclet.webdoclet.fileset.4=true
maven.xdoclet.webdoclet.fileset.4
  .include=**/*Action.java
maven.xdoclet.webdoclet.fileset.5=true
maven.xdoclet.webdoclet.fileset.5
  .include=**/*Form.java

This tells the plugin what files to parse. By default it is already preconfigured to parse Servlet, Tag, Filter, and Listener classes, which is why we start the fileset index at 4 and add Struts Actions and Forms:

maven.xdoclet.webdoclet.deploymentdescriptor.0
  =true
maven.xdoclet.webdoclet.deploymentdescriptor.0
  .Servletspec=2.3
maven.xdoclet.webdoclet.deploymentdescriptor.0
  .tagLibs=true
maven.xdoclet.webdoclet.deploymentdescriptor.0
  .ConfiguredTaglib.0=true
maven.xdoclet.webdoclet.deploymentdescriptor.0
  .ConfiguredTaglib.0.uri=xpetstore
maven.xdoclet.webdoclet.deploymentdescriptor.0
  .ConfiguredTaglib.0.location=
    /WEB-INF/xpetstore.tld
[...]

This configures the generation of the web.xml file, including the Servlet specification to use, the taglibs to include, and so on.

maven.xdoclet.webdoclet.strutsconfigxml.0=true

This tells the plugin to generate the strusts-config.xml file.

maven.xdoclet.webdoclet.mergeDir=${maven.src.dir}/xdoclet

This tells the plugin where to find custom file snippets that it will merge with the files it generates. In our case here, we have put these files in src/xdoclet (see Figure 9). We have three files: filter-mappings.xml, filers.xml, and welcomefiles.xml. The content of these three files will be merged with the generated web.xml file. The reason we need to do this is because we want to add a filter definition and mapping for the Sitemesh PageFilter filter. This is a file provided in an external library and thus we can't add javadoc annotations to automatically generate its definition and mapping. The welcomefiles.xml contains the name of the page to call when the user types the root URL for the webapp. As this information cannot be associated with any java source file we need to define it externally from the source code. Note that we could also have defined it as a configuration property in project.properties. The full list of files that can be merged to generate the web.xml file can be found in the xdoclet reference documentation for the deploymentdescriptor element.

The project.xml file is similar to the one shown in the previous section on building a WAR. It contains dependencies tagged with the <war.bundle>true</war.bundle> property.

Running the presentation project's build with "maven war:install" (or simply "maven" if you've defined a default goal) generates the WAR file.

Building an EAR

With your newfound knowledge of building JARs, EJB-JARs, and WARs we are now ready to tackle building applications in the form of a packaged EAR. Let's build the Java Petstore's applications/waf project (Figure 10). The WAF is a reusable presentation framework but the Java Petstore also packages it as an application to demonstrate how it works.

figure 10
Figure 10. Project directory structure to generate the WAF EAR

This is really a minimal Maven project, but we don't need more to generate an EAR. We do this using the Maven EAR plugin. It can automatically generate an application.xml deployment descriptor. Note that we could also provide one by putting it in src/main/application/META-INF/application.xml and telling the EAR plugin to use it by setting the following in our project.properties file (Again this is required because by default the EAR plugin looks into src/application, which is no longer the recommended directory structure, as we saw earlier.):

maven.ear.src = ${maven.src.dir}/main/application

As we want to generate the application.xml file, the only things that we need to specify are the dependencies that will get included in the EAR (JARs, EJB-JARs, WARs, RARs, and SARs). For example we need to include the WAF WAR that we created earlier on in a previous lab. This is done in project.xml as follows:

<dependency>
  <groupId>com.sun.j2ee.blueprints</groupId>
  <artifactId>waf-web</artifactId>
  <version>1.4</version>
  <type>war</type>
  <properties>
    <ear.bundle>true</ear.bundle>
    <ear.appxml.war.context-root>
      /waf
    </ear.appxml.war.context-root>
  </properties>
</dependency>

This follows the same principle that we already used when building a WAR: we tag the dependencies we want to include with an ear.bundle property set to true. However, we have also used the ear.appxml.war.context-root property, which tells the EAR plugin what context root our WAR should have. This information will find its way into the generated application.xml:

<module>
  <web>
    <web-uri>waf-web-1.4.war</web-uri>
    <context-root>/waf</context-root>
  </web>
</module>

There are also three other useful properties:

Now in our case we have no other resources to add to our EAR, but if we had any we could put them in src/main/applications, or we could put them in src/main/resources and specify them as resources in the project.xml:

<build>
  [...]
  <resources>
    <resource>
      <directory>src/main/resources</directory>
    </resource>
  </resources>

Running the build with "maven ear:install" generates the EAR file and installs it in your local repository:

C:\[...]\applications\waf>maven ear:install
[...]
ear:generate-ear-descriptor:
    [echo] Generating appxml file:
"C:\dev\maven-petstore1.4\applications\waf
/target/application.xml
version 1.3 encoding: UTF-8"

    [echo] Building EAR waf-ear-1.4.ear with appxml 
"C:\dev\maven-petstore1.4\applications\waf
/target/application.xml"
    [ear] Building ear: C:\dev\maven-petstore1.4\
applications\waf\target\waf-ear-1.4.ear

ear:install:
    [echo] Installing...
Uploading to com.sun.j2ee.blueprints/ears/
waf-ear-1.4.ear:
.................... (82K)
Uploading to com.sun.j2ee.blueprints/poms/
waf-ear-1.4.pom:
.................... (1K)

We have completed our tour of building J2EE applications with Maven. You should now be able to find your way around Maven and explore more by using the reference documentation for each of the Maven plugins we have touched upon.

Vincent Massol is the creator of the Jakarta Cactus framework and an active member of the Maven project since the early days. After having spent four years as a technical architect on several major projects (mostly J2EE), Vincent is now the co-founder and CTO of Pivolis, a company specializing in applying agile methodologies to offshore software development. He lives in the City of Light, Paris, France and can be found online at www.massol.net.


Return to ONJava.com

Copyright © 2009 O'Reilly Media, Inc.