JaVa
   

Case Study: Managing Our Pet Store Project with Maven

Now let's look at how to convert our project using Maven. First we examine the new directory structure and its relevant files.

The New Directory Structure

We have decided to create a new structure instead of trying to change our existing structure to reflect how Maven works. We did this for a variety of reasons, which we discuss here. First, the test directory contains the unit tests for our app. However, the only portion tested is the model, not the Web app. Second, this app has two distinct deliverables: a JAR file for the models, and a WAR file for the Web app. Now, we could have put the model and model tests in the root directory, but one of the things I want to show you is how to modularize our app, so I chose to split the tests into separate directories as well. What does that leave us in the root directory then?

project.xml project.properties (if needed)
maven.xml (if needed)
model web target


Next let's look at the model directory. Essentially this is the same directory as before; however, we have now added in a test directory, where we have moved the tests. Since we are no longer relying on using Test as a package, we can do a simple refactor and change the package to xptoolkit.petstore.model (or dbmodel). So, what does the model directory look like now?

project.xml project.properties maven.xml src
 java
 test


The project.xml file extends the existing root project.xml through this definition:

<extend>${basedir}/../project.xml</extend>


This tells Maven to grab the dependencies from the parent before the child, allowing us to store dependencies specific to this subproject without polluting the parent or other children with that dependency. The Web subproject is similar in layout to the model subproject, with a few changes:

project.xml project.properties maven.xml
src
 java
 webapp


As you will notice there is a new directory here called webapp. This directory stores all of your information relating to the deployment of a Web app. Underneath it you will place your JSP files, WEB-INF directory, web.xml file, and basically anything that needs to go in the WAR or EAR file, except for JAR files, which are determined elsewhere, and class files, which are built by Maven and placed inside at artifact creation time. We show you how to define these a little later on.

Defining Our Dependencies

