ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Extending Maven Through Plugins

by Eric Pugh
03/17/2004

You may already be using Maven, a build tool that attempts to put structure around your build environment. As part of this, Maven comes with a very rich set of plugins that provide enhanced functionality such as running JUnit tests; building .jar, .war, and .ear files; and generating reports like JCoverage, Clover, PMD, or Simian. However, what happens when your favorite tool doesn't have a corresponding Maven plugin? This is where writing your own Maven plugin allows you to add new functionality to Maven.

This article will cover creating a "Hello World" plugin for Maven. We will demonstrate the various aspects of Maven plugin development, starting with a simple "Hello World" goal, introducing the use of Java code in goals, and finally generating a web report. This article assumes you have already downloaded and installed the latest version of Maven. For more information about using Maven, please read Rob Herbst's ONJava article "Developing with Maven."

Step 1. Creating the "Hello World" Plug-in

All of the source code for this article is available as a .zip file. Just download and unzip it to a convenient location.

Our first example is a very simple plug-in that will emit "Hello World" to the console when you run maven helloworld.

Let's look at what makes up a plugin. In the step1 directory there are two files, project.xml and plugin.jelly. The project.xml file is your standard Maven POM (Project Object Model) file. Because there is no Java code, or any resources, it is a very short file. The plugin.jelly file is the heart of your plugin. The extension .jelly means that the plugin is written using Jelly, a XML-based scripting language that is similar to the one used by Ant. Indeed, Ant syntax can be used directly inside of Jelly scripts! More information is available in the ONJava article "Using the Jakarta Commons, Part 2."

When you look at plugin.jelly you can see how simple it is:

<project
  xmlns:ant="jelly:ant"
  >
  <goal name="helloworld"
        description="Emit Hello World">
    <ant:echo message="Hello World"/>
  </goal>
</project>

As you can see, we are just using the Ant tag echo to send "Hello World" to the console! We import the namespace ant with the XML xmlns:ant="jelly:ant". You can think of a namespace as a Java import statement for Jelly.

From the directory step1, install the plugin by entering maven plugin:install.

c:\[your dir]\step1\maven plugin:install

At the end of the output from the console, you will see that it has copied a file to your plugins directory.

plugin:install:
   [copy] Copying 1 file to C:\java\maven\plugins
BUILD SUCCESSFUL
Total time: 3 seconds
Finished at: Tue Feb 03 19:00:01 CET 2004

You are now ready to run your plugin. On the command line, enter the following (this is the Windows version):

c:\[your dir]\step1\maven -g

You will see a long list of all the goals available to Maven. The goals with a description are meant to be used by users; the goals without a description are internal goals for Maven. You should see your plugin has been registered:

[helloworld] : Emit Hello World

Now go ahead and run the plugin. Enter:

c:\[your dir]\step1\maven helloworld

You should see on the console a "Hello World" message like this:

 __  __
|  \/  |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \  ~intelligent projects~
|_|  |_\__,_|\_/\___|_||_|  v. 1.0-rc2-SNAPSHOT

helloworld:
    [echo] Hello World
BUILD SUCCESSFUL
Total time: 1 seconds
Finished at: Tue Feb 03 19:06:18 CET 2004

If you want to generate the typical Maven documentation for the plugin, you use the site command:

c:\[your dir]\step1\maven site

Notice it generates all of the typical Maven documentation in step1/target/docs/. All the functionality of Maven that you would use in any other type of project is also available in a plugin project. There is nothing special that you need to configure to run unit tests, code coverage reports, etc.

Step 2. Parameterize "Hello World"

This is all well and good, but what if want to customize the message emitted when you enter maven helloworld? We can parameterize the plugin by providing a plugin.properties file. Look at step2/plugin.properties:

helloworld.message=Hello out there!

Notice the helloworld.message property? This is the default value used by the plugin. The plugin.properties file provides defaults for the plugin to use. If you want to customize this plugin property, then in your project.properties file, specify a different value, such as helloworld.message=My custom hello world message.

If you open step2/plugin.jelly, you will now see that the message has been replaced with ${helloworld.message}. Any parameter you need to replace can be done with the ${} syntax, just like in Ant!

When packaging this plugin, Maven needs to know to package the new plugin.properties file. Look at step2/project.xml and you can see we are explicitly including the various plugin-related files:

