Contents


5 things you didn't know about ...

Apache Maven plugins

Tips for mastering modern Maven plugins

Comments

Content series:

This content is part # of # in the series: 5 things you didn't know about ...

Stay tuned for additional content in this series.

This content is part of the series:5 things you didn't know about ...

Stay tuned for additional content in this series.

Do you know Maven?

Maven is the leading dependency management and build tool for Java™ developers, and with good reason! It standardizes the software build process by articulating a project’s constitution, deploying it as an application, and sharing it with other projects.

Maven employs a robust set of plugins that provide all of its functionality. In Maven, plugins have goals, which under the hood are just Java methods. Goals perform build tasks such as compiling the project, packaging it, and deploying it to a local or remote server. These activities map perfectly to build lifecycle phases.

Maven comes with its build plugins packaged and ready to go, and defaults are preconfigured. Convention-over-configuration ensures that configuration is scaled to the complexity of the given task. Most build tasks require very minimal configuration.

You also can customize the behavior of Maven plugins. It’s easy to overwrite plugin defaults and define new values using Maven’s <configuration> element. Without doubt the most overwritten default values are the compiler plugin’s <source> and <target> values.

Need proof? How many times have you added the XML in Listing 1 to your POM to set the correct JVM version?

Listing 1. Java version configuration in the compiler plugin
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.6.1</version>
   <configuration>
       <source>1.8</source>
       <target>1.8</target>
   </configuration>
</plugin>

Executing the Maven lifecycle

Each plugin goal is mapped to a Maven lifecycle phase. Issuing the mvn compile command instructs the mvn utility to call all the goals bound to the compile lifecycle phase. The compiler plugin’s compile goal is bound to this lifecycle phase.

The phase-to-goal relationship is one-to-many. Multiple plugin goals may be bound to the same phase to form a collection of related tasks. Phases are also hierarchical, meaning the execution of one phase causes the execution of all its preceding phases.

In this case, issuing the mvn compile command initiates the validate phase (which is the first phase of Maven’s default build lifecycle), calling all goals bound to that phase.

The Maven and Google Code websites maintain a list of Maven plugins. These plugins provide a range of useful and time-saving functionality that you can incorporate into your build process. I’ll introduce some of the most useful ones I’ve found and show you how to make the most of them.

1. Digitally sign a JAR or WAR

Click the button below to download the code for this example. It contains a simple application and a POM file with a configured Jarsigner plugin.

Digitally signing a JAR or WAR is an important task, especially if you want to distribute your application. Luckily, the Java platform provides an archive-signing utility called jarsigner. It’s a command-line tool that signs an archive by passing it the location of the archive, along with various parameters, such as the keystore containing your cryptographic keys.

You can find jarsigner in the <%JAVA_HOME%>/bin directory of your JDK installation. Listing 2 shows how to sign a JAR using this utility.

Listing 2. Signing an archive using the jarsigner utility
jarsigner 
    -keystore /path/to/keystore.jks 
    -storepass <password> 
    -keypass <key password> 
    YourArchive.jar 
    alias

Out of the box, jarsigner is an easy-to-use command-line tool. But wouldn't it be nice to automate the signing process, and have the archive signed during the package phase of the build cycle? Well, you can do this thanks to the Maven Jarsigner plugin, which wraps the jarsigner utility. All you need to do is bind the plugin’s sign goal to your build’s package phase.

To get started you need a keystore. If you don't already have one, you can create it with the Java platform’s keytool utility, found in the <%JAVA_HOME%>/bin directory of your JDK installation.

Creating a keystore

To create a keystore, navigate to the location where you want it and execute the command in Listing 3. Be sure to replace KeyAlias with an appropriate value such as your domain name. The official Oracle documentation details further configuration options the tool accepts.

Listing 3. Creating a keystore for the jarsigner utility
keytool -genkey -alias <KeyAlias> -keyalg RSA -keystore keystore.jks -keysize 2048

