Skip to main content

skip to main content

developerWorks  >  Java technology  >

Project management: Maven makes it easy

Add project management features to your next Java build

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Intermediate

Charles Chan (charlesc@ibiblio.org), Senior Software Developer, Finetix LLC

08 Apr 2003

Even though Ant acts as the de facto standard for building Java programs, in many ways the tool falls short for project management tasks. In contrast, Maven, a high-level project management tool from the Apache Jakarta project, provides everything that Ant offers plus more. Java developer Charles Chan introduces Maven's features and walks you through a complete Maven project setup.

Editor's note: Apache Maven has been updated to version 2. Refer to the "Introduction to Apache Maven 2" tutorial for details on getting started with this newer version.

Nowadays, most developers recognize Ant as the standard build tool for Java programming projects. Unfortunately, Ant's project management facilities, as a replacement for make, fall short of most developers' needs. By examining an Ant build file, it's difficult to discover a project's dependencies and other meta information such as developers/owners, version, or site home page.

Maven, in addition to featuring program-build capabilities, offers high-level project management tools absent in Ant. Because Maven's default build rules are highly reusable, you can often build simple projects with two or three Maven build-script lines as compared to a dozen lines for Ant. Many Apache Jakarta projects, in fact, now use Maven, and the adoption rate for corporate projects continues to increase because of Maven's project-oriented approach.

Maven versus Ant

So, how does Maven differ from Ant? Before I answer that question, let me emphasize that Maven and Ant target two different aspects of the build problem. Ant provides cross-platform build tasks for Java technology development projects. Maven, for its part, describes a project's high-level aspects and borrows most build tasks from Ant. Therefore, since Maven and Ant represent very different beasts, I will explain only the differences between the tools' equivalent components, as seen in Table 1.

Table 1. Maven versus Ant

Maven Ant
Standard build filesproject.xml, maven.xmlbuild.xml
Properties process order
  1. ${maven.home}/bin/driver.properties
  2. ${project.home}/project.properties
  3. ${project.home}/build.properties
  4. ${user.home}/build.properties
  5. System properties defined by -D command line option
The last definition wins.
  1. System properties defined by -D command line option
  2. Properties loaded by the <property> task