There are essentially two dependencies that we need to worry about: the hsqldb JAR file and the Servlet API JAR file. Both of these are available on the Maven repository at Ibiblio (respectively at http:// www.ibiblio.org/maven/hsqldb/ and http://www.ibiblio.org/maven/servletapi/). In addition, we have an interproject dependency between the model and the Web subprojects, so we need to define that dependency in the project descriptor for the Web subproject. The main project descriptor will look like this:

<?xml version="1.0"?>
<project>
 <pomVersion>3</pomVersion>
 <name>XPToolkit Petstore</name>
 <id>petstore</id>
 <groupId>xptoolkit</groupId>
 <currentVersion>1.0</currentVersion>
 <organization>
 <name>Wrox</name>
 <url>http://wrox.com/</url>
 </organization>
 <inceptionYear>2004</inceptionYear>
 <package>xptoolkit.petstore</package>
 <shortDescription>XPToolkit Petstore demo app</shortDescription>
 <description>
 This is a simple Petstore app to demonstrate the techniques in the Java Tools for Extreme Programming 2nd version.
 </description>
 <url>http://www.wrox.com/javatools/</url>
 <issueTrackingUrl/>
 <siteAddress/>
 <siteDirectory/>
 <distributionSite/>
 <distributionDirectory/>
 <repository/>
 <versions/>
 <mailingLists/>
 <developers>
 <developer>
 <name>Rick Hightower</name>
 <id>rick</id>
 <email>rick@bugmenot.com</email>
 <organization>Arc-Mind</organization>
 </developer>
 <developer>
 <name>Warner Onstine</name>
 <id>warner</id>
 <email>warner@bugmenot.com</email>
 <organization>Sandcast Software</organization>
 </developer>
 </developers>
 <contributors/>
 <licenses/>
 <dependencies>
 <dependency>
 <groupId>hsqldb</groupId>
 <artifactId>hsqldb</artifactId>
 <version>1.7.2-rc1</version>
 <properties>
 <war.bundle>true</war.bundle>
 </properties>
 </dependency>
 </dependencies>
 <build/>
 <reports/>
</project>


As you can see, everything is rather straightforward. Since the main project does not have any source to compile (remember, we moved that to the model and Web subprojects), we do not have to define anything in the build section. We currently aren't going to define any specific reports other than the defaults, so we don't need anything in that element either.

Now, when we run java:compile on one of our subprojects, it will automatically check the dependencies here first, download them if necessary, and add them to the class path. We have also defined something new here called war.bundle, which will be explained in just a second when we look at the Web subproject.

The New Project Descriptors and How They Work

First we look at the model subproject's project descriptor:

<project>
<extend>${basedir}/../project.xml</extend>
 <pomVersion>3</pomVersion>
 <name>Petstore-Model</name>
 <id>petstore-model</id>
 <groupId>xptoolkit</groupId>
 <currentVersion>2.0</currentVersion>
 <build>
 <sourceDirectory>src/java</sourceDirectory>
 <unitTestSourceDirectory>src/test</unitTestSourceDirectory>
 <!-- Unit test cases -->
 <unitTest>
 <includes>
 <include>**/*Test.java</include>
 </includes>
 </unitTest>
 </build>
</project>


Note that this subproject has id and name elements that are different from the parent. This is to distinguish its artifacts from the parent and other children. In this case it is petstore-model, which denotes that it is the model for the pet store project. We will do something similar to the Web subproject. Also notice that this has a test directory specified; we will not have one specified for the Web subproject since we don't have any unit tests for that one. The Web subproject is similar but with some new elements in the dependencies we will be discussing shortly:

<project>
 <extend>${basedir}/../project.xml</extend>
 <pomVersion>3</pomVersion>
 <name>Petstore-Web</name>
 <id>petstore-web</id>
 <groupId>xptoolkit</groupId>
 <currentVersion>2.0</currentVersion>
 <dependencies>
 <dependency>
 <groupId>xptoolkit</groupId>
 <artifactId>petstore-model</artifactId>
 <version>2.0</version>
 <properties>
 <eclipse.dependency>true</eclipse.dependency>
 <war.bundle>true</war.bundle>
 </properties>
 </dependency>
 <!-- non-war dep -->
 <dependency>
 <groupId>servletapi</groupId>
 <artifactId>servletapi</artifactId>
 <version>2.3</version>
 <type>jar</type>
 </dependency>
 </dependencies>
 <build>
 <sourceDirectory>src/java</sourceDirectory>
 </build>
</project>


There is quite a bit more going on here. First let's tackle the petstore-model dependency. This basically tells Maven that you require that this JAR be built and in the local repository before you start the build. And there is something new here as well: a properties element. This element is used for placing plug-in properties in the project.xml file. In our case we have two plug-ins: the WAR plug-in and the Eclipse plug-in. The war.bundle property tells the WAR plug-in that we want this JAR file to be included in the lib directory of our final WAR file. The eclipse.dependency property tells the Eclipse plug-in to create an interproject dependency in Eclipse to make coding easier inside the Eclipse IDE.

Using the Eclipse Plug-in

If you use Eclipse as your IDE, then this plug-in will be of great use to you. It maintains and updates your .project and .classpath files for you. So, if you add a new project dependency, or remove one, then by running the plug-in on the appropriate project you can remove that dependency from the .project and .classpath files—and then refresh your project in Eclipse and you're off and running.

So, how do you use this plug-in? First, make sure you have specified all of your dependencies in your project.xml file. Then you need to set a custom variable in Eclipse to point to your Maven repository. Do this in Eclipse by selecting the Window menu and choosing Preferences. In the resulting dialog box, select the Java node and then Classpath Variables. Create a new variable named MAVEN_REPO that points to your local Maven repository, which will typically be at /user_home/.maven/repository. Once you have that set, you can either add this as a new project to Eclipse (if it isn't already) or refresh the project by right-clicking on the project name and choosing Refresh.

Building the Respective Pieces

Now that we have everything defined, we are ready to build our project. There are a couple of ways to do this: First, we could build each project separately by changing to the subproject directory and running jar:install, or running war:war on the Web subproject. In order for this to work properly, we need to run the jar:install first, and then switch to the Web directory and run the war:war goal. Or we could let Maven handle it for us. To tell Maven that we want to build both subprojects' artifacts, we have to let Maven know what artifacts those are by using the respective project.properties of each subproject: First, for the model's project.properties file:

maven.multiproject.type=jar


This tells Maven that for the model subproject we want it to build the JAR as the artifact. Now, let's take a look at the Web subproject's project.properties file:

maven.multiproject.type=war


This tells Maven that for the Web subproject we want it to build a WAR file. Depending on the artifact, Maven will automatically choose the right goal to run on each subproject. So, for the model it will run jar:jar, and for Web it will run war:war. Now that we have those defined, we can simply type

maven multiproject:install


This will run the respective {artifact}:install goal, jar:install for model, and war:install for the Web subproject. There are more options that we discussed earlier for running specific goals on subprojects. I invite you to review the Maven multiproject section and try them out yourself.

Adding In Custom Deployment Options Using Jelly

Say that every time we built the Web subproject we wanted to deploy it to a container. What steps would we need to take to make that happen? The first step is determining when in the build process we want that to happen—ideally, right after the war:war goal. So, our code might look something like this:

 <postGoal name="war:war">
 <copy file="${maven.war.final.name}" todir="my/tomcat/directory/webapps/" />
</postGoal>


This code tells Maven that, after we WAR a file, we want to copy that file (identified by the group and project IDs) to our local Tomcat webapps directory. This should work great, as long as Tomcat is not running; otherwise we will receive an error that Maven couldn't overwrite the file. Not good. So, what are some of our other options? We could look at calling Tomcat through Ant—there are several scripts out there that do this already for starting and stopping Tomcat. Basically anything that you can already do in Ant, you should be able to do in Maven with little or no trouble. Luckily for us, someone has already done this and created a Tomcat plug-in. You can download this plug-in from http://www.codeczar.com/products/tomcat/ and install it in your MAVEN_HOME/plugins directory. The next time you run Maven it should pick up on the new plug-in and automatically extract and install it for you. In our original Ant build we copied over the database to the container, and we need to do the same for the new Maven-handled code. The easiest way for us to do this is to include the database code as part of the Web app, so copy it into the final build for the Web app with something like this:

<preGoal name="war:war">
 <copy todir="${maven.war.build.dir}/WEB-INF/data">
 <fileset dir="../src/data/">
 <include name="**/petstore.*"/>
 </fileset>
 </copy>
</preGoal>


This code copies over all of the data files into the WEB-INF directory underneath the data directory. Of course, in a real app we would not want this—it would be better to have a running database to hit against rather than an in-memory one.

Deploying the Web app to Tomcat or Other Containers

As we have just seen, someone has already written a plug-in for Tomcat deployment. By examining this plug-in we might also be able to write one for deploying to something like Resin. But to keep things simple we have specified only two properties, one for Resin and the other for Tomcat. To ensure that the files are copied over correctly, make sure that your instance of Resin or Tomcat is properly shut down. Let's specify two new goals, one for Tomcat and one for Resin, that you can use:

<goal name="deploy-tomcat">
 <attainGoal name="war:war"/>
 <copy file="${maven.war.final.name}" todir="${tomcat.deploy.dir}/webapps" />
</goal>
<goal name="deploy-resin">
 <attainGoal name="war:war"/>
 <copy file="${maven.war.final.name}" todir="${resin.deploy.dir}/webapps" />
</goal>


To execute either of these tasks, all you need to do is type maven deploy-tomcat or maven deploy-resin, but make sure that you have set the deploy directories in the master project.properties file first; otherwise Maven will complain that it can't find the files.

Building the Web Site

The last item I want to show you is how to build the Web site, which includes all of the reports. Basically to build the Web site for our project you go to the top-level of the project and type

maven multiproject:site


This code generates our site as shown in the next three figures, with some of the built-in reports.

Java Click To expand Java Click To expand Java Click To expand
JaVa
   
Comments