<resources>
 <resource>
  <directory></directory>
  <includes>
   <include>plugin.jelly</include>
   <include>plugin.properties</include>
   <include>project.properties</include>
   <include>project.xml</include>
  </includes>
 </resource>
</resources>

Go ahead and compile the step2 version of the plugin by switching to the step2 directory and running the install goal:

c:\helloworld\step2\maven plugin:install

The output should look something like this:

jar:jar:
    [jar] Building jar: C:\clients\jn\html\
helloworld\step2\target\helloworld-plugin-2.0.jar

    [copy] Copying 1 file to C:\java\maven\
repository\helloworld-plugin\plugins

plugin:uninstall:
    [delete] Deleting 1 files from
    C:\java\maven\plugins
    [delete] Deleting 8 files from
    C:\java\maven\plugins
    [delete] Deleted 2 directories from
    C:\java\maven\plugins

plugin:install:
    [copy] Copying 1 file to
    C:\java\maven\plugins
BUILD SUCCESSFUL

Did you notice in the output that you compiled a .jar that was set to version 2.0, and that the plugin:install goal deleted the older 1.0 version of the plug-in? Maven is smart enough to delete all other versions of this plugin when you install a new one.

Go ahead and run the plugin. You will see that it will emit Hello out there! if you don't have helloworld.message specified in your project.properties file.

Step 3. Extending the Plugin Through Java Classes

Now we have a helloworld plug-in that gives us a nice message, but what about adding the date and time to the message? Well, it would be nice if we could specify a property describing the date/time format to use, and get back the date formatted. Unfortunately there isn't a Jelly tag to do this, and maybe we don't want to use the Ant <tstamp/> task.

Fortunately it is very easy to write your own Java classes and integrate them in. First we need to update our POM to specify where our unit tests and Java code live:

<sourceDirectory>src/main</sourceDirectory>
<unitTestSourceDirectory>src/test</unitTestSourceDirectory>

We also add to the <dependencies> section of the POM a dependency on commons-logging from Jakarta:

<dependencies>
 <dependency>
 <groupId>commons-logging</groupId>
 <artifactId>commons-logging</artifactId>
 <version>1.0.3</version>
 <properties>
 <classloader>root.maven</classloader>
 </properties>
 </dependency>
</dependencies>

By adding this dependency to our POM, Maven will download from www.ibiblio.org/maven the commons-logging-1.0.3.jar file. Thus, when you distribute the plugin to new users, all of the .jar dependencies defined for the plugin will be downloaded. This means that your plugin distributable can be very small!

Take a look at step3/src/main/example/helloworld/DateFormatter.java. Notice how we don't have to extend any classes or implement any interfaces? This makes it much easier to write portable code. And, of course, because we have Java code and a plugin is like any other Maven project, we can add to our <reports/> section of POM the reports for Javadocs, JUnit tests, JCoverage, etc.

Go ahead and test the code by entering:

c:\helloworld\step3\maven test

Assuming it all passes, you can also generate all of the site documentation into step3/target/docs by entering:

c:\helloworld\step3\maven site

Now that we have our DateFormatter properly working, let's start using it in our plugin.jelly script.

First, we have to import two more namespaces into our project tag:

  xmlns:define="jelly:define"
  xmlns:dateformatter="dateformatter"

The jelly:define imports the tag needed to define and interact with our DateFormatter class. The dateformatter specifies under which namespace our DateFormatter will load up.

<define:taglib uri="dateformatter">
  <define:jellybean
    name="dateformatter"
    className="example.helloworld.DateFormatter"
    method="doExecute"
    />
</define:taglib>

As you can see, because we pass the method that will be the equivalent of the main method, we don't have to implement any interfaces. We could call the method to be executed doSomething if we wanted to. This provides a lot of flexibility in reusing existing code.

Now let's go ahead and actually format the date. There is a new variable helloworld.dateFormatPattern that has been defined in plugin.properties. This can be overridden in your project.properties just like the helloworld.message property. The dateformatter object will be bound to a variable called $df when we invoke it so we can later retrieve the formatted date:

<goal name="helloworld" description="Emit Hello World">
<dateformatter:dateformatter
  var="df"
  dateFormatPattern=
    "${helloworld.dateFormatPattern}"
  />

  <ant:echo message="${helloworld.message}
  on ${df.formattedDate}"/>
