Improving Java Software Builds with Maven - Part 1

by Robert Shanahan

Maven provides a comprehensive approach to managing software projects. From compilation, to distribution, to documentation, to team collaboration, Maven provides the necessary abstractions that encourage reuse and take much of the work out of project builds.

For more information about Maven, visit the Maven Web site at http://maven.apache.org/.

In this article we'll explore configuring and running Maven.

Internal Repository

Maven depends on repositories for storing and managing Java build artifacts. The Maven community provides public repositories for common libraries, such as log4j, junit, etc. The Maven runtime will cache these artifacts to a local repository when you declare them as dependencies in a project and you can install/deploy build artifacts to your own local repository, but others cannot access your local repository. Hence the need for an internal repository, which is similar to a public repository—both use webdav—except that you can publish to it!

Currently there are not many production quality repository apps, which made the selection easier. For me it came down to two choices: Archiva and Artifactory. Artifactory won—it just works. Artifactory listens on port 8081 and can be browsed at http://(host-name):8081/artifactory.

Login with admin/password and create a non administrator user account for Maven or Eclipse plugin command line client access to Artifactory; for the sake of configuration examples to follow, the user name repouser and password repopasswd are used.

Add the following xml snippets to your $M2_HOME/conf/settings.xml file to configure your local Maven environment to use Artifactory.
Insert this after <servers> tags.

<server>
 <id>my-repo-releases</id>
 <username>repouser</username>
 <password>repopasswd</password>
</server>
<server>
 <id>my-repo-snapshots</id>
 <username>repouser</username>
 <password>repopasswd</password>
</server>

Insert this after the <profiles> tag.

<profile>
 <id>myprofile</id>
 <repositories>
  <repository>
   <id>central</id>
   <url>http://localhost:8081/artifactory/repo</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </repository>
  <repository>
   <id>snapshots</id>
   <url>http://localhost:8081/artifactory/repo</url>
   <releases>
    <enabled>false</enabled>
   </releases>
  </repository>
 </repositories>
 <pluginRepositories>
  <pluginRepository>
   <id>central</id>
   <url>http://localhost:8081/artifactory/repo</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </pluginRepository>
  <pluginRepository>
   <id>snapshots</id>
   <url>http://localhost:8081/artifactory/repo</url>
   <releases>
    <enabled>false</enabled>
   </releases>
  </pluginRepository>
 </pluginRepositories>
</profile>

And replace commented <activeProfiles> (if exists, otherwise simply add at end of settings.xml).

<activeProfiles>
 <activeProfile>myprofile<activeProfile>
</activeProfiles>

The following (simpler) addition to settings.xml is intended to accomplish the same purpose as the profile/activeProfile combo, but it doesn't work in all versions of Maven (need to test 2.1.0).

<mirrors>
 <mirror>
  <id>maven.mirror.com</id>
  <name>My Maven Proxy</name>
  <url>http://localhost:8081/artifactory/repo</url>
  <mirrorOf>*</mirrorOf>
 </mirror>
</mirrors>

Alternatively, each project's pom.xml file can override the default repo with essentially the same bit of xml. Insert the following into project's pom.xml (within <project> tags):

<repositories>
 <repository>
  <id>central</id>
  <url>http://localhost:8081/artifactory/repo</url>
  <snapshots>
   <enabled>false</enabled>
  </snapshots>
 </repository>
 <repository>
  <id>snapshots</id>
  <url>http://localhost:8081/artifactory/repo</url>
  <releases>
   <enabled>false</enabled>
  </releases>
 </repository>
</repositories>
<pluginRepositories>
 <pluginRepository>
  <id>central</id>
  <url>http://localhost:8081/artifactory/repo</url>
  <snapshots>
   <enabled>false</enabled>
  </snapshots>
 </pluginRepository>
 <pluginRepository>
  <id>snapshots</id>
  <url>http://localhost:8081/artifactory/repo</url>
  <releases>
   <enabled>false</enabled>
  </releases>
 </pluginRepository>
</pluginRepositories>

Command Line Options

The general flow of Maven is: clean (only if necessary), compile, test, package, install, deploy.
clean
Removes any previously compiled classes.
compile
Compiles all the classes.
test
Runs junit or testng tests via SureFire plugin.
Use mvn test or to run isolated tests use mvn test -Dtest=NameOfTestClass.
package
Compiles and creates the appropriate file for the plugin target.
-Jar plugin -> jar file.
-War plugin -> war file.
-Ear plugin -> ear file.
install
Installs the jar file into your local Maven repository.
deploy
Installs artifacts into repository or an application to an app server.