The process of creating a keystore requires you to answer a series of questions about yourself and your organization, plus supply a secure password. You will need this password when you configure the Maven Jarsigner plugin, which you’ll do next.

Add Jarsigner to your build process

Now you’ll add the Maven Jarsigner plugin to your build lifecycle, then configure it to digitally sign a JAR produced during the package phase. It makes sense to hook into this phase because the JAR has already been constructed and stored in your project’s default output directory, which is usually the target directory. In the <plugins> section of your POM file, add the code shown in Listing 4.

Listing 4. Adding the Jarsigner plugin
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-jarsigner-plugin</artifactId>
   <version>1.4</version>
</plugin>

You want the JAR to be signed after it has been built, so you need to connect the signing process (which is wrapped in the sign goal) to the package lifecycle phase. To do this, add the code in Listing 5 to the plugin.

Listing 5. Connecting the sign goal to the package phase
   <executions>
       <execution>
           <id>sign</id>
           <phase>package</phase>
           <goals>
               <goal>sign</goal>
           </goals>
       </execution>
   </executions>

The sign goal needs some configuration details to be able to digitally sign the JAR. So between <configuration> elements you should add the keystore location, the alias for the credentials you want to use, the store password, and the password for the credentials, as shown in Listing 6.

Listing 6. Specifying security credential
<configuration>
   <keystore>/location/of/keystore.jks</keystore>
   <alias>KeyAlias</alias>
   <storepass>password</storepass>
   <keypass>password</keypass>
</configuration>

This is the minimum configuration required to get the JAR-signing feature working. Note that the plugin provides a handsome range of additional configuration options to choose from.

The final step is to build the JAR and verify that it has been correctly signed. From the command line, execute the package goal, like so: mvn clean package. In the console output, you’ll see the JAR building and then being signed. Figure 1 shows what this might look like.

Figure 1. Output showing archive signed
Output showing archive signed
Output showing archive signed

The JAR will be built into the target output directory, and this is where the Jarsigner plugin expects to find it. The plugin will sign the JAR with the credentials you’ve given, then it will add two additional files to the META-INF directory: a signature file with an .SF extension and a signature block file with an .RSA extension.

You now have a digitally signed JAR ready for distribution.

Verify digital signing

Before distributing the JAR, let’s verify that it has been properly signed. You can do this with the Maven Jarsigner’s verify goal, or via the command-line using the jarsigner tool. In Listing 7 I’ve used the command-line tool and passed it the archive file I want to verify.

Listing 7. Using the jarsigner utility to verify a signed JAR
jarsigner -verify target/YourJavaArchive.jar

If successful, you will receive the message: jar verified.

Signing WAR files

Signing a JAR is not the only trick Jarsigner has up its sleeve. The plugin can sign any Java archive file, including WAR files. To see how this works, change the POM’s packaging type from JAR to WAR, add the Maven WAR plugin (as shown in Listing 8), and execute the command: mvn clean package.

Listing 8. Adding the Maven WAR plugin
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-war-plugin</artifactId>
   <version>3.1.0</version>
</plugin>

Check the target directory and you will find the WAR file has been signed.

Signing multiple archives

If you have more than one archive to sign, Jarsigner can handle that situation, too. The code in Listing 9 specifies the location of the JARs to sign as target/all, and all JARs in this directory will be signed.

Listing 9. Specifying an archive directory
<archiveDirectory>target/all</archiveDirectory>

If you need more fine-grained control over the archives to be signed, you can use wildcards to explicitly include and exclude files, as shown in Listing 10.

Listing 10. Including and excludingselected archives
<includes>
   <include>*1.0.jar</include>
</includes>
<excludes>
   <exclude>*SNAPSHOT.jar</exclude>
</excludes>

2. Deploy a secured web app with Maven Cargo

Click the button below to download the code for this example. It contains a simple web application and a POM file with a configured Cargo plugin.

The Cargo plugin from Codehaus is a wrapper for the Cargo API. The API is designed for configuring, starting, stopping, and deploying applications to a range of supported containers, as well as parsing, creating, and merging Java EE modules.

One particularly interesting feature is Cargo’s ability to specify server properties in a container-agnostic way. Such properties include ports, remote security credentials, JVM arguments, and—most relevant to our interest—the login details for users of the application you want to deploy. The plugin supports 14 of the most popular servers, right up to the latest edition of each.

The Cargo plugin works best hooked into the package phase of the Maven lifecycle. It can also be executed independently by referencing the run goal directly: mvn cargo:run. Independent execution assumes the project has already been packaged and is ready for deployment.

Let's start with hooking Cargo’s run goal into the package phase, as shown in Listing 11.

Listing 11. Hooking Cargo’s run goal to the package phase
<plugin>
   <groupId>org.codehaus.cargo</groupId>
   <artifactId>cargo-maven2-plugin</artifactId>
   <version>1.6.4</version>
   <executions>
       <execution>
           <phase>package</phase>
           <goals>
               <goal>run</goal>
           </goals>
       </execution>
   </executions>
</plugin>

With the Maven integration done, you now need to configure the container where you want Cargo to deploy the application. Cargo supports a variety of container scenarios, including deploying to a local or remote server that is already running (which includes starting up the server if required). Alternatively, Cargo can be configured to download, install, deploy, and start any of the 14 servers it supports.

For this example I will use IBM® WebSphere® Liberty which I have already downloaded and extracted into a directory called servers.

The container configuration expects at least the container ID and the home location of the installed server. Listing 12 shows the configuration of a Liberty server located in my servers/wlp directory.

Listing 12. Container configuration
<configuration>
   <container>
       <containerId>liberty</containerId>
       <home>\path\to\servers\wlp</home>
   </container>
</configuration>

Download the container ZIP

Another option is to specify the URL of the Liberty ZIP on the IBM website, in which case the Cargo plugin will download and install the server. Listing 13 shows what this would look like.

Listing 13. Installing the container from the web
<container>
   <containerId>liberty</containerId>
   <zipUrlInstaller>
       <url>
              https://public.dhe.ibm.com/ibmdl/export/pub/
              software/websphere/wasdev/downloads/wlp/17.0.0.2/
              wlp-webProfile7-17.0.0.2.zip
       </url>
       <downloadDir>/path/to/downloadDir</downloadDir>
       <extractDir>/path/to/extractDir</extractDir>
   </zipUrlInstaller>
</container>

The first time you execute the run goal, the ZIP file is downloaded and extracted. On subsequent executions Maven will detect that the file has already been downloaded and installed, and will skip that task.

You now have a bare-bones configuration, which you can test by deploying the application into your server. Kickoff the process by calling the package phase (mvn package). The application will be compiled, packaged, and deployed into the server, and can be reached at the URL: http://localhost:8080/{artifactid}/index.html.

Customize the context root

By default the artifactid is used to name the context root of the web application. In some cases you will need to specify a custom context root. In Listing 14 I’ve defined the context root as home.

Listing 14. Customizing the context root
<deployables>
   <deployable>
       <properties>
           <context>home</context>
       </properties>
   </deployable>
</deployables>

When the application is deployed, the web application can be reached at: localhost:8080/home/index.html.

Specify user logins

You now have a pretty robust deployment process that downloads and installs a server and deploys your application into a custom context root. But this is just the beginning of what the Cargo plugin can do.

Cargo really comes into its own when we start defining configuration properties. As I mentioned before, these are properties of the container such as port, protocol, and most interestingly, user login details.

Let’s add a user to an example application and force login to the site. For simplicity, we will use the basic authentication method, which requires very minimal configuration in the app’s web.xml file. Listing 15 shows the specification of the user-role and a restriction to the WelcomeServlet web resource.

Listing 15. Declaring the user-role
<web-app version="3.1"...>

   <security-constraint>
       <web-resource-collection>
           <web-resource-name>Welcome Servlet</web-resource-name>
           <url-pattern>/WelcomeServlet/*</url-pattern>
           <http-method>GET</http-method>
       </web-resource-collection>

       <auth-constraint>
           <role-name>user-role</role-name>
       </auth-constraint>
   </security-constraint>

   <login-config>
       <auth-method>BASIC</auth-method>
       <realm-name>file</realm-name>
   </login-config>

   <security-role>
       <role-name>user-role</role-name>
   </security-role>

</web-app>

You can configure the Cargo plugin to create a user for the user-role, which is done on-the-fly at deployment time. Listing 16 show a username and password being defined for the user-role. The username is user and the password is user123!.

Listing 16. Defining a username and password
<configuration>
   <properties>
      <cargo.servlet.users>
         user:user1234!:user-role
      </cargo.servlet.users>
   </properties>
</configuration>

The format for the <cargo.servlet.users> property is username:password:role. By separating the set with the pipe symbol, you can declare multiple users for the same and different roles within a single property:

username1:password1:role11,...,role1N|username2:password2:role21,...,role2N|...

With the username and password assigned to the user role, you are now ready to package and deploy the application. With just one command—mvn clean package—your web application will be compiled, packaged, and deployed to the Liberty container. The server will also be started.

If you navigate to the URL http://localhost:8080/home/WelcomeServlet you will be presented with a login box, as shown in Figure 2. Enter the username and password configured in the Cargo plugin and click Log In to enter the application.

Figure 2. The login challenge
The login challenge
The login challenge

3. Install a custom JAR in your local repository

The traditional way to include JARs in your project is to create a directory in your project and ensure that it is on the classpath. With Maven you don't need to do this. Instead of requiring that you co-locate JARs with your project, Maven stores them in an isolated local repository. Your project must then specify a dependency on those JARs.

The local repository is populated with dependencies downloaded from the central Maven repository. This is the Maven mothership and contains thousands of popular and commonly used JARs. But what if the JAR you want to use is not in Maven central? You could, of course, rely on the traditional solution and include your JAR directly in your project, but then you would not benefit from Maven’s dependency management.

Instead, you can use the maven install plugin to install the JAR into your local Maven repository. First, you’ll need to download the plugin, then you can execute its install-file goal. This example is a little different from the previous ones because you will not hook the goal to a Maven lifecycle phase. Instead, you’ll specify the goal in the Maven command-line tool.

The install-file goal requires five parameters that specify the location of the JAR file, the group and artifactid, its version number, and the package type. Listing 17 shows the structure of the Maven command.

Listing 17. Installing a JAR in the local Maven repository
mvn install:install-file 
    -Dfile=PATH/TO/YOUR-JAR.jar 
    -DgroupId=GROUP-ID 
    -DartifactId=ARTIFACT-ID 
    -Dversion=VERSION 
    -Dpackaging=jar

For the JAR you wish to install, you will need to specify values for the shown parameters. These can be anything you wish. Once executed, you can see for yourself that the new file is stored in your local Maven repository. Navigate to the repository directory /.m2/repository and find the folder with the name GROUP-ID. If the groupId value is a reverse domain name such as com.readlearncode, simply search for the com directory then navigate to the readlearncode directory. Here is where you will find a folder with the version number of the artifact and the JAR installed inside. The location of the JAR may look something like this: /.m2/repository/com/readlearncode/4.0/MyJar.jar.

Your JAR is now available to all your projects. You can add it as a dependency to your POM in the usual way, as shown in Listing 18.

Listing 18. Specifying your JAR as a dependency
<dependency>
   <groupId>GROUP-ID</groupId>
   <artifactId>ARTIFACT-ID</artifactId>
   <version>VERSION</version>
</dependency>

4. Create a GitHub-based Maven repository

Click the button below to download the code for this example. It contains a simple application and a POM file with the site-maven-plugin configured.

There are many reasons to host a Maven repository on GitHub, including licensing issues, privacy, and cost of commercial hosting. Regardless of why you choose to host a GitHub-based Maven repository, setting one up is easy.

To start, create a branch in an existing GitHub repository and install your project’s artifacts directly into the branch. This will bind to the deploy phase of Maven’s lifecycle, so that when the project is deployed the remote repository is automatically refreshed.

Add GitHub credentials

After you’ve created the Maven branch in your GitHub repository, you need to add your GitHub repository credentials to Maven’s settings.xml file. This file is usually found in the .m2 directory. Between settings.xml’s <servers> elements, you need to define a new server and specify your username and password. Listing 19 demonstrates how this might look (note that I’ve named my new server github).

Listing 19. Configuring a github repo as a server
 <servers>
	<server>
		<id>github</id>
  		<username>USERNAME_OR_EMAIL</username>
		<password>PASSWORD</password>
	</server>
 </servers>

Two-Factor Authentication

If you have two-factor authentication enabled you’ll have to create a Personal Access Token via your GitHub Settings. Copy and paste the token directly to the <password> elements. For extra security, passwords can be encrypted by following the instructions on the Maven website.

Maven site plugin

In your Maven POM you will be using GitHub’s site-maven-plugin. The first thing you need to do is set a base directory from which to commit files. Start by creating a staging repository and locating it in the project's target directory, as shown in Listing 20. For this example I have called mine repo-stage.

Next, because you want to hook this plugin to the deploy phase, you have to specify the repository location in the maven-deploy-plugin’s <altDeploymentRepository>. The code snippet in Listing 20 shows how this is done.

Listing 20. Specifying the alternative local repository
<plugin>
   <artifactId>maven-deploy-plugin</artifactId>
   <version>2.8.2</version>
   <configuration>
       <altDeploymentRepository>
              Repo.stage::default::file://
              ${project.build.directory}/repo-stage
       </altDeploymentRepository>
   </configuration>
</plugin>

Finally, you need to configure the site-maven-plugin and hook it into the deploy phase. To do this, pass the plugin the id of the GitHub server you created earlier. You also need to pass it the repository name and owner, the branch where you are committing project artifacts, and a comment for the commit. You also need to switch off Jekyll, so that it doesn’t think it has to generate GitHub pages.

Listing 21 shows a complete configuration of the site-maven-plugin. Note that the repository name is dependency (if the branch doesn’t exist GitHub will create it).

Listing 21. Configuring the site plugin
<plugin>
   <groupId>com.github.github</groupId>
   <artifactId>site-maven-plugin</artifactId>
   <version>0.12</version>
   <configuration>
       <!-- Github settings -->
       <server>github</server>
       <repositoryName>MavenSampler</repositoryName>
       <repositoryOwner>atheedom</repositoryOwner>
       <branch>refs/heads/dependency</branch>
       <message>
         Artifacts for
         ${project.name}/${project.artifactId}/${project.version}
       </message>
       <noJekyll>true</noJekyll>
       <!-- Deployment values -->
       <outputDirectory>
              ${project.build.directory}/repo-stage
       </outputDirectory>
       <includes>
           <include>**/*</include>
       </includes>
   </configuration>
   <executions>
       <execution>
           <phase>deploy</phase>
           <goals>
               <goal>site</goal>
           </goals>
       </execution>
   </executions>
</plugin>

All that's left to do is to is run the command mvn clean deploy. In the console, you’ll see the plugin uploading the repository files to your GitHub account, creating the commit, and pushing it to the dependency branch. The console output should look something like Figure 3.

Figure 3. Console output of a successful commit
Console output of successful commit
Console output of successful commit

To verify that everything is working as it should, visit your GitHub repository and select the dependency branch. You should see all your project’s artifacts, as shown in Figure 4.

Figure 4. Snapshot of the Maven repository in GitHub
Snapshot of the Maven repository in GitHub
Snapshot of the Maven repository in GitHub

Once you’ve confirmed that your artifacts are safely committed to your GitHub Maven repository, you will want to share the new repository with others who wish to rely on your project.

Add GitHub as repository

To specify your GitHub Maven repository as a remote repository for their own projects, subscribers will need to declare the repository in their POM, as shown in Listing 22. Then you’ll specify your project’s artifacts in the normal way, as shown in Listing 23.

Listing 22. Specifying GitHub as a remote repository
<repositories>
   <repository>
       <id>PROJECT_NAME-dependency</id>
       <url>
              https://raw.github.com/
              GITHUB_USERNAME/PROJECT_NAME/dependency
       </url>
       <snapshots>
           <enabled>true</enabled>
           <updatePolicy>always</updatePolicy>
       </snapshots>
   </repository>