</goal>

Go ahead and install the plugin and run it by entering:

  c:\helloworld\step3\maven plugin:install
  c:\helloworld\step3\maven helloworld

You should see some output similar to:

helloworld:
    [echo] Hello out there! on 03/07/04 10:07:35
BUILD SUCCESSFUL

Step 4. Integrating a "Hello World" Maven Report

One of the differentiators between Maven and other tools like Ant and Make is the integrated reports. Maven makes it easy to have reports generated by plugins that share a look and feel and are tied into the site navigation. Typical reports include the results of automated unit tests or generated Javadocs.

To register a new report, you need to define three new goals in your plug-in: helloworld-plugin:register, helloworld-plugin:deregister, and helloworld-plugin:report. These goals are called by Maven to register, unregister, and generate the report, respectively. Then, in the <reports/> section of the POM, you list the new report to be run:

<reports>
  <report>helloworld-plugin</report>
</reports>

Take a look at step4/plugin.jelly. We have added two more namespaces to support generating reports: xmlns:j="jelly:core" and xmlns:doc="doc".

Then we added the goal helloworld-plugin:report, which takes the "Hello World" message and saves it to a text file:

<goal name="helloworld-plugin:report"
    prereqs="helloworld,xdoc:init"
    description="Emit Hello World to File">

<j:file
  name="${maven.build.dir}/helloworld.txt"
  prettyPrint="true"
  outputMode="txt"
  omitXmlDeclaration="true">
  ${helloworld.message} on ${df.formattedDate}
</j:file>

<j:set var="genDocs"
  value="${maven.gen.docs}" />
<doc:text-xdoc
    title="Hello World Report"
    section="Hello World Report"
    inputFile="${maven.build.dir}/helloworld.txt"
    output="${genDocs}/helloworld.xml"/>

</goal>

Remember the prereqs tag? This specifies that we need to run these goals prior to running the helloworld-plugin:report goal. Also recall that the helloworld goal bound a DateFormatter object to the variable ${df}, which we can then use in all subsequent goals.

The rest consists of writing out a text file in step4/target/helloworld.txt with the text string and then reformatting it using the doc:text-xdoc tag to an XML file.

Go ahead and install the plug-in and then generate the site by entering:

  c:\helloworld\step4\maven plugin:install
  c:\helloworld\step4\maven site

This will generate, in step4/target/docs/, the site documentation that includes your "Hello World" report, which is shown in Figure 1:

Figure 1. Hello World Report
Figure 1. "Hello World" Report

Lastly, there are two basic documents in the /step4/xdocs directory: goals.xml and properties.xml. These are pretty standard documents used in plug-ins to describe what the available goals and properties are for your users, and are available under the Overview menu option in the generated docs.

Distributing Plug-ins

To distribute your plug-in, enter:

  c:\helloworld\step4\maven plugin

This will create a .jar file that has everything you need and will install it into your local Maven repository:

jar:jar:
    [jar] Building jar: C:\clients\jn\html\
    helloworld\step4\target\
    helloworld-plugin-4.0.jar

    [copy] Copying 1 file to C:\java\maven\
    repository\helloworld-plugin\plugins
BUILD SUCCESSFUL
Total time: 3 seconds

If you distribute the plugin on a networked repository, then users can download the plugin via the plugin:download goal.

First, you define in your project.properties file where Maven should look for resources to download:

maven.repo.remote=http://my.local.repository/maven

Then, from the command line, you can run plugin:download and Maven will attempt to download the plugin and install it from your local repository.

c:\helloworld\step4\maven plugin:download
  -DartifactId=helloworld-plugin
  -DgroupId=helloworld-plugin
  -Dversion=4.0

In the above example, Maven will look for the plugin.jar file at:

http://my.local.repository/maven/helloworld-plugin/
plugins/helloworld-plugin-4.0.jar

Conclusion

As you can see, Maven's plugin architecture makes it very easy to write your own reusable functionality. The ability to directly use Ant tasks allows you to leverage all the existing Ant tasks in your Maven plugins. And because you can define any Java class as a tag, you can very easily integrate functionality that doesn't already have an Ant task.

Eric Pugh is a freelance consultant who specializes in mentoring groups using open source Java software successfully.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.