Adding a 3rd Party Jar to the Internal Repository

Some 3rd party jars are not available in the public repos. Resist the temptation to create a lib folder for your project. Maintaining a hybrid approach to dependency management more than defeats the advantage of Maven. Instead, deploy the 3rd party jar to the internal repository.
Download the 3rd party jar. If the jar name does not currently conform to Maven file naming standard, change its name accordingly. For example, if the file name is foo.jar and the version is 1.2.2, then rename the jar to foo-1.2.2.jar. Deploy the jar to the internal repository with the following command line:

C:\data\temp>mvn deploy:deploy-file -DgroupId=foo \
	-DartifactId=bar \
	-Dversion=1.2.2 \
	-Dpackaging=jar \
	-Dfile=foo-1.1.1.jar \
	-DrepositoryId=cim.releases \
	-Durl=http://localhost:8081/artifactory/3rdp-releases@repo

Alternatively, you can upload or import artifacts via the Artifactory user interface.

Deploying a Utility Jar to the Internal Repository

Common utility libraries developed by your development team should be regarded no differently than 3rd party libraries. Versioned utility jars should be declarable as dependencies in pom.xml files just like any 3rd party jar.
The Maven install goal will make a jar available to all projects on a single workstation, but the deploy goal must be used to install it into the internal repository. Add the following to your utility jar's pom.xml.

<distributionManagement>
 <repository>
  <id>my-repo-releases</id>
  <!-- id must match settings.xml: servers/server/id -->
  <name>Releases</name>
  <url>http://localhost:8081/artifactory/libs-releases@repo</url>
 </repository>
 <snapshotRepository>
  <id>my-repo-snapshots</id>
  <!-- id must match settings.xml: servers/server/id -->
  <name>Snapshots</name>
  <url>http://localhost:8081/artifactory/libs-snapshots@repo</url>
 </snapshotRepository>
</distributionManagement>

Run the Maven build with the deploy goal to install the jar into internal repository.

Maven and Eclipse

Maven Plugin for Eclipse

Maven functionality can be integrated into Eclipse via the m2eclipse plugin. You can find more information about the plugin here:
http://m2eclipse.codehaus.org/.
To install and configure the plugin, add the following to your Eclipse update sites:
http://m2eclipse.sonatype.org/update/.

Impedance Mismatch

Beyond simple projects, Eclipse and Maven don't integrate very well. The problem is rooted in the fact that Maven projects are typically organized in a hierarchical manner, whereas Eclipse project structure is flat. In other words, Maven allows project nesting and Eclipse doesn't. There is a workaround, but it requires some manual intervention.

The general approach is outlined here: http://maven.apache.org/plugins/maven-eclipse-plugin/reactor.html.

More specifically, follow these steps to enable Eclipse to handle a nested, multi-module Maven project:

1. Check out the parent module (top level project) into your workspace or create a Maven multi-module project if starting from scratch.
2. If the Eclipse .project, .classpath, etc. files are not checked into Software Configuration Management (SCM), run mvn eclipse:eclipse to create .project, .classpath, etc. files in the sub-modules.
3. Switch to the Java perspective in Eclipse.
4. Select the parent module and hit F5 to refresh.
5. Choose File->Import..., Existing Projects into Workspace, and browse into one of the sub-modules under the parent module.
6. Select the sub-module, make sure Copy projects into workspace is not checked and click Finish.
7. Repeat steps 5 and 6 with the other sub-modules.
The submodules will appear as projects in the Eclipse Package Explorer. Changes made to code in these projects will be reflected in the parent project since projects were not copied.

Bootstrap the Build

You can run the Maven command from within Eclipse but it is usually faster from the command line. For bootstrapping only, disable tests.
mvn install -Dmaven.test.skip=true

What's Next

In part 2 we'll discuss how to streamline dependency management of multi-module projects including JEE projects.
Read Improving Java Software Builds with Maven - Part 2

info@stoutsystems.com
877.663.0877
© Copyright 1995-2022 - STOUT SYSTEMS DEVELOPMENT INC. - All Rights Reserved
envelopephone-handsetlaptop linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram