Sonatype

Previous: Chapter 15

This chapter follows an example project. You may wish to download the calculator-stateless-pojo example and follow along.

The Culmination

Although this book is not slated to be a cookbook of project recipies, but rather a guide to the pieces that make up the Maven puzzle, we would be remiss in out duties were we to ignore one of the most common project structures: a Java Enterprise Edition (Java EE - formerly J2EE) project. Although it is unnecessary to read the entire book to make sense of this chapter, nor can you proceed green. To this end, we suggest you begin with the first couple of chapters, get a good feel for Maven, before proceeding here.

The Project

Our Java EE project will have the following structure: an EJB project consisting of a simple EJB 3 Session bean, a WAR containing presentation files as JSPs, and an EAR containing the modules and dependencies. We will then go over the task of using Maven to help develop, test and finally deploy this project. The sample project is based upon the Apache Geronimo Stateless Calculator POJO sample project, version 2.

Note: The examples in this chapter will download and install two entire application servers in the course of testing - one Jetty and one JBoss. You mut have 200 Megs of diskspace available to install and run each safely. If diskspace is a consideration, then do not run the demos.

Note: You must be running Maven version 2.0.5 or above to run these examples properly.

Using Archetypes to Generate Project

Maven Central comes with several archetypes available to generate the basic project structures. First, create a base directory and change to it and generate the EJB directories. Since we wil make use of EJB3, this project requires Java 5 (jdk 1.5) to run. Why? Because EJB 2 is so 2004.

Alert: We avoid using the maven-archetype-j2ee-simple archetype here because it generates a sample J2EE project assuming EJB2. If you use EJB2, then by all means, go ahead and use it!

mkdir calculator-stateless-pojo
cd calculator-stateless-pojo

Create the base project and site.

mvn archetype:create \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-site \
  -DarchetypeVersion=1.0 \
  -DgroupId=org.apache.geronimo.samples \
  -DartifactId=calculator-stateless
  -Dversion=2.0-SNAPSHOT

We use the site creation archetype for a dual purpose. It generates a starting calculator-stateless project POM - and the project's site structure with some example docs. We will alter the generated pom.xml later which will server as a parent and multi-module POM for the entire project.

Create the project which will house the EJB layer.

mvn archetype:create \
  -DgroupId=org.apache.geronimo.samples \
  -DartifactId=calculator-stateless-ejb
  -Dversion=2.0-SNAPSHOT

And next create the WAR project which will be the presentation layer.

mvn archetype:create \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-webapp \
  -DarchetypeVersion=1.0 \
  -DgroupId=org.apache.geronimo.samples \
  -DartifactId=calculator-stateless-war
  -Dversion=2.0-SNAPSHOT

Finally, create the simple EAR project, which will house these modules when packaged.

mkdir calculator-stateless-ear
touch calculator-stateless-ear/pom.xml

With these few short commands, you now have the following structure. Although unbuildable in its current form, the basic skeleton is complete.

Filling Out the Project

The previous section displayed how one may begin a Maven-based Java EE project in under a minute. Now, we will make the project work. You can either blow away the project structure we created above and copy the sample project (it follows the same strcuture), or you can copy the sample project files into the structure you created manually. In either case, your project stucture should resemble this.

Note the addition of the src/test directory to the EAR project, and the removal of the EJB's generated App.java and test files.