</repositories>
Listing 23. Project dependency
<dependency>
   <groupId>com.readlearncode</groupId>
   <artifactId>maven-sampler</artifactId>
   <version>1.0</version>
</dependency>

Now you have a poor-man's public Maven repository into which to deploy your dependencies and share them with the world.

5. More timesaving tips and tricks

Apache Maven is so widely used that finding tips and tricks that are unknown is like prospecting for gold. So I’ve decided to conclude with a collection of tidbits that I have found extremely useful. Perhaps among them you will find a few nuggets that will save you time and frustration.

A ‘Roomba’ for unused dependencies

Developing is fun and exciting, especially when you get to play with new APIs and technology. Maven makes it quick and easy to get into something new, too: all you have to do is copy and paste the dependency into your POM file and you’re off. As developers we sometimes get carried away with enthusiasm, however, and forget to remove unused dependencies; or we’re just too lazy to do it. Over time, this bad habit can bloat your POM and slow down deployments.

So my first tip is the Maven equivalent of the robotic vacuum cleaner, Roomba. Simply run the command in Listing 24 and Maven will analyze your project’s code for unused dependencies and identify them.

Listing 24. Identify unused dependencies
dependency:analyze

Purge the local repository

From time to time you will need to purge your Maven directory—perhaps due to corruption, or if your IDE is indexing really slowly. Whatever the reason, you can remove all the gunk by executing the code in Listing 25.

Listing 25. Purging the local repository
mvn dependency:purge-local-repository

This will purge and then automatically download all the dependencies for your current project. Your project will feel reborn and ready to out-perform itself again.

Timestamp it!

Adding a timestamp to your build can be very useful. Maven 2 introduced the build variable maven.build.timestamp, which denotes the start of the build. You can use this variable anywhere in your POM with the variable ${maven.build.timestamp}.

The timestamp is formatted using the Java SimpleDateFormat class and declared with the property <maven.build.timestamp.format> property. Listing 26 shows an example of how to do this.

Listing 26. Timestamp format
<properties>
    <maven.build.timestamp.format>
        yyyy-MM-dd'T'HH:mm:ss'Z'
    </maven.build.timestamp.format>
</properties>

Echo properties

Have you ever wanted to know a Maven property value? Instead of guessing you can be sure with the maven-antrun-plugin. This neat little tool echos any property you pass it to the console.

In Listing 27, antrun’s run goal is hooked into Maven’s validate phase (the first default lifecycle phase) and uses the <echo> element in the configuration section to output property values.

Listing 27. Echo a property to the console
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-antrun-plugin</artifactId>
   <version>1.1</version>
   <executions>
       <execution>
           <phase>validate</phase>
           <goals>
               <goal>run</goal>
           </goals>
           <configuration>
               <tasks>
                   <echo>
                        settings.localRepository = ${settings.localRepository}
                   </echo>
                   <echo>
                        project.build.directory = ${project.build.directory}
                   </echo>
               </tasks>
           </configuration>
       </execution>
   </executions>
</plugin>

Now you can simply use mvn validate to execute the validate phase and see the property value output to the console.

Conclusion

Maven is far more than just a build tool, thanks to the plugins that simplify and automate a vast range of development tasks. In this article I've presented a handful of plugins and offered tips for using them for a more effective and productive development lifecycle. For even more about Maven and its project lifecycle, see "5 things you didn't know about ... Apache Maven," which I've recently updated for Maven 3.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java development, Open source, WebSphere
ArticleID=1049453
ArticleTitle=5 things you didn't know about ...: Apache Maven plugins
publish-date=09112017