
While this chapter covers an advanced topic, don't let the idea of writing a Maven plugin intimidate. For all of the theory and complexity of this tool, the fundamental concepts are easy to understand and the mechanics of writing a plugin are straightforward. After you read this chapter, you will have a better grasp of what is involved in creating a Maven plugin.
Most of this book has dealt with using Maven, and for a book on Maven, you haven't seen too many code examples dealing with Maven customization. In fact, you haven't yet seen any. This is by design, 99 out of 100 Maven users will never need to write a custom plugin to customize Maven; there is an abundance of configurable plugins, and unless your project has particularly unique requirements, you will have to work to find a reason to write a new plugin. An even smaller percentage of people who end up writing custom plugins will ever need to crack open the source code for Maven and customize a core Maven component. If you really need to customize the behavior of Maven, then you would write a plugin. Modifying the core Maven code is as far out of scope for most developers as modifying the TCP/IP stack on an operating system, it is that abstract for most Maven users.
On the other hand, if you are going to start writing a custom plugin, you are going to have to learn a bit about the internals of Maven: How does it manage software components? What is a Plugin? How can I customize the lifecycle? This section answers some of those questions, and it introduces a few concepts at the core of Maven's design. Learning how to write a custom Maven plugin is the gateway to customizing Maven itself. If you were wondering how to start understanding the code behind Maven, you've found the proper starting line.
At the heart of Maven is an Inversion of Control (IoC) named Plexus. What does it do? It is a system for managing and relating components. While there is a canonical essay about IoC written by Martin Fowler, the concept and term have been so heavily overloaded in the past few years it is tough to find a good definition of the concept that isn't a self-reference (or just a lazy reference to the aforementioned essay). Instead of resorting to a Wikipedia quote, we'll summarize Inversion of Control and Dependency Injection with an analogy.
Assume that you have a series of components which need to be wired together. When you think about components, think stereo components not software components. Imagine several stereo components hooked up to a Playstation 3 and a Tivo that have to interface with both an Apple TV box and a 50" flat panel LCD TV. You bring everything home from the electronics store and you purchase a series of cables that you are going to use to connect everything to everything else. You unpack all of these components, put them in the right place and then get to the job of hooking up fifty thousand coaxial cables and stereo jacks to fifty thousand digital inputs and network cables. Step back from your home entertainment center and turn on the TV, you've just performed dependency injection, and you've just been an inversion of control container.
So what did that have to do with anything? Your Playstation 3 and
a Java Bean both provide an interface. The Playstation 3 has two inputs:
power and network, and one output to the TV. Your JavaBean has three
properties: power, network, and
tvOutput. When you open the box of your Playstation
3, it didn't provide you with detailed pictures and instructions for how
to connect it to every different kind of TV that might be in every
different kind of house, and when you look at your Java Bean it just
provides a set of properties, not an explicit recipe for creating and
managing an entire system of components. In an IoC container like
Plexus, you are responsible for declaring the relationships between a
set of components which simply provide an interface of inputs and
outputs. You don't instantiate objects, Plexus does; your application's
code isn't responsible for managing the state of components, Plexus is.
Even though it sounds very cheesy, when you start up Maven, it is
starting Plexus and managing a system of related components just like
your stereo system.
What are the advantages of using an IoC container? What is the advantage of buying discrete stereo components? If one component breaks, you can drop in a replacement for your Playstation 3 without having to spend $20,000 on the entire system. If you are unhappy with your TV, you can swap it out without affecting your CD player. Most important to you, your stereo components cost less and are more capable and reliable because manufacturers can build to a set of known inputs and outputs and focus on building individual components. Inversion of Control containers and Dependency Injection encourage Disaggregation and the emergence of standards. The software industry likes to imagine itself as the font of all new ideas, but dependency injection and inversion of control are really just new words for the concepts of Disaggregation and interchangeable machinery. If you really want to know about DI and IoC, learn about the Model T, the Cotton Gin, and the emergence of a railroad standard in the late 19th century.
The most important feature of an IoC container
implemented in Java is a mechanism called dependency injection. The
basic idea of IoC is that the control of creating and
managing objects is removed from the code itself and placed into the
hands of an IoC framework. Using dependency injection
in an application that has been programmed to interfaces, you can create
components which are not bound to specific implementations of these
interfaces. Instead, you program to interfaces and then configure Plexus
to connect the appropriate implementation to the appropriate component.
While your code deals with interfaces, you can capture the dependencies
between classes and components in an XML file that
defines components, implementation classes, and the relationships
between your components. In other words, you can write isolated
components, then you can wire them together using an
XML file that defines how the components are wired
together. In the case of Plexus, system components are defined with an
XML document that is found in
META-INF/plexus/components.xml.
In a Java IoC container, there are several methods for injecting dependencies values into a component object: constructor, setter, or field injections. Although Plexus is capable of all three dependency injection techniques, Maven only uses two types: field and setter injection.
Constructor injection is populating an object's values
through its constructor when an instance of the object is created.
For example, if you had an object of type
Person which had a constructor
Person(String name, Job job), you could
pass in values for both name and the
job via this constructor.
Setter injection is using the setter method of a property on
a Java bean to populate object dependencies. For example, if you
were working with a Person object with the
properties name and job, an
IoC container which uses setter injection,
would create an instance of Person using a
no-arg constructor. Once it had an instance of
Person, it would proceed to call the
setName() and
setJob() methods.
Both Constructor and Setter injection rely on a call to a
public method. Using Field injection, an IoC
container populates a component's dependencies by setting an
object's fields directly. For example, if you were working with a
Person object that had two fields
name and job, your
IoC container would populate these dependencies
by setting these fields directly (i.e. person.name =
"Thomas"; person.job = job;)
Spring does happen to be the most popular IoC container at the moment, and there's a good argument to be made that it has affected the Java "ecosystem" for the better forcing companies like Sun Microsystems to yield more control to the open source community and helping to open up standards by providing a pluggable, component-oriented "bus". But, Spring isn't the only IoC container in open source. There are many IoC containers (like PicoContainer).
Years and years ago, when Maven was created, Spring wasn't a mature option. The initial team of committers on Maven were more familiar with Plexus because they invented it, so they decided to use it as an IoC container. While it might not be as popular as the Spring Framework, it is no less capable. And, the fact that it was created by the same person who created Maven makes it a perfect fit. After reading this chapter you've have an idea of how Plexus works. If you already use an IoC container you'll notice similarities and differences between Plexus and the container you currently use.
Just because Maven is based on Plexus doesn't mean that the Maven community is "anti-Spring" (we've included a whole chapter with a Spring example in this book, portions of the Spring project are moving to Maven as a build platform). The question, "Why didn't you use Spring?" comes up often enough it did make sense to address it here. We know it, Spring is a rock star, we don't deny it, and it is on our continuing to-do list to introduce people to (and document) Plexus: choice in the software industry is always a good thing.
A Maven Plugin is a Maven artifact which contains a plugin
descriptor and one or more Mojos. A Mojo can be thought of as a goal in
Maven, and every goal corresponds to a Mojo. The
compiler:compile goal corresponds to the
CompilerMojo class in the Maven Compiler Plugin,
and the jar:jar goal corresponds to the
JarMojo class in the Maven Jar Plugin. When you
write your own plugin, you are simply grouping together a set of related
Mojos (or goals) in a single plugin artifact.[5]
Mojo? What is a Mojo? The word mojo[5] is defined as "a magic charm or spell", "an amulet, often in a small flannel bag containing one or more magic items", and "personal magnetism; charm". Maven uses the term Mojo because it is a play on the word Pojo (Plain-old Java Object).
A Mojo is much more than just a goal in Maven, it is a component managed by Plexus that can include references to other Plexus components.
A Maven plugin contains a road-map for Maven that tells Maven about
the various Mojos and plugin configuration. This plugin descriptor is
present in the plugin JAR file in
META-INF/maven/plugin.xml. When Maven loads a plugin,
it reads this XML file, instantiates and configures
plugin objects to make the Mojos contained in a plugin available to
Maven.
When you are writing custom Maven plugins, you will almost never
need to think about writing a plugin descriptor. In Chapter 10, The Build Lifecycle, the lifecycle goals bound to the
maven-plugin packaging type show that the
plugin:descriptor goal is bound to the
generate-resources phase. This goal generates a plugin
descriptor off of the annotations present in a plugin's source code. Later
in this chapter, you will see how Mojos are annotated, and you will also
see how the values in these annotations end up in the
META-INF/maven/plugin.xml file.
Example 17.1, “Plugin Descriptor” shows a plugin descriptor for the Maven Zip Plugin. This plugin is a contrived plugin that simply zips up the output directory and produces an archive. Normally, you wouldn't need to write a custom plugin to create an archive from Maven, you could simply use the Maven Assembly Plugin which is capable of producing a distribution archive in multiple formats. Read through the following plugin descriptor to get an idea of the content it contains.
Example 17.1. Plugin Descriptor
<plugin>
<description></description>
<groupId>com.training.plugins</groupId>
<artifactId>maven-zip-plugin</artifactId>
<version>1-SNAPSHOT</version>
<goalPrefix>zip</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>zip</goal>
<description>Zips up the output directory.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>package</phase>
<implementation>com.training.plugins.ZipMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<parameters>
<parameter>
<name>baseDirectory</name>
<type>java.io.File</type>
<required>false</required>
<editable>true</editable>
<description>Base directory of the project.</description>
</parameter>
<parameter>
<name>buildDirectory</name>
<type>java.io.File</type>
<required>false</required>
<editable>true</editable>
<description>Directory containing the build files.</description>
</parameter>
</parameters>
<configuration>
<buildDirectory implementation="java.io.File">
${project.build.directory}</buildDirectory>
<baseDirectory implementation="java.io.File">
${basedir}</baseDirectory>
</configuration>
<requirements>
<requirement>
<role>org.codehaus.plexus.archiver.Archiver</role>
<role-hint>zip</role-hint>
<field-name>zipArchiver</field-name>
</requirement>
</requirements>
</mojo>
</mojos>
<dependencies>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependencies>
</plugin>
There are three parts to a plugin descriptor: the top-level
configuration of the plugin which contains elements like
groupId and artifactId, the
declaration of mojos, and the declaration of dependencies. Let's examine
each of these sections in more detail.
The top-level configuration values in the
plugin element are:
This element contains a short description of the plugin. In the case of the Zip plugin, this description is empty.
Just like everything else in Maven, plugins need to have a unique coordinate. The groupId, artifactId, and version are used to locate the plugin artifact in a Maven repository.
This element controls the prefix used to reference goals in
a particular plugin. If you were to look at the Compiler plugin's
descriptor you would see that goalPrefix has a
value of compile, and if you look at the
descriptor for the Jar plugin, it would have a
goalPrefix of jar. It is
important that you choose a distinct goal prefix for your custom
plugin.
This is a legacy property which is no longer used by Maven.
It is still present in the system to provide for backwards
compatibility with older plugins. Earlier versions of Maven used
to provide a mechanism to load a plugin's dependencies in an
isolated ClassLoader. Maven makes extensive
use of a project called ClassWorlds from
the Codehaus
community to create hierarchies of
ClassLoader objects which are modeled by a
ClassRealm object. Feel free to ignore this
property and always set it to false.
If inheritedByDefault is set to true, any mojo in this plugin which is configured in a parent project will be configured in a child project. If you configure a mojo to execute during a specific phase in a parent project and the Plugin has inheritedByDefault set to true, this execution will be inherited by the child project. If inheritedByDefault is not set to true, then an goal execution defined in a parent project will not be inherited by a child project.
Next is the declaration of the each Mojo. The plugin element contains an element named mojos which contains a mojo element for each mojo present in the Plugin. Each mojo element contains the following configuration elements:
This is the name of the goal. If you were running the
compiler:compile goal, then
compiler is the plugin's
goalPrefix and compile would
be the name of the goal.
This contains a short description of the goal to display to the use when they use the Help plugin to generate plugin documentation.
If you set this to true, the goal can
only be executed if it is explicitly executed from the
command-line by the user. If someone tries to bind this goal to a
lifecycle phase in a POM, Maven will print an
error message. The default for this element is
false.
Specifies that a given goal cannot be executed outside of a
project. The goal requires a project with a
POM. The default value for this
requiresProject is
true.
If you were creating a plugin that relies on the presence of
reports, you would need to set requiresReports
to true. For example, if you were writing a
plugin to aggregate information from a number of reports, you
would set requiresReports to
true. The default for this element is
false.
A Mojo descriptor with aggregator set to
true is supposed to only run once during the
execution of Maven, it was created to give plugin developers the
ability to summarize the output of a series of builds; for
example, to create a plugin that summarizes a report across all
projects included in a build. A goal with
aggregator set to true
should only be run against the top-level project in a Maven build.
The default value of aggregator is
false. Aggregator is slated for deprecation in
a future release of Maven.
Specifies that a given goal cannot be executed if Maven is
running in offline mode (-o command-line
option). If a goal depends on a network resource, you would
specify a value of true for this element and
Maven would print an error if the goal was executed in offline
mode. The default for requiresOnline is
false.
If inheritedByDefault is set to
true, a mojo which is configured in a parent
project will be configured in a child project. If you configure a
mojo to execute during a specific phase in a parent project and
the Mojo descriptor has inheritedByDefault set
to true, this execution will be inherited by
the child project. If inheritedByDefault is not
set to true, then a goal execution defined in a
parent project will not be inherited by a child project.
If you don't bind this goal to a specific phase, this element defines the default phase for this mojo. If you do not specify a phase element, Maven will require the user to explicitly specify a phase in a POM.
This element tells Maven which class to instantiate for this
Mojo. This is a Plexus component property (defined in Plexus
ComponentDescriptor).
The default language for a Maven Mojo is
java. This controls the Plexus
ComponentFactory used to create instances
of this Mojo component. This chapter focuses on writing Maven
plugins in Java, but you can also write Maven in a number of
alternative languages such as Groovy, Beanshell, and Ruby. If you
were writing a plugin in one of these languages you would use a
language element value other than java.
This property is a Plexus component configuration property,
it tells Plexus how to create and manage instances of the
component. In Maven, all mojos are going to be configured with an
instantiationStrategy of
per-lookup; a new instance of the component
(mojo) is created every time it is retrieved from Plexus.
The execution strategy tells Maven when and how to execute a
Mojo. The valid values are once-per-session and
always. Honestly, the valid values are
anything, this particular property doesn't do a thing, it is a
hold over from an early design of Maven. This property is slated
for deprecation in a future release of Maven.
This element describes all of the parameters for this Mojo. What's the name of the parameter What is the type of parameter? Is it required? Each parameter has the following elements:
Is the name of the parameter (i.e.
baseDirectory)
This is the type (Java class) of the parameters (i.e.
java.io.File)
Is the parameter required? If true,
the parameter must be non-null when the goal is
executed.
If a parameter is not editable (if
editable is set to
false), then the value of the parameter
cannot be set in the POM. For example, if
the plugin descriptor defines the value of
buildDirectory to be
${basedir} in the descriptor, a
POM cannot override this value to be
another value in a POM.
A short description to use when generating plugin documentation (using the Help Plugin)
This element provides default values for all of the Mojo's
parameters using Maven property notation. This example provides a
default value for the baseDir Mojo parameter
and the buildDirectory Mojo parameter. In the
parameter element, the implementation specifies the type of the
parameter (in this case java.io.File), the
value in the parameter element contains either a hard-coded
default or a Maven property reference.
This is where the descriptor gets interesting. A Mojo is a component that is managed by Plexus, and, because of this, it has the opportunity to reference other components managed by Plexus. This element allows you to define dependencies on other components in Plexus.
While you should know how to read a Plugin Descriptor, you will almost never need to write one of these descriptor files by hand. Plugin Descriptor files are generated automatically off of a set of annotations in the source for a Mojo.
Lastly, the plugin descriptor declares a set of dependencies just like a Maven project. When Maven uses a plugin, it will download any required dependencies before it attempts to execute a goal from this plugin. In this example, the plugin depends on Jakarta Commons IO version 1.3.2.
When you write a custom plugin, you are going to be writing a series of Mojos (goals). Every Mojo is a single Java class which contains a series of annotations that tell Maven how to generate the Plugin descriptor described in the previous section. Before you can start writing Mojo classes, you will need to create Maven project with the appropriate packaging and POM.
To create a plugin project, you should use the Maven Archetype
plugin. The following command-line will create a plugin with a
groupId of
com.sonatype.maven.plugins and the
artifactId of
first-maven-plugin:
$ mvn archetype:create \
-DgroupId=com.sonatype.maven.plugins \
-DartifactId=first-maven-plugin \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-mojoThe Archetype plugin is going to create a directory named my-first-plugin which contains the following POM.
Example 17.2. A Plugin Project's POM
<?xml version="1.0" encoding="UTF-8"?><project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.sonatype.maven.plugins</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>first-maven-plugin Maven Mojo</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>The most import element in a plugin project's
POM is the packaging element which has a value of
maven-plugin. This packaging element customizes the
Maven lifecycle to include the necessary goals to create a plugin
descriptor. The plugin lifecycle was introduce in Section 10.2.3, “Maven Plugin”, it is similar to the Jar lifecycle
with three exceptions: plugin:descriptor is bound to
the generate-resources phase,
plugin:addPluginArtifactMetadata is added to the
package phase, and
plugin:updateRegistry is added to the
install phase.
The other important piece of a plugin project's
POM is the dependency on the Maven Plugin API. This
project depends on version 2.0 of the
maven-plugin-api and it also adds in JUnit as a
test-scoped dependency.
In this chapter, we're going to introduce a Maven Mojo written in
Java. Each Mojo in your project is going to implement the
org.apache.maven.plugin.Mojo interface, the
Mojo implementation shown in the following
example implements the Mojo interface by extending the
org.apache.maven.plugin.AbstractMojo class.
Before we dive into the code for this Mojo, let's take some time to
explore the methods on the Mojo interface. Mojo provides the following
methods:
void setLog( org.apache.maven.monitor.logging.Log
log )Every Mojo implementation has to
provide a way for the plugin to communicate the progress of a
particular goal. Did the goal succeed? Or, was there a problem
during goal execution? When Maven loads and executes a Mojo, it is
going to call the setLog() method and
supply the Mojo instance with a suitable logging destination to be
used in your custom plugin.
protected Log getLog()Maven is going to call setLog()
before your Mojo is executed, and your
Mojo can retrieve the logging object by
calling getLog(). Instead of printing out
status to Standard Output or the console, your
Mojo is going to invoke methods on the
Log object.
void execute() throws
org.apache.maven.plugin.MojoExecutionExceptionThis method is called by Maven when it is time to execute your goal.
The Mojo interface is concerned with two
things: logging the results of goal execution and executing a goal. When
you are writing a custom plugin, you'll be extending
AbstractMojo. AbstractMojo
takes care of handling the setLog() and
getLog() implementations and contains an abstract
execute() method. When you extend
AbstractMojo, all you need to do is implement the
execute() method. Example 17.3, “A Simple EchoMojo” shows a trivial
Mojo implement which simply prints out a message
to the console.
Example 17.3. A Simple EchoMojo
package com.sonatype.maven.plugins;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
/**
* Echos an object string to the output screen.
* @goal echo
*/
public class EchoMojo extends AbstractMojo
{
/**
* Any Object to print out.
* @parameter expression="${echo.message}" default-value="Hello World..."
*/
private Object message;
public void execute()
throws MojoExecutionException, MojoFailureException
{
getLog().info( message.toString() );
}
}
If you create this Mojo in ${basedir} under
src/main/java in
com/sonatype/maven/mojo/EchoMojo.java in the
project created in the previous section and run mvn
install, you should be able to invoke this goal directly from
the command-line with:
$ mvn com.sonatype.maven.plugins:first-maven-plugin:1.0-SNAPSHOT:echoThat large command-line is mvn followed by the
groupId:artifactId:version:goal. When you run this
command-line you should see output that contains the output of the echo
goal with the default message: "Hello Maven World...". If you want to
customize the message, you can pass the value of the message parameter
with the following command-line:
$ mvn com.sonatype.maven.plugins:first-maven-plugin:1.0-SNAPSHOT:echo \
-Decho.message="The Eagle has Landed"The previous command-line is going to execute the
EchoMojo and print out the message "The Eagle has
Landed".
Specifying the groupId,
artifactId, version, and
goal on the command-line is cumbersome. To address
this, Maven assigns a plugin a prefix. Instead of typing:
$ mvn org.apache.maven.plugins:maven-jar-plugin:2.2:jar
You can use the plugin prefix jar and turn that
command-line into mvn jar:jar. How does Maven resolve
something like jar:jar to
org.apache.mven.plugins:maven-jar:2.3? Maven looks at
a file in the Maven repository to obtain a list of plugins for a
specific groupId. By default, Maven is configured to
look for plugins in two groups:
org.apache.maven.plugins and
org.codehaus.mojo. When you specify a new plugin
prefix like mvn hibernate3:hbm2ddl, Maven is going to
scan the repository metadata for the appropriate plugin prefix. First,
Maven is going to scan the org.apache.maven.plugins
group for the plugin prefix hibernate3. If it doesn't
find the plugin prefix hibernate3 in the
org.apache.maven.plugins group it will scan the
metadata for the org.codehaus.mojo group.
When Maven scans the metadata for a particular
groupId, it is retrieving an XML
file from the Maven repository which captures metadata about the
artifacts contained in a group. This XML file is
specific for each repository referenced, if you are not using a custom
Maven repository, you will be able to see the Maven metadata for the
org.apache.maven.plugins group in your local Maven
repository (~/.m2/repository) under
org/apache/maven/plugins/maven-metadata-central.xml.
Example 17.4, “Maven Metadata for the Maven Plugin Group” shows a snippet of the
maven-metadata-central.xml file from the
org.apache.maven.plugin group.
Example 17.4. Maven Metadata for the Maven Plugin Group
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<plugins>
<plugin>
<name>Maven Clean Plugin</name>
<prefix>clean</prefix>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
<plugin>
<name>Maven Compiler Plugin</name>
<prefix>compiler</prefix>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<name>Maven Surefire Plugin</name>
<prefix>surefire</prefix>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
...
</plugins>
</metadata>As you can see in Example 17.4, “Maven Metadata for the Maven Plugin Group”, this
maven-metadata-central.xml file in your local
repository is what makes it possible for your to execute mvn
surefire:test. Maven scans
org.apache.maven.plugins and
org.codehaus.mojo: plugins from
org.apache.maven.plugins are considered core Maven
plugins and plugins from org.codehaus.mojo are
considered extra plugins. The Apache Maven project manages the
org.apache.maven.plugins group, and a separate
independent open source community manages the Codehaus Mojo project. If
you would like to start publishing plugins to your own
groupId, and you would like Maven to automatically
scan your own groupId for plugin prefixes, you can
customize the groups that Maven scans for plugins in your Maven
Settings.
If you wanted to be able to run the
first-maven-plugin's echo goal by running
first:echo, add the
com.sonatype.maven.plugins groupId to your
~/.m2/settings.xml as shown in Example 17.5, “Customizing the Plugin Groups in Maven Settings”. This will prepend the
com.sonatype.maven.plugins to the list of groups
which Maven scans for Maven plugins.
Example 17.5. Customizing the Plugin Groups in Maven Settings
<settings>
...
<pluginGroups>
<pluginGroup>com.sonatype.maven.plugins</pluginGroupd>
</pluginGroups>
</settings>You can now run mvn first:echo from any
directory and see that Maven will properly resolve the goal prefix to
the appropriate plugin identifiers. This worked because our project
adhered to a naming convention for Maven plugins. If your plugin project
has an artifactId which follows the pattern
maven-first-plugin or
first-maven-plugin. Maven will automatically assign a
plugin goal prefix of first to your plugin. In other
words, when the Maven Plugin Plugin is generating the Plugin descriptor
for your plugin and you have not explicitly set the
goalPrefix in your project, the
plugin:descriptor goal will extract the prefix from
your plugin's artifactId when it matches the
following patterns:
${prefix}-maven-plugin, OR
maven-${prefix}-plugin
If you would like to set an explicit plugin prefix, you'll need to
configure the Maven Plugin Plugin. The Maven Plugin Plugin is a plugin
that is responsible for building the Plugin descriptor and performing
plugin specific tasks during the package and load phases. The Maven
Plugin Plugin can be configured just like any other plugin in the build
element. To set the plugin prefix for your plugin, add the following
build element to the first-maven-plugin project's
pom.xml.
Example 17.6. Configuring a Plugin Prefix
<?xml version="1.0" encoding="UTF-8"?><project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.sonatype.maven.plugins</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>first-maven-plugin Maven Mojo</name>
<url>http://maven.apache.org</url>
<build>
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>2.3</version>
<configuration>
<goalPrefix>blah</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>Example 17.6, “Configuring a Plugin Prefix” sets the plugin prefix to
blah. If you've added the
com.sonatype.maven.plugins to the
pluginGroups in your
~/.m2/settings.xml, you should be able to execute
the EchoMojo by running mvn
echo:blah from any directory.
Maven takes care of connecting your Mojo to a logging provider by
calling setLog() prior to the execution of your
Mojo. It supplies an implementation of
org.apache.maven.monitor.logging.Log. This class
exposes methods that you can use to communicate information back to the
user. This Log class provides multiple levels of
logging similar to that API provided by Log4J. Those levels are
captured by a series of methods available for each level: debug, info,
error and warn. To save trees, we've only listed the methods for a
single logging level: debug.
void debug( CharSequence message
)Prints a message to the debug logging level.
void debug( CharSequence message, Throwable t
)Prints a message to the debug logging level which includes
the stack trace from the Throwable (either
Exception or
Error)
void debug( Throwable t )Prints out the stack trace of the
Throwable (either
Exception or
Error)
Each of the four levels exposes the same three methods. The four logging levels serve different purposes. The debug level exists for debugging purposes and for people who want to see a very detailed picture of the execution of a Mojo. You should use the debug logging level to provide as much detail on the execution of a Mojo, but you should never assume that a user is going to see the debug level. The info level is for general informational messages that should be printed as a normal course of operation. If you were building a plugin that compiled code using a compiler, you might want to print the output of the compiler to the screen.
The warn logging level is used for messages about unexpected events and errors that your Mojo can cope with. If you were trying to run a plugin that compiled Ruby source code, and there was no Ruby source code available, you might want to just print a warning message and move on Warnings are not fatal, but errors are usually build-stopping conditions. For the completely unexpected error condition, there is the error logging level. You would use error if you couldn't continue executing a Mojo. If you were writing a Mojo to compile some Java code and the compiler wasn't available, you'd print a message to the error level and possibly pass along an Exception that Maven could print out for the user. You should assume that a user is going to see most of the messages in info and all of the messages in error.
In first-maven-plugin, you didn't write the
plugin descriptor yourself, you relied on Maven to generate the plugin
descriptor from your source code. The descriptor was generated using
your plugin project's POM information and a set of
annotations on your EchoMojo class.
EchoMojo only specifies the
@goal annotation, here is a list of other
annotations you can place on your Mojo
implementation.
This is the only required annotation which gives a name to this goal unique to this plugin.
Flags this mojo as requiring the dependencies in the
specified scope (or an implied scope) to be resolved before it
can execute. Supports compile, runtime, and test. If this
annotation had a value of test, it would tell
Maven that the Mojo cannot be executed until the dependencies in
the test scope had been resolved.
Marks that this goal must be run inside of a project,
default is true. This is opposed to plugins
like archetypes, which do not.
If you were creating a plugin that relies on the presence
of reports, you would need to set
requiresReports to true.
The default value of this annotation is false.
A Mojo with aggregator set to
true is supposed to only run once during the
execution of Maven, it was created to give plugin developers the
ability to summarize the output of a series of builds; for
example, to create a plugin that summarizes a report across all
projects included in a build. A goal with
aggregator set to true
should only be run against the top-level project in a Maven
build. The default value of aggregator is
false.
When set to true, Maven must not be
running in offline mode when this goal is executed. Maven will
throw an error if one attempts to execute this goal offline.
Default: false.
When set to true, the goal can only be
executed if it is explicitly executed from the command-line by
the user. Maven will throw an error if someone tries to bind
this goal to a lifecycle phase. The default for this annotation
is false.
This annotation specifies the default phase for this goal.
If you add an execution for this goal to a
pom.xml and do not specify the phase, Maven
will bind the goal to the phase specified in this annotation by
default.
This annotation can be used in a number of ways. If a
phase is supplied, Maven will execute a parallel lifecycle
ending in the specified phase. The results of this separate
execution will be made available in the Maven property
${executedProperty}.
The second way of using this annotation is to specify an
explicit goal using the prefix:goal notation.
When you specify just a goal, Maven will execute this goal in a
parallel environment that will not affect the current Maven
build.
The third way of using this annotation would be to specify a phase in an alternate lifecycle using the identifier of a lifecycle.
@execute phase="package" lifecycle="zip" @execute phase="compile" @execute goal="zip:zip"
If you look at the source for EchoMojo,
you'll notice that Maven is not using the standard annotations available
in Java 5. Instead, it is using Commons Attributes.
Commons Attributes provided a way for Java programmers to use
annotations before annotations were a part of the Java language
specification. Why doesn't Maven use Java 5 annotations? Maven doesn't
use Java 5 annotations because it is designed to target pre-Java 5
JVMs. Because Maven has to support older versions of
Java, it cannot use any of the newer features available in Java
5.
The execute() method in Mojo throws two
exceptions MojoExecutionException and
MojoFailureException. The difference between
these two exception is both subtle and important, and it relates to what
happens when a goal execution "fails". A
MojoExecutionException is a fatal exception,
something unrecoverable happened. You would throw a
MojoExecutionException if something happens that
warrants a complete stop in a build; you re trying to write to disk, but
there is no space left, or you were trying to publish to a remote
repository, but you can't connect to it. Throw a
MojoExecutionException if there is no chance of a
build continuing; something terrible has happened and you want the build
to stop and the user to see a "BUILD ERROR" message.
A MojoFailureException is something less
catastrophic, a goal can fail, but it might not be the end of the world
for your Maven build. A unit test can fail, or a MD5
checksum can fail; both of these are potential problems, but you don't
want to return an exception that is going to kill the entire build. In
this situation you would throw a
MojoFailureException. Maven provides for
different "resiliency" settings when it comes to project failure. Which
are described below.
When you run a Maven build, it could involve a series of projects each of which can succeed or fail. You have the option of running Maven in three failure modes:
Fail-fast mode: Maven will fail (stop) at the first build failure.
Fail-at-end: Maven will fail at the end of the build. If a project in the Maven reactor fails, Maven will continue to build the rest of the builds and report a failure at the end of the build.
Fail never: Maven won't stop for a failure and it won't report a failure.
You might want to ignore failure if you are running a continuous
integration build and you want to attempt a build regardless of the
success of failure of an individual project build. As a plugin
developer, you'll have to make a call as to whether a particular failure
condition is a MojoExecutionException or a
MojoFailureExeception.
Just as important as the execute() method
and the Mojo annotations, a Mojo is configured via parameters. This
section deals with some configuration and topics surrounding Mojo
parameters.
In EchoMojo we declared the message parameter with the following annotations:
/**
* Any Object to print out.
* @parameter
* expression="${echo.message}"
* default-value="Hello Maven World"
*/
private Object message;
The default expression for this parameter is
${echo.message}, this means that Maven will try to
use the value of the echo.message property to set the
value for message. If the value of the echo.message
property is null, the default-value attribute of the
@parameter annotation will be used instead.
Instead of using the echo.message property, we can
configure a value for the message parameter of the EchoMojo directly in
a project's POM.
There are a few ways to populate the message parameter in the
EchoMojo. First we can pass in a value from the
command-line like this (assuming that you've added
com.sonatype.maven.plugins to your
pluginGroups):
$ mvn first:echo -Decho.message="Hello Everybody"
We could also specify the value of this message parameter, by
setting a property in our POM or in our
settings.xml.
<project>
...
<properties>
<echo.message>Hello Everybody</echo.message>
</properties>
</project>
This parameter could also be configured directly as a configuration value for the plugin. If we wanted to customize the message parameter directly, we could use the following build configuration. The following configuration bypasses the echo.message property and populates the Mojo parameter in plugin configuration.
<project>
...
<build>
<plugins>
<plugin>
<groupId>com.sonatype.maven.plugins</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<message>Hello Everybody!</message>
</configuration>
</plugin>
</plugins>
</build>
</project>If we wanted to run the EchoMojo twice at
difference phases in a lifecycle, and we wanted to customize the message
parameter for each execution separately, we could configure the
parameter value at the execution level in a POM like
this:
<build>
<build>
<plugins>
<plugin>
<groupId>com.sonatype.maven.plugins</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<executions>
<execution>
<id>first-execution</id>
<phase>generate-resources</phase>
<goals>
<goal>echo</goal>
</goals>
<configuration>
<message>The Eagle has Landed!</message>
</configuration>
</execution>
<execution>
<id>second-execution</id>
<