The first definition wins.
Build rulesBuild rules are more dynamic (similar to a programming language); they are executable XML based on Jelly.Build rules are more or less static (unless you use the <script> task. (See Resources for a tutorial.)
Extension languagePlugins are written in Jelly (XML).Plugins are written in the Java language.
Build rules extensibilityBuild goals are extensible by defining <preGoal> and <postGoal>.Build rules are not easily extensible; one can simulate the <preGoal> and <postGoal> effects by using the <script> task.



Back to top


Maven's major components

Now that you have a feel for the differences between Maven and Ant, let's examine Maven's major components, which are shown in Figure 1.


Figure 1. Maven's major components
Maven's major components

The Project Object Model

A Project Object Model (POM) describes a project's various aspects. Although no inherent limitation exists on the POM's physical representation, Maven developers typically use an XML project file (project.xml). The XML file format is defined by the XML schema -- maven-project.xsd -- in the Maven installation directory.

In general, three main sections comprise a project.xml file:

  • The project management section includes such information as the project's organization, the developer list, source code location, and the bug tracking-system URL.

  • The project dependency section includes information about a project's dependencies. The current Maven implementation (1.0 beta 8) supports only JAR file dependency.

  • The project build and reports section contains project build information such as a source code directory, a unit test-case directory, and the reports to generate in the build.

Listing 1 offers an annotated look at a sample project.xml file. Because many elements in a project.xml file are optional, you can gradually employ different Maven features as your understanding increases. Note: In the code below, optional elements are indicated with "OPTIONAL."

The main document contains your project's unique identifiers and group ID. A group ID proves especially useful when your project includes multiple subprojects. All subprojects should share the same group ID but each should have a distinct <id>.


Listing 1. Main project.xml skeleton
                
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- A project file's root element -->
<project>
  <!-- The POM version. This tag is currently unused. -->
  <pomVersion>3</pomVersion>

  <!-- A project group id. If present, the id serves as the project's
        directory name in the repository -->
  <groupId>crayola-group</groupId>

  <!-- A unique project identifier. The project identifier and its
        version number often generate file/directory names during the
        build. For example, a project JAR file follows the
        <id>-<version> naming convention. -->
  <id>crayola</id>

  <!-- A short name for the project -->
  <name>Crayola Professional</name>

  <!-- The project version number. Maven does not enforce a particular
        version numbering scheme. -->
  <currentVersion>0.0.1</currentVersion>

  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project management section                                -->
  <!-- 
---------------------------------------------------------------- -->
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project dependency section                                -->
  <!-- 
---------------------------------------------------------------- -->
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project build and reports section                         -->
  <!-- 
---------------------------------------------------------------- -->
  ...
</project>

The project management section, shown in Listing 2, includes mostly optional items. In this section you specify the list of developers (with proper ids), especially when you want the Change Log report and Development Activity report.


Listing 2. Project management section
                
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project management section                                -->
  <!-- 
---------------------------------------------------------------- -->

  <!-- Details of the organization holding the project. Only the name
        is required. -->
  <organization>
    <name>Markers Inc.</name>
    <url>http://w3.markers.com/</url>
    
<logo>http://w3.markers.com/logo/company-logo.gif</logo>
  </organization>

  <!-- (OPTIONAL) Year of inception -->
  <inceptionYear>2003</inceptionYear>

  <!-- (OPTIONAL) Project main package -->
  <package>com.markers.crayola.*</package>

  <!-- (OPTIONAL) Project logo picture (URL) -->
  <logo>http://w3.markers.com/logo/crayola.gif</logo>

  <!-- (OPTIONAL) GUMP repository ID. Useful only if you use GUMP. -->
  <gumpRepositoryId>crayola</gumpRepositoryId>

  <!-- (OPTIONAL) Project description -->
  <description>...</description>

  <!-- (OPTIONAL) Short project description -->
  <shortDescription>...</shortDescription>

  <!-- (OPTIONAL) Project site URL -->
  <url>http://w3.markers.com/crayola</url>

  <!-- (OPTIONAL) Issue-tracking system URL -->
  
<issueTrackingUrl>http://w3.markers.com/jira/crayola</issueTrackingUrl>

  <!-- (OPTIONAL) Project site address. -->
  <siteAddress>w3.markers.com</siteAddress>

  <!-- (OPTIONAL) Project-site deploy directory (physical location) -->
  <siteDirectory>/www/crayola/site/</siteDirectory>

  <!-- (OPTIONAL) Project distribution directory (physical location) -->
  
<distributionDirectory>/www/crayola/builds/</distributionDirectory>

  <!-- (OPTIONAL) Project source-repository information -->
  <repository>
    
<connection>
scm:cvs:pserver:anoncvs@cvs.markers.com:/home/cvspublic:crayola
</connection>
    <url>http://cvs.markers.com/viewcvs/crayola/</url>
  </repository>

  <!-- (OPTIONAL) Mailing list information -->
  <mailingLists>
    <mailingList>
      <name>Dev List</name>
      
<subscribe>dev-subscribe@crayola.markers.com</subscribe>
      
<unsubscribe>dev-unsubscribe@crayola.markers.com</unsubscribe>
    </mailingList>
    ...
  </mailingLists>

  <!-- Developers involved in this project -->
  <developers>
    <developer>
      <name>John Smith</name>
      <id>jsmith</id>
      <email>jsmith@markers.com</email>
    </developer>
    ...
  </developers>

The information in Listing 3, together with a central artifact repository, will eliminate several common build problems, including a misconfigured CLASSPATH or a dependency-version mismatch.


Listing 3. Project dependency section
                

  <!-- 
---------------------------------------------------------------- -->
  <!-- Project dependency section                                -->
  <!-- 
---------------------------------------------------------------- -->

  <dependencies>

    <!-- This project depends on the JAR file "commons-beanutils-1.5.jar"
          in the Maven repository's commons-beanutils/jars subdirectory
          (more about repository later). -->
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.5</version>
    </dependency>

    <!-- This project depends on the JAR file "commons-lib-2.1.jar" in
          the Maven repository's markers/jars subdirectory. -->
    <dependency>
      <groupId>markers</groupId>
      <artifactId>commons-lib</artifactId>
      <version>2.1</version>
    </dependency>

  </dependencies>

The project build and reports section, shown in Listing 4, contains important build and reports information used to configure some Maven plugins. For example, you can configure Maven to include or exclude certain reports when a site documentation generates.


Listing 4. Project build section
                
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project build and reports section                         -->
  <!-- 
---------------------------------------------------------------- -->

  <build>

    <!-- (OPTIONAL) Build notification email address. -->
    <nagEmailAddress>jsmith@markers.com</nagEmailAddress>

    <!-- (OPTIONAL) Defines where the Java source resides. -->
    <sourceDirectory>src/java</sourceDirectory>

    <!-- (OPTIONAL) Defines where the Java source for unit test-cases
          resides. -->
    
<unitTestSourceDirectory>test/java</unitTestSourceDirectory>

    <!-- (OPTIONAL) Unit test-case file pattern. -->
    <unitTest>
      <includes>
        <include>**/*Test.java</include>
      </includes>
    </unitTest>

    <!-- (OPTIONAL) Resources packaged inside the JAR file. -->
    <resources/>

    <!-- (OPTIONAL) The reports tag lets you select which reports you
          want generated for your site. In this case, only the checkstyle
          report will generate. -->
  </build>

  <reports>
    <report>
      maven-checkstyle-plugin
    </report>
  </reports>

A project depends on libraries to fulfill its functionalities. For example, your project may depend on log4j for logging and Xalan for XSLT transformation. For a J2EE project, the Web component might depend on the EJB component to perform business operations. Maven lets you express different dependencies in its POM. You can describe each dependency in the project.xml file with the tags shown in Table 2.

Table 2. The project dependency section

groupId Tells Maven which subdirectory within the repository contains the dependency's files.
artifactId Tells Maven the artifact's unique identifier.
version Represents the dependency's version number.
jar (OPTIONAL) Represents the dependency's JAR file. In most cases, the JAR file's name can be constructed from the dependency's <artifactId> and <version>.
type (OPTIONAL) The dependency's type; for example, jar, distribution, and so on. The default is jar.
url (OPTIONAL) The dependency project's URL, which proves especially useful when the dependency is a third-party library found on the Internet.

Repository

The repository represents another major Maven component. In a multiproject Java-based site, a central repository of third-party libraries often ensures consistency among projects. Maven standardizes the repository's structure and supports a remote repository hosted anywhere on the Internet or the intranet. Listing 5 shows a repository's general structure.


Listing 5. The repository
                
repository
|-- ant                   <-- project group ID -->
|   `-- jars              <-- artifact type, followed by 's', 
|                         <-- e.g. jars, wars, ears -->
|       `-- ant-1.5.1.jar <-- actual artifact -->
...

To create a remote repository, you simply deploy the repository directory in a Web site. Maven recommends using a remote repository so you can centrally maintain it, and you'll realize the maximum possibility of sharing resources among projects. To avoid downloading files on every build, Maven automatically caches the necessary dependencies in a local repository when they first download. Maven uses the properties shown in Table 3 for remote and local repositories.

Table 3. Properties for remote and local repositories

maven.repo.remote Specifies to the remote repositories a comma-separated list of URLs; http://www.ibiblio.org/maven is used by default.
maven.proxy.host, maven.proxy.port, maven.proxy.username, maven.proxy.password If you are behind a firewall and require proxy authentication to access the Internet, these settings will come in handy.
maven.repo.local Specifies where downloaded dependencies are cached, by default in ${MAVEN_HOME}/repository. In UNIX environments, to share the repository directory with multiple teams, you can create a special group for developers and give it read/write access to the repository directory.

Ant tasks in Maven
A Maven goal can contain any valid Ant task in its definition, which will help you quickly learn Maven and protect your Ant investments.

Goals

Goals in Maven resemble target in Ant. They both contain tasks that execute when the goal (or target) is attained. To attain a specific goal in command line, type maven <goal>.

To list all defined goals, use maven -g. Table 4 lists frequently used goals.

Table 4. Frequently used goals

java:compile Compile all Java sources.
jar Create a JAR file of the compiled sources.
jar:install Publish the created JAR file to the local repository, which lets other projects see the JAR file.
site Create project site documentation. The default site documentation contains useful information about the project such as package/class dependency, coding style conformance, source code cross-references, unit testing results, or Javadocs. The list of reports to generate is customizable.
site:deploy Deploy the generated site documentation.

Maven's goals are extensible and reusable. With that in mind, before you write your own goals, check the list of Maven plugins on the Maven site or ${MAVEN_HOME}/plugins). Another good resource for free Maven plugins is the Maven plugins project at SourceForge. (Links for all these items are available in Resources).

If you still cannot find a suitable goal for your purpose, Maven gives you two options:

  • Write a <preGoal> or <postGoal> to extend the standard goals
  • Write your own goals

For both options, create a special file called maven.xml in the project directory. Listing 6 shows a skeleton maven.xml.


Listing 6. A skeleton maven.xml
                
      <?xml version="1.0" encoding="ISO-8859-1"?>
      <project xmlns:j="jelly:core">
        ...
        <goal name=...>
          ... build rules, e.g.
          <mkdir dir="${test.result.dir}"/>
          <echo>Executing JUnit tests</echo>
          ...
        </goal>
        ...
        <preGoal name=...>
          ...
        </preGoal>

        <postGoal name=...>
          ...
        </postGoal>

      </project>

Developers familiar with Ant will find that Maven's goals (and similarly preGoal and postGoal) can contain any valid Ant task in its definition, which will help you learn Maven quickly and protect your Ant investments. To add dynamism to the Ant tasks, Maven also uses the Jelly scripting language. "Basic Jelly programming" introduces the Jelly scripting language using a sample maven.xml file.

Writing <preGoal> and <postGoal>
Ant's <target> resembles a makefile rule in that the precondition and the postcondition are fixed after the rule is defined. That makes reusing build rules across multiple projects more difficult. For example, a compile target in one project may depend on XDoclet to generate source files, while another compile target may not include any prerequisite. To overcome this limitation, Maven provides two special tags: <preGoal> and <postGoal>. From the tag names, you can see that a preGoal defines the build rules to be executed before the specified goal. A postGoal, on the other hand, defines the build rules to be executed after the specified goal is reached. For example, the preGoal in Listing 7 instructs Maven to generate source files using XDoclet before compiling the source code.


Listing 7. A sample preGoal section
                
        <preGoal name="java:compile">
          <attainGoal name="xdoclet:ejbdoclet"/>
        </preGoal>

Maven also provides an <attainGoal> tag similar to Ant's <antcall> tag for cases where attaining a goal directly proves necessary (as is the case above).

Writing your own goals
If your goals are project-specific, you can define your own goals in the maven.xml file. Such self-defined goals override other goals with the same names. If your project includes subprojects, they also inherit these goals.

Writing a plugin
To share your goals among projects, package them as plugins in the Maven installation plugins directory (${MAVEN_HOME}/plugins). A typical Maven plugin contains a project.xml and a plugin.jelly file. The project.xml file describes the plugin's POM; plugin.jelly resembles maven.xml and contains the goals exposed by that plugin. A plugin can have its own resources and dependencies. The predefined variable ${plugin.dir} lets users refer to resources in the plugin directory. For example, in the plugin structure shown in Listing 8, ${plugin.dir}/dtd/web-app_2_3.dtd, can access web-app_2_3.dtd inside plugin.jelly.


Listing 8. A sample plugin structure
                
    ejbjar-plugin-1.0
    |-- dtd
    |   |-- application_1_3.dtd
    |   |-- ejb-jar_2_0.dtd
    |   |-- web-app_2_3.dtd
    |-- plugin.jelly
    `-- project.xml



Back to top


Installing Maven

The recently released Maven 1.0-beta-8 is almost feature-complete for 1.0. Because the Maven development community fixes bugs daily, if you encounter any show-stoppers, don't hesitate to grab the latest Maven version from CVS (Concurrent Version System) and build it yourself (see Resources for instructions). After you've downloaded the latest Maven source code, build Maven by invoking:

      ant -f build-bootstrap.xml

      (set MAVEN_HOME to where you want Maven to reside and
      use Ant 1.5.1 to perform the build)

If you operate behind a firewall, set the following properties properly: maven.proxy.host, maven.proxy.port, maven.proxy.username, maven.proxy.password. By default, the Maven repository resides in ${MAVEN_HOME}/repository; you can change its location by setting the maven.repo.local property to the new location.

Sample project files
See the Maven project files used in this sample J2EE project.


Back to top


Sample J2EE project

Armed with the knowledge you've gained thus far, we're ready to put Maven into use. This section describes how to set up a sample J2EE project in Maven.

Project directory layout

Before we get into the specifics, let me explain the project's directory layout. Although not a requirement, a consistent cross-project directory layout can prove incredibly useful because developers familiar with one project can easily navigate through other projects. More importantly, a consistent directory layout lets you write generic build rules.

Maven's directory layout guideline (see Resources) works well with most projects. For demonstration purposes, I use a slightly different layout, as shown in Listing 9.


Listing 9. The sample project directory layout
                
 project
 |
 |-- LICENSE.txt
 |-- project.properties
 |-- maven.xml
 |-- project.xml
 |-- src
 |   `-- java
 |       `-- com/....
 |   `-- conf
 |       `-- Configuration files for staging environments.
 |-- test
 |   `-- java
 |       `-- com/....
 |   `-- conf
 |       `-- Configuration files for unit testing environments.
 `-- xdocs
     `-- index.xml

A J2EE project typically produces WAR files, EJB JAR files, and EAR files. Because each includes its own dependencies and source files, you should build them as separate projects. You'd typically structure this project/subproject relationship by storing the subproject as a subdirectory of the master project. Our layout is shown in Listing 10.


Listing 10. The high-level directory layout for a J2EE project in Maven
                
j2ee-project
|
|-- project.xml       - Produces the EAR file
|
|-- util-subproject
|   |
|   `-- project.xml   - Produces the Utility JAR file
|
|-- ejb-subproject
|   |
|   `-- project.xml   - Produces the EJB JAR file
|
`-- web-subproject
    |
    `-- project.xml   - Produces the WAR file

Project inheritance

Project inheritance lets the POM inherit from a master POM in a way similar to object inheritance -- a feature especially important here because of the minimal differences among these projects (mainly in dependencies). The project management section can be centrally maintained in the master project.xml. To use project inheritance, use the <extend> tag in project.xml (see Listing 2 in "Sample project files").

Goals for the sample projects

Now that you've defined the POMs, you can write goals for them. Because these goals use properties defined in the POM, you should first understand the project.xml files in "Sample project files" before proceeding.

Utility subproject

The Utility subproject produces a JAR file containing the classes in the source directory -- a requirement fulfilled by the default jar:jar goal, so you don't need a custom goal here.

Because the Web subproject and the EJB subproject both depend on the Utility subproject, you should invoke the jar:install goal to deploy the Utility subproject JAR file to the local repository before building the Web subproject and the EJB subproject. That way, the WAR subproject and the EJB subproject can resolve the dependency properly.

Web subproject

The Web subproject produces a WAR file containing classes in the source directory, JSP files in the jsp directory, and the web.xml file in the conf directory. The default war:war goal has a more simplistic view of a project's directory layout. To reuse that goal, customize its behavior as follows:

  1. In the project's project.properties file, set the properties maven.war.src and maven.war.webxml to ${maven.build.dir}/webapp and ${maven.src.dir}/conf/web.xml, respectively. These tell war:war where to find the Web sources (JSP pages, HTML static pages, images, and so on) and the web.xml file.

  2. Define a preGoal that copies all JSP files into the ${maven.build.dir}/webapp directory. The following maven.xml achieves the effect:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <project>
    
      <preGoal name="war:init">
        <copy todir="${maven.build.dir}/webapp">
          <fileset dir="${maven.src.dir}/jsp" include="*.jsp"/>
        </copy>
      </preGoal>
    
    </project>
    

When you invoke the war:war goal, notice that both the Utility JAR file and the commons-beanutils JAR file are packaged into the WAR file. Maven knows which file to include in the WAR file by looking at the war.bundle.jar property in the project.xml file's dependency section.

EJB subproject

Packaging an EJB JAR file resembles packaging a JAR file. If your project settings do not match the default ejb goal, you can use the technique described in the "Web subproject" section above. In this particular case, copy the ejb-jar.xml from the conf directory to the ${maven.build.dir}/ejb/META-INF directory and set the maven.ejb.src property to ${maven.build.dir}/ejb.

To add a dependency JAR file into the EJB JAR's manifest classpath, use the ejb.manifest.classpath property in the dependency section.

Master (EAR) project

After you have successfully compiled and deployed the subprojects (using the jar:install, war:install, and ejb:install goals), you can create the final EAR file. The dependency properties ear.bundle.jar, ear.bundle.ejb, and ear.bundle.war tell the ear plugin which files to include in the EAR file. (As of Maven 1.0-beta-8, WAR files are not a supported dependency type, so the EAR plugin cannot package a WAR file properly. The workaround: use a postGoal to update the EAR file manually.)

Reactor: Build subprojects automatically

Building a J2EE project requires significant effort. Repeating the same procedure for each project change proves to be time consuming and error prone. To help alleviate those problems, Maven's reactor feature builds subprojects automatically and in the right order, saving time and reducing mistakes.

Listing 11's maven.xml demonstrates one way to define a reactor.


Listing 11. A sample reactor definition
                
<?xml version="1.0" encoding="ISO-8859-1"?>
<project default="all"
         xmlns:m="jelly:maven">

  <goal name="all">
    <m:reactor basedir="${basedir}"
               includes="*/project.xml"
               goals="install"
               banner="Building"
               ignoreFailures="false"/>
  </goal>
</project>

The reactor first searches project.xml files under the basedir directory, then invokes the install goal. The order of execution depends on the dependencies section in each project. Moreover, you'd typically define the reactor in the master project's maven.xml file. Because goals are inherited in subprojects, take care when choosing the goal's name.



Back to top


Get involved

Although Maven is a feature-rich product, it is still in its beta stage. Bugs, therefore, can crop up anywhere. Do not panic. The best way to find the answer to a bug is to search Maven's mailing list archive (see Resources) for relevant posts. If nothing comes up, try posting your questions to the mailing list for advice. People on the mailing list are often willing to help.

To formally report a bug, access the Maven project's issue tracking system (see Resources).

Once you become familiar with Maven, you may find most of your answers in the plugin's implementation. When you reach that expertise level, and you believe Maven is the way to go, please help Maven grow by submitting patches to the community.



Back to top


Conclusion

As today's projects become more complex, we need tools that let us express and handle these complexities. Maven combines a project object model with a powerful XML scripting language to provide such a tool. In this article, you have discovered how to define the POM and how to build your projects using Maven's goal mechanism. We have also examined different ways to customize build behavior using Jelly. Finally, the sample J2EE project puts the concepts into practical use. I hope you will download Maven to give it a spin.

The author thanks Jason van Zyl for his review of this article.



Resources

  • For more information on Maven and its various plugins, go to the Maven Web site. You'll find many useful resources here.

  • The Maven plugins project at Sourceforge.net offers free Maven plugins.

  • Maven's API documentation will prove useful when you write build rules using Jelly.

  • The Ant Web site provides good references to Ant tasks. You can use most Ant tasks in Maven.

  • The tutorial "Using JavaScript with Ant" shows you how to write dynamic Ant tasks with JavaScript.

  • Krysalis Centipede is another build system based on Ant.

  • In "Extending Ant to support interactive builds" (developerWorks, November 2001), Anthony Young-Garner shows you how to incorporate interactive support in your builds to provide a smoother and more flexible experience for your end users.

  • "Incremental development with Ant and JUnit" (developerWorks, November 2000) by Malcolm Davis offers advice to improve your code with unit testing.

  • GUMP is a tool for continuous integration of inter-dependent projects.

  • You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.


About the author

Charles Chan is a consultant at Finetix LLC. Charles's interests include distributed systems, high-performance computing, internationalization, and software design patterns. In his spare time, he contributes to the open source community. You can contact Charles at charlesc@ibiblio.org.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top