calculator-stateless-pojo
|-- calculator-stateless-ear
|   |-- pom.xml
|   `-- src
|     |-- main
|     |   `-- resources
|     |     `-- META-INF
|     |       `-- geronimo-application.xml
|     `-- test
|       |-- java
|       |   `-- org
|       |     `-- apache
|       |     `-- geronimo
|       |       `-- samples
|       |       `-- calculator
|       |         `-- SeleniumTest.java
|       `-- resources
|         `-- my-selenium-test.properties
|-- calculator-stateless-ejb
|   |-- pom.xml
|   `-- src
|     `-- main
|       `-- java
|         `-- org
|           `-- apache
|           `-- geronimo
|             `-- samples
|             `-- slsb
|               `-- calculator
|                 |-- Calculator.java
|                 |-- CalculatorLocal.java
|                 `-- CalculatorRemote.java
|-- calculator-stateless-war
|   |-- pom.xml
|   `-- src
|     `-- main
|       |-- java
|       |   `-- org
|       |     `-- apache
|       |     `-- geronimo
|       |       `-- samples
|       |       `-- calculator
|       |         `-- CalculatorServlet.java
|       `-- webapp
|         |-- index.html
|         |-- sample-docu.jsp
|         |-- images
|         |   |-- menu_javadoc_off_116x19.gif
|         |   `-- menu_src_off_116x19.gif
|         `-- WEB-INF
|          `-- web.xml
`-- calculator-stateless
  |-- pom.xml
  `-- src
    `-- site
      |-- site.xml
      |-- site_fr.xml
      |-- fr
      |   |-- apt
      |   |   |-- format.apt
      |   |   `-- index.apt
      |   |-- fml
      |   |   `-- faq.fml
      |   `-- xdoc
      |     `-- xdoc.xml
      |-- apt
      |   |-- format.apt
      |   `-- index.apt
      |-- fml
      |   `-- faq.fml
      `-- xdoc
        `-- xdoc.xml
  • calculator-stateless - This is the parent project and the multi-module project for the EJB, WAR and EAR modules. This also contains the project site. In some project layouts this pom and site data can be in the base directory. I placed it in a parallel directory to illustrate two things: 1) how the module elements are relative paths to project base directories, not artifactIds, and 2) the Parent relativePath has a very useful purpose.
  • calculator-stateless-ejb - A simple EJB 3 project containing a Calculator Stateless Session bean. This performs two actions: add and multiply two integers.
  • calculator-stateless-war - A WAR that contains a CalculatorServlet that uses the EJB. It is populated by the Java EE container via dependency injection. Beyond that, it also contains a JSP and other static web files. When built, it also houses the project site (moved from the calculator-stateless project when built).
  • calculator-stateless-ear - Generates an application descriptor and packages the EJB and WAR project along with dependencies as a deployable EAR. The EAR POM also contains three profiles: one to run on Geronimo, one to run on JBoss, and a third that runs integration tests on JBoss.

Before digging into the details of the POM structure, let's first generate the project site under the calculator-stateless base project and install it.

cd calculator-stateless
mvn site install

Since it is a multi-module project, it will run the site lifecycle phases, then the build lifecycle phases (up to install) on each of the project modules. Your build should begin with

[INFO] Scanning for projects...
[INFO] Reactor build order: 
[INFO]   Geronimo Calculator
[INFO]   Geronimo Calculator EJB
[INFO]   Geronimo Calculator WAR
[INFO]   Geronimo Calculator EAR
[INFO] ----------------------------------------------------------------------------
[INFO] Building Geronimo Calculator
[INFO]  task-segment: [site, install]
[INFO] ----------------------------------------------------------------------------

and end with

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Geronimo Calculator ................................... SUCCESS [12.767s]
[INFO] Geronimo Calculator EJB ............................... SUCCESS [1.082s]
[INFO] Geronimo Calculator WAR ............................... SUCCESS [1.482s]
[INFO] Geronimo Calculator EAR ............................... SUCCESS [0.560s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16 seconds
[INFO] Finished at: Sat Apr 14 20:54:30 CDT 2007
[INFO] Final Memory: 28M/51M
[INFO] ------------------------------------------------------------------------

Now that we know the projects are all installed, it's time to run the Geronimo profile install phase inside of the EAR project. It will likely take a few minutes, as the Geronimo plugin will download a Jetty 6 container to your project's target. This should only happen once, for then it lives in your local repository. Note: certain environments may require special access to open ports, for example, by executing the <<<startup.sh>> command via "sudo" in Mac OSX or Ubuntu Linux.>

cd ../calculator-stateless-ear
mvn install -P geronimo
./target/geronimo-jetty6-jee5-2.0-SNAPSHOT/bin/startup.sh

You can visit the project in a web browser at: http://localhost:8080/calculator-stateless/

./target/geronimo-jetty6-jee5-2.0-SNAPSHOT/bin/shutdown.sh

You may be asked for a Username and Password to complete shut down. They are system and manager, respectively.

Using GERONIMO_BASE:   ~/calculator-stateless-pojo/calculator-stateless-ear/target/geronimo-jetty6-jee5-2.0-SNAPSHOT
Using GERONIMO_HOME:   ~/calculator-stateless-pojo/calculator-stateless-ear/target/geronimo-jetty6-jee5-2.0-SNAPSHOT
Using GERONIMO_TMPDIR: ~/calculator-stateless-pojo/calculator-stateless-ear/target/geronimo-jetty6-jee5-2.0-SNAPSHOT/var/temp
Using JRE_HOME:    /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
Username: system
Password: *******
Locating server on port 1099... Server found.
Server shutdown begun
Server shutdown completed

The Details

Assuming all went to plan you should have had a fully functioning build. Considering that you did not have to download anything manually from the internet, it was relatively painless operation: a few configurations, install the project and start the server. If only all things were so easy! But, although it was simple from the user perspective, a fair amount of configuration lay underneath. Let's take a look at the simplest POM, for the EJB project.

Note: These POMs will be slightly different from the sample projects available for download to be simpler to read. See if you can spot the differences.

<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
  <groupId>org.apache.geronimo.samples</groupId>
  <artifactId>calculator-stateless</artifactId>
  <version>2.0-SNAPSHOT</version>
  <relativePath>../calculator-stateless/pom.xml</relativePath>
  </parent>
  <artifactId>calculator-stateless-ejb</artifactId>
  <name>Geronimo Calculator EJB</name>
  <packaging>ejb</packaging>

  <build>
  <plugins>
    <plugin>
    <artifactId>maven-ejb-plugin</artifactId>
    <configuration>
      <ejbVersion>3.0</ejbVersion>
    </configuration>
    </plugin>
  </plugins>
  </build>
</project>

This project couldn't be more straightforward. The EJB project inherits from the org.apache.geronimo.samples:calculator-stateless project which is relative to the same depth as this project, is of packaging type EJB, and we configured the maven-ejb-plugin to treat this project as ejbVersion 3.0. If not set, ejbVersion defaults to 2.1.

The WAR is slightly more complex, but not by much.

<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.apache.geronimo.samples</groupId>
    <artifactId>calculator-stateless</artifactId>
    <version>2.0-SNAPSHOT</version>
    <relativePath>../calculator-stateless/pom.xml</relativePath>
  </parent>
  <artifactId>calculator-stateless-war</artifactId>
  <name>Geronimo Calculator WAR</name>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>${groupId}</groupId>
      <artifactId>calculator-stateless-ejb</artifactId>
      <version>${version}</version>
     <type>ejb</type>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-parent-site</id>
            <phase>process-resources</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <echo>Copying site directory from parent</echo>
                <copy
                    todir="${project.build.directory}/${artifactId}-${version}"
                    failonerror="false"
                    overwrite="true">
                  <fileset dir="${basedir}/../calculator-stateless/target/site"/>
                </copy>
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
          <warSourceExcludes>WEB-INF/lib/*.jar</warSourceExcludes>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <classpathPrefix>lib/</classpathPrefix>
            </manifest>
          </archive>
        </configuration>   
      </plugin>
    </plugins>
  </build>
</project>

Like the EJB, the WAR inherits from calculator-stateless. This project is packaged as a WAR, meaning that - besides generating an artifact with a *.war extension - it also has different goals bound to the build lifecycle phases as well as different requirements. A WAR requires a web.xml file to succeed, else a build error.

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Error assembling WAR: Deployment descriptor: ~/calculator-stateless-pojo/calculator-stateless-war/target/calculator-stateless-war-2.0-SNAPSHOT/WEB-INF/web.xml does not exist.

By default it looks for src/main/webapp/WEB-INF/web.xml - see the maven-war-plugin for details on configuring the war package goal. The WAR lifecycle also packages all files under src/main/webapp into the root of the WAR, similar to src/main/resources - in fact you can use both, for example to filter an .html file but leave a .jsp file alone.

The two plugins that are configured here are the maven-antrun-plugin and the maven-war-plugin. The antrun plugin is being used as a convenient way to copy all the calculator-stateless site to the ${project.build.directory}/${artifactId}-${version} directory (which, when the properties are realized, becomes target/calculator-stateless-war-2.0-SNAPSHOT). This is the directory that the war:war goal uses as a temporary location to hold files as it builds the final WAR artifact (the parameter is webappDirectory, which can naturally be reconfigured - were you inclined to do so).

The war plugin is configured to strip out all JAR files from being packaged. This practice is known as creating a "skinny war". By default the WAR plugin packages up it's dependencies (a "fat war"). However, we want the EAR artifact to bundle the WAR's dependencies in it's root instead. Were we to allow the WAR to bundle it's own dependencies, then the generated EAR would contain two sets of the same dependencies - one set in it's own root, and one set in the bundled WAR.

GOTCHA: The downside of this "skinny war" process is that you must add the WAR's dependencies into the EAR, since the maven-ear-plugin does not currently download a WAR's transative dependencies. This should be fixed in later versions of the plugin.

The EAR is what will bundle the WAR and EJB into a single Enterprise Archive.

<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.apache.geronimo.samples</groupId>
    <artifactId>calculator-stateless</artifactId>
    <version>2.0-SNAPSHOT</version>
    <relativePath>../calculator-stateless/pom.xml</relativePath>
  </parent>
  <artifactId>calculator-stateless-ear</artifactId>
  <name>Geronimo Calculator EAR</name>
  <packaging>ear</packaging>

  <dependencies>
    <dependency>
      <groupId>${groupId}</groupId>
      <artifactId>calculator-stateless-ejb</artifactId>
      <version>${version}</version>
      <type>ejb</type>
    </dependency>
    <dependency>
      <groupId>${groupId}</groupId>
      <artifactId>calculator-stateless-war</artifactId>
      <version>${version}</version>
      <type>war</type>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ear-plugin</artifactId>
        <configuration>
          <displayName>Geronimo Sample EAR for Stateless Session</displayName>
          <description>Geronimo Sample EAR for Stateless Session</description>
          <version>5</version>
          <modules>
            <webModule>
              <groupId>${groupId}</groupId>
              <artifactId>calculator-stateless-war</artifactId>
              <contextRoot>/calculator-stateless</contextRoot>
              <bundleFileName>calculator-stateless-war-${version}.war</bundleFileName>
            </webModule>
            <ejbModule>
              <groupId>${groupId}</groupId>
              <artifactId>calculator-stateless-ejb</artifactId>
              <bundleFileName>calculator-stateless-ejb-${version}.jar</bundleFileName>
            </ejbModule>
          </modules>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <profiles>
    ...
  </profiles>
</project>

The profiles have been removed for now. The things to note are the two dependencies which will be packaged into the EAR, and the EAR plugin configuration, which is used by the ear:generate-application-xml goal to generate the EAR's application descriptor - which is a required component of an EAR, much like the web.xml for the WAR.

The important piece to note on the EAR plugin is the modules configuration. There are ten supported types at the time of writing.

  • ejb3Module - ejb3 (Enterprise Java Bean version 3)
  • ejbClientModule - ejb-client (Enterprise Java Bean Client)
  • ejbModule - ejb (Enterprise Java Bean)
  • jarModule - jar (Java Archive)
  • harModule - har (Hibernate Archive)
  • parModule - par (Plexus Archive)
  • rarModule - rar (Resource Adapter Archive)
  • sarModule - sar (JBoss Service Archive)
  • webModule - war (Web Application Archive)
  • wsrModule - wsr (JBoss Web Service Archive)

Each of the above modules accepts the following configuration elements.

  • groupId - the groupId of the configured artifact
  • artifactId - the artifactId of the configured artifact
  • classifier - the classifier of the configured artifact, if needed
  • bundleDir - the location of this artifact inside the ear archive - defaults to the root of the archive
  • bundleFileName - the new name of this artifact inside the ear archive - defaults to the artifact's finalname
  • excluded - exclude this artifact from being packaged into the ear archive - defaults to false
  • uri - sets the uri path of this artifact within the ear archive. Automatically determined when not set
  • unpack - unpack this artifact into the ear archive according to its uri - defaults to false

The webModule then has a special element, contextRoot:

  • contextRoot - sets the context root of this web artifact

The jarModule has another element, since by default JARs are not added to the application descriptor.

  • includeInApplicationXml - generate an entry of this module in application.xml - defaults to false

Finally, if you have a requirement for a non-supported artifact mapping (for example a Native Archive, or a NAR), add the following to the maven-ear-plugin's configuration to map the artifact type to an existing module type.

      <artifactTypeMappings>
        <artifactTypeMapping type="nar" mapping="jar"/>
      </artifactTypeMappings>

Like most plugins in this book, there are more configurations available for the maven-ear-plugin. Consult the online documentation for a more comprehensive and up to date list .

The last pom that we will go over is the base pom. It it both a parent and a multi-module POM, meaning that the sub-projects inherit this project's POM settings and this project coallates the sub-projects into a single build.

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.apache.geronimo.samples</groupId>
  <artifactId>calculator-stateless</artifactId>
  <version>2.0-SNAPSHOT</version>
  <name>Geronimo Calculator</name>
  <packaging>pom</packaging>

  <licenses>
    <license>
    <name>Apache 2</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
    <distribution>repo</distribution>
    <comments>A business-friendly OSS license</comments>
    </license>
  </licenses>

  <!-- note that the modules are relative paths -->
  <modules>
    <module>../calculator-stateless-ejb</module>
    <module>../calculator-stateless-war</module>
    <module>../calculator-stateless-ear</module>
  </modules>

  <!-- these dependencies will be inherited by all children -->
  <dependencies>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-jsp_2.1_spec</artifactId>
      <version>1.0-M1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-ejb_3.0_spec</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-servlet_2.5_spec</artifactId>
      <version>1.1-M1</version>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <!-- all children src/main/resources directories will now be filtered -->
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
  <plugins>
    <!-- set this project compiler to jdk version 1.5 -->
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
      <source>1.5</source>
      <target>1.5</target>
    </configuration>
    </plugin>
  </plugins>
  </build>

  <reporting>
    <!-- run the Javadoc and JXR reports -->
    <plugins>
      <plugin>
        <artifactId>maven-javadoc-plugin</artifactId>
        <configuration>
          <aggregate>true</aggregate>
          <minmemory>128m</minmemory>
          <maxmemory>512</maxmemory>
          <breakiterator>true</breakiterator>
          <quiet>true</quiet>
          <verbose>false</verbose>
          <source>1.5</source>
          <linksource>true</linksource>
          <links>
            <link>http://java.sun.com/j2se/1.5.0/docs/api/</link>
            <link>http://java.sun.com/j2ee/1.4/docs/api/</link>
            <link>http://jakarta.apache.org/commons/collections/apidocs</link>
            <link>http://www.junit.org/junit/javadoc/</link>
          </links>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-jxr-plugin</artifactId>
        <configuration>
          <aggregate>true</aggregate>
        </configuration>
      </plugin>
    </plugins>
  </reporting>

  <profiles>
  ...
  </profiles>
</project>

Since we configure the licenses, reporting, resources, set the compiler to 1.5 and added some required dependencies in the parent POM, we have no need to repeat this information to the child projects - and have a single point from which to make changes to all inheriting project's defaults (which the children can override, see more on inheritence in the chapter on The POM and Project Relationships.

Geronimo Configuration

The POMs shown above were examples of each projects in the simplest sense. Although running mvn site from the calculator-stateless directory will generate a Maven site - complete with Javadoc and a Java Cross Reference (JXR) source report; and although running mvn install will indeed generate descriptor files, compile, unit test, package and install all of the projects to the local repository - they will not install and run our Geronimo server, which we used to test the application on the local machine.

For this alternate use-case we create a profile.

<project>
  ...
  <artifactId>calculator-stateless</artifactId>
  ...
  <profiles>
    <profile>
      <id>geronimo</id>
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <groupId>org.apache.geronimo.plugins</groupId>
              <artifactId>geronimo-maven-plugin</artifactId>
              <configuration>
                <assemblies>
                  <!-- hint: see the geronimo-maven-plugin documentation for more assemblies -->
                  <assembly>
                    <id>jetty</id>
                    <groupId>org.apache.geronimo.assemblies</groupId>
                    <artifactId>geronimo-jetty6-jee5</artifactId>
                    <version>${version}</version>
                    <classifier>bin</classifier>
                    <type>zip</type>
                  </assembly>
                </assemblies>
                <defaultAssemblyId>jetty</defaultAssemblyId>
                <background>true</background>
                <moduleArchive>${project.build.directory}/${artifactId}-${version}.ear</moduleArchive>
              </configuration>
              <executions>
                <execution>
                  <id>deploy-and-start</id>
                  <phase>install</phase>
                  <goals>
                    <goal>deploy-module</goal>
                    <goal>start</goal>
                  </goals>
                </execution>
              </executions>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </profile>
  </profiles>
</project>

A quick glance will tell us a few things about this configuration

  1. It will only be executed if a plugin actively adds the plugin to it's configuration (vis-a-vis the pluginManagement element). We had two options: place the profile in the EAR or configure using the pluginManagement element in the base project. We chose to do the former for posterity - EAR developers down the road may with to use the same configuration without all that typing... DRY.
  2. Geronimo will use Jetty in some capacity. Which is correct - the plugin will download an assembly artifact containing this server and install it locally before use.
  3. The server will attempt to launch a project's default-named EAR artifact (moduleArchive).
  4. the deploy-module and start goals will be bound to the install phase.Despite the scarey-looking nature of the configuration, it does quite a lot. Our EAR POM can now use the plugin by simply asking to (and, pointing to it's own geronimo descriptor).
    <project>
      ...
      <artifactId>calculator-stateless-ear</artifactId>
      ...
      <profiles>
        <profile>
          <id>geronimo</id>
          <build>
            <plugins>
              <plugin>
                <groupId>org.apache.geronimo.plugins</groupId>
                <artifactId>geronimo-maven-plugin</artifactId>
                <configuration>
                  <modulePlan>${project.build.outputDirectory}/META-INF/geronimo-application.xml</modulePlan>
                </configuration>
              </plugin>
            </plugins>
          </build>
        </profile>
      </profiles>
    </project>

    We put it under a matching profile id. Now when we run the command mvn install -P geronimo from the EAR project, it will inherit the configuration from the matching active profile in the parent POM - and run the EAR with Geronimo using Jetty.

    GOTCHA: Since the plugin is set in the EAR POM under a profile, the parent project's geronimo plugin must be configured under a profile that will be active - otherwise, Maven will not be able to find the parent configuration (since it is inactive). The easiest way to do this is by making the profile ids or activations match.

Running with Free Cargo

Although the above example using the org.apache.geronimo.plugins:geronimo-maven-plugin plugin is sufficient for Geronimo, what if you need to depoy to another application server, such as JBoss? Enter Cargo - an abstract way to start, stop and deploy to various types of Java EE contrainers. You can find all sorts of detailed information about Cargo at the project website: http://cargo.codehaus.org.

The Cargo Plugin

The maven-cargo-plugin has several goals:

  • start - Start a container and optionally deploy deployables (WAR, EAR, etc)
  • stop - Stop a container
  • deployer-deploy - (aliased to cargo:deploy) Deploy a deployable to a running container
  • deployer-undeploy - (aliased to cargo:undeploy) Undeploy a deployable from a running container
  • deployer-start - Start a deployable already installed in a running container
  • deployer-stop - Stop a deployed deployable without undeploying it
  • deployer-redeploy - Undeploy and deploy again a deployable
  • uberwar - Merge several WAR files into one
  • install - Installs a container distribution on the file system. Note that this is performed automatically by the cargo:start goal

At the time of this writing, Cargo supported 9 containers and 16 versions.

  • Geronimo 1.x - geronimo1x
  • JBoss 3.x, 4.x - jboss3x, jboss4x
  • Jetty 4.x, 5.x, 6.x - jetty4x, jetty5x, jetty6x
  • jo! 1.x - jo1x
  • OC4J 9.x - oc4j9x
  • Orion 1.x, 2.x - orion1x, orion2x
  • Resin 2.x, 3.x - resin2x, resin3x
  • Tomcat 3.x, 4.x, 5.x - tomcat3x, tomcat4x, tomcat5x
  • WebLogic 8.x - weblogic8x

JBoss Example

With Cargo, deploying to JBoss is as easy as the Geronimo example. Just like the Geronimo plugin downloaded the Jetty 6 container, so too have we configured the Cargo plugin to download a JBoss zip - so be prepared to wait a while if you have a slow connection - JBoss is approximately 80+ Megs. Once it is loaded and started, visit http://localhost:8080/ to see that the JBoss base page is running. If it does not, check to ensure that the ZIP was downloaded successfully. Hit Control-C to stop the server from running.

<project>
  ...
  <artifactId>calculator-stateless</artifactId>
  ...
  <profiles>
    <profile>
      <id>jboss</id>
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <groupId>org.codehaus.cargo</groupId>
              <artifactId>cargo-maven2-plugin</artifactId>
              <version>0.3.1</version>
              <configuration>
                <container>
                  <containerId>jboss4x</containerId>
                  <zipUrlInstaller>
                     <url>http://downloads.sourceforge.net/jboss/jboss-4.0.5.GA.zip</url>
                     <installDir>${user.home}/jboss</installDir>
                   </zipUrlInstaller>
                </container>
                <configuration>
                  <home>${project.build.directory}/jboss/server</home>
                </configuration>
              </configuration>
              <executions>
                <execution>
                  <id>start-container</id>
                  <phase>install</phase>
                  <goals>
                    <goal>start</goal>
                  </goals>
                </execution>
              </executions>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </profile>
  </profiles>
</project>

Like the geronimo-maven-plugin the bulk of the configuration is done in the parent project under pluginManagement - so as not to attach the plugin to all inheriting POMs. We can see a few facts about this configuration:

  1. The container used is JBoss 4 and can be downloaded in zip from from the given URL. Cargo will download and install JBoss for you in your user directory/jboss. Where as the Geronimo plugin tackles this issue by packaging up a container server as a special assembly, Cargo accepts the project's existing layout.
  2. The configuration for the server will be put up under the build directory (probably target/jboss/server).
  3. The cargo:start goal is to be bound to the install phase.Just like the Geronimo plugin, the EAR project need only put the org.codehaus.cargo:cargo-maven2-plugin plugin under the jboss profile. Note that there is no real necessity that these configurations live under profiles, we do it here out of convenience. In fact, the next section, integration testing, may well be desired default behavior.

    More interesting is the next profile, configurated to run as part an integration test.

Integration Testing

There exist a few ways to run integration tests in a Java EE application, but they all have a one step in common: before they begin any application servers must be started. So far we have seen how to start a server using Geronimo and Cargo. Now we will bind the Cargo start>> goal to the <<<pre-integration-test phase, and stop the server on post-integration-test.

The jboss-test profile is similar to the jboss profile with a couple changes. First: the install phase biding execution is replaced with the following two executions:

              <executions>
                <execution>
                  <id>start-container</id>
                  <phase>pre-integration-test</phase>
                  <goals>
                    <goal>start</goal>
                  </goals>
                </execution>
                <execution>
                  <id>stop-container</id>
                  <phase>post-integration-test</phase>
                  <goals>
                    <goal>stop</goal>
                  </goals>
                </execution>
              </executions>

Second: we need to set a configuration element wait to false, otherwise the cargo-maven2-plugin will run (wait) until you manually shutdown with "Ctrl-C".

                <wait>false</wait>

And finally, the pluginManagement configures an execution to start a Selenium server before integration testing.

            <plugin>
              <groupId>org.codehaus.mojo</groupId>
              <artifactId>selenium-maven-plugin</artifactId>
              <executions>
                <execution>
                  <phase>pre-integration-test</phase>
                  <goals>
                    <goal>start-server</goal>
                  </goals>
                  <configuration>
                    <background>true</background>
                  </configuration>
                </execution>
              </executions>
            </plugin>

You can learn more about Selenium as an integration test tool for browser client-side testing from the Selenium website (http://openqa.org/selenium/). We embed the test into a JUnit class file for this example. In order to get the integration test to run, we need to make a few changes to the default EAR build lifecycle. We achieve this by binding the compiler:testCompile goal to the test-compile phase, and finally binding surefire:test (unit test execution framework) to run at the integration-test phase.

The cargo and selenium plugins are, again, added here so that they will run inheriting the parent configuration.

<project>
  ...
  <artifactId>calculator-stateless-ear</artifactId>
  ...
  <profiles>
    <profile>
      <id>jboss-test</id>
      <build>
        <testResources>
          <testResource>
            <directory>src/test/resources</directory>
            <filtering>true</filtering>
          </testResource>
        </testResources>
        <plugins>
          <plugin>
            <groupId>org.codehaus.cargo</groupId>
            <artifactId>cargo-maven2-plugin</artifactId>
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>selenium-maven-plugin</artifactId>
          </plugin>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <executions>
              <execution>
                <phase>test-compile</phase>
                <goals>
                  <goal>testCompile</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <executions>
              <execution>
                <phase>process-test-resources</phase>
                <goals>
                  <goal>testResources</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <executions>
              <execution>
                <phase>integration-test</phase>
                <goals>
                  <goal>test</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
      <properties>
        <selenium.browser.type>*firefox</selenium.browser.type>
      </properties>
    </profile>
  </profiles>
</project>

Our test also contains a resource file that we wish to filter, containing the type of browser we wish selenium to test our selenium script on. This is not required, but merely a good practice, for now we can change the browser tested upon by editing a property in our POM, leaving this configuration up to the builder to decide. So we add src/test/resources resource directory and filter, and bind resources:testResources to the process-test-resources phase. The my-selenium-test.properties file is this:

browser=${selenium.browser.type}

But after the process-test-resources phase will be used by the test as this:

browser=*firefox

Our unit, test in question (org.apache.geronimo.samples.calculator.SeleniumTest) contains a single test:

  public void testRunningCalculator()
    throws Exception
  {
    // getBrowserType reads the my-selenium-test.properties file for the browser to run on
    DefaultSelenium selenium = 
      new DefaultSelenium( "localhost", 4444, getBrowserType(), "http://localhost:8080/calculator-stateless/" );

    selenium.start();

    selenium.open( "http://localhost:8080/calculator-stateless/sample-docu.jsp" );

    selenium.waitForPageToLoad( "5000" );

    assertEquals( "A Stateless Session Sample - Calculator", selenium.getTitle() );

    assertTrue( selenium.isTextPresent( "Calculator" ) );

    selenium.stop();
  }

Our install execution will now run as follows when running mvn install -P jboss-test in the EAR project:

[INFO] [ear:generate-application-xml]
[INFO] Generating application.xml
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [resources:testResources {execution: default}]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile {execution: default}]
[INFO] Compiling 1 source file to ~/calculator-stateless-pojo/calculator-stateless-ear/target/test-classes
[INFO] [ear:ear]
...

Once the ear is packaged, cargo is started at the pre-integration-test phase.

[INFO] [cargo:start {execution: start-container}]
[INFO] [neLocalConfiguration] Configuring JBoss using the [default] server configuration
[INFO] [talledLocalContainer] Parsed JBoss version = [4.0.5]
[INFO] [stalledLocalDeployer] Deploying [~/calculator-stateless-pojo/calculator-stateless-ear/target/calculator-stateless-ear-2.0-SNAPSHOT.ear] to [~/calculator-stateless-pojo/calculator-stateless-ear/target/jboss/server/deploy]...
[INFO] [talledLocalContainer] JBoss 4.0.5 starting...
[INFO] [talledLocalContainer] 23:56:14,100 INFO  [Server] Starting JBoss (MX MicroKernel)...
...
[INFO] [talledLocalContainer] JBoss 4.0.5 started on port [8080]

The selenium server is also started at the pre-integration-test phase. This plugin has no shutdown phase, and so will shutdown with the Maven lifecycle is complete. C'est la vie.

[INFO] [selenium:start-server {execution: default}]
[INFO] Starting Selenium server...
[INFO] User extensions: ~/calculator-stateless-pojo/calculator-stateless-ear/target/selenium/user-extensions.js
[INFO] 00:38:15,897 INFO  [org.mortbay.http.HttpServer] Version Jetty/0.8.1
...
[INFO] 00:38:16,655 INFO  [org.mortbay.util.Credential] Checking Resource aliases
[INFO] Selenium server started

The SeneliumTest case executes as part of the intergration-test. We used the surefire:test goal for that. This is a good example of the power of goals - since good goals are atomic, we are free to move them anywhere in the lifecycle - even re-purpose them to other uses (surefire:test traditionally only runs in the unit test phase.

[INFO] [surefire:test {execution: default}]
[INFO] Surefire report directory: ~/calculator-stateless-pojo/calculator-stateless-ear/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.apache.geronimo.samples.calculator.SeleniumTest
[INFO] queryString = cmd=getNewBrowserSession&1=*firefox&2=http%3A%2F%2Flocalhost%3A8080%2Fcalculator-stateless%2F
[INFO] Preparing Firefox profile...
[INFO] Launching Firefox...
[INFO] 01:23:54,994 INFO  [org.mortbay.util.Container] Started HttpContext[/,/]
[INFO] Got result: OK,1176963830527
[INFO] queryString = cmd=open&1=http%3A%2F%2Flocalhost%3A8080%2Fcalculator-stateless%2Fsample-docu.jsp&sessionId=1176963830527
[INFO] Got result: OK
[INFO] queryString = cmd=waitForPageToLoad&1=5000&sessionId=1176963830527
[INFO] Got result: OK
[INFO] queryString = cmd=getTitle&sessionId=1176963830527
[INFO] Got result: OK,A Stateless Session Sample - Calculator
[INFO] queryString = cmd=isTextPresent&1=Calculator&sessionId=1176963830527
[INFO] Got result: OK,true
[INFO] queryString = cmd=testComplete&sessionId=1176963830527
[INFO] Killing Firefox...
[INFO] Got result: OK
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.052 sec
...

Finally, we can stop the application server on the post-integration-test phase.

[INFO] [cargo:stop {execution: stop-container}]
[INFO] [talledLocalContainer] JBoss 4.0.5 is stopping...
[INFO] [talledLocalContainer] Shutdown message has been posted to the server.
[INFO] [talledLocalContainer] Server shutdown may take a while - check logfiles for completion
[INFO] [talledLocalContainer] Shutdown complete
[INFO] [talledLocalContainer] Halting VM
[INFO] [talledLocalContainer] JBoss 4.0.5 is stopped

Passing all tests, the EAR is installed as normal. Had it failed, well, then it would be a good thing we did not install a broken artifact!

Tips & Tricks

Use Profiles for Dev, Test, Prod

A useful tactic for enterprise environments is to create a profile per server type. There are a few ways to address this. The most straightforward approach is to create a profile in the POM with tweaks for the various environments. For example, an EJB project may have a persistance.xml file which needs to flex for different environments - for example, with a different JDBC url. Like we did with the my-selenium-test.properties file above, filter by resource location - replace the JDBC string in the XMl file with a descriptive property, for example \${ejb.jdbc.url}. Each profile can contain a different string.

Moreover, it might behoov you to deploy these projects under different names to tell them apart. Here is where classifiers come in handy. You can now generate and deploy different EJB projects for different servers.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ejb-plugin</artifactId>
        <configuration>
          <ejbVersion>3.0</ejbVersion>
          <classifier>${server.type}</classifier>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <profiles>
    <profile>
      <id>prod</id>
      <properties>
        <ejb.jdbc.url>jdbc:odbc:prod;UID=prod;PWD=p4ssw0rd</ejb.jdbc.url>
        <server.type>prod</server.type>
      </properties>
    </profile>
    <profile>
      <id>test</id>
      <properties>
        <ejb.jdbc.url>jdbc:odbc:test;UID=test;PWD=p4ssw0rd</ejb.jdbc.url>
        <server.type>test</server.type>
      </properties>
    </profile>
    <profile>
      <id>dev</id>
      <properties>
        <ejb.jdbc.url>jdbc:derby:dev</ejb.jdbc.url>
        <server.type>dev</server.type>
      </properties>
    </profile>
  </profiles>
<project>

This works for small or finite numbers of server. If your demands require more flexibility, then externalize the profiles into a profiles.xml file. This is a file that sits in the basedir, that has a top profiles element on cotaining any number of profile child elements.

Summary

An important peice not to overlook is the easy at which a Java EE project managed by Maven can switch entire execution environments by the mere configuration of a few plugin. In the examples in this chapter we creates an deployed a Geronimo project effortlessly on the Jetty 6 runtime - and with a few deft configurations, created a profile capable of deploying the project to JBoss. Maven cannot take complete credit, of course, since it is mostly a testement to the Java EE framework. But never before has swapping framework implementations and servers been so conceptually simple.

There are several ways to do the same thing in Maven, as the Geronimo deployment example illustrates: either use the Apache project' plugin, or utilize Cargo instead. The mechanism is up to you but do not forget the important point: from your user's point of view, the trigger is the same... "mvn install".


Previous: Chapter 15