Although this chapter covers an advanced topic, don’t let the idea of writing a Maven plugin intimidate you. 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, though you haven’t yet seen many code examples dealing with Maven customization. In fact, you haven’t seen any. This is by design, since 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. And a very small 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 needed to customize the behavior of Maven, you would then 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 will have to learn a bit about the internals of Maven: How does it manage software components? What does a plugin do? How can I customize the lifecycle? This section answers some of those questions and 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 begin understanding the code behind Maven, you’ve found the proper starting place.
At the heart of Maven is an Inversion of Control (IoC) container called Plexus. What does it do? It is a system for managing and relating components. Although Martin Fowler wrote a canonical essay about IoC, the concept and term have been so heavily overloaded in the past few years that 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 (DI) with an analogy.
Suppose you have a series of components that 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 an Apple TV box as well as a 50” flat panel LCD TV. You bring everything home from the electronics store, and you purchase a series of cables that you will use to connect it all. You unpack all of these components, put them in their right places, and then get to the job of hooking up 50,000 coaxial cables and stereo jacks to 50,000 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 using an IoC container.
So, what does 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 Java
bean has three properties: power
,
network
, and tvOutput
. When
you open the box of your PlayStation 3, it doesn’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 such as Plexus, you
are responsible for declaring the relationships between a set of
components that 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.
Although it sounds cheesy, when you start up Maven, it starts Plexus
and manages a system of related components just like your stereo
system does.
What are the advantages of using an IoC container? Well, 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 can focus on building individual components. IoC containers and DI encourage disaggregation and the emergence of standards. The software industry likes to imagine itself as the font of all new ideas, but DI and IoC are really just new terms 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 the mechanism of 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 power of an IoC framework. Using DI in an application that has been programmed to interfaces, you can create components that 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, and 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, several methods exist for injecting dependencies values into a component object: constructor, setter, or field injections. Although Plexus is capable of all three Dependency Injection techniques, Maven uses only 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
that had the constructor
Person(String name, Job job)
, you
could pass in values for both name
and
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 that 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 happens 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 such as Sun Microsystems to yield more control to the open source community and helping 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 (such as PicoContainer; see http://www.picocontainer.org/).
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 had invented it, so they decided to use it as an IoC container. Although 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 will 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). We get the question “Why didn’t you use Spring?” often enough that it makes sense for us to address it here. We know—Spring is a rock star, we don’t deny it, but it is on our continual 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 that 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.
Mojo? What is a Mojo? The word mojo 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.”[5] 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 configurations. 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, and 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 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 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 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 that is capable of producing a distribution archive in multiple formats. Read through the plugin descriptor in this example to get an idea of the content it contains.
<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>
A plugin descriptor has three parts: the top-level configuration
of the plugin that contains elements such as
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:
description
This element contains a short description of the plugin. In the case of the Zip plugin, this description is empty.
groupId
, artifactId
, version
Just like everything else in Maven, a plugin needs to
have a unique coordinate. The groupId
, artifactId
, and version
are used to locate the
plugin artifact in a Maven repository.
goalPrefix
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.
isolatedRealm
(deprecated)This is a legacy property that is no longer used by
Maven. It is still present in the system to provide for
backward compatibility with older plugins. Earlier versions of
Maven provided a mechanism to load a plugin’s dependencies in
an isolated ClassLoader
. Maven makes
extensive use of a project called ClassWorlds (http://classworlds.codehaus.org/)
from the Codehaus community to create hierarchies of
ClassLoader
objects that are modeled by
a ClassRealm
object. Feel free to
ignore this property and always set it to
false
.
inheritedByDefault
If inheritedByDefault
is set to true
, any
Mojo in this plugin that 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
, a goal execution
defined in a parent project will not be inherited by a child
project.
Next is the declaration of each Mojo. The plugin
element contains an element named mojos
that contains a mojo
element for each Mojo present in the
plugin. Each mojo
element
contains the following configuration elements:
goal
This is the name of the goal. If you were running
the compiler:compile
goal, then
compiler
would be the plugin’s
goalPrefix
and compile
would be the name of the goal.
description
This contains a short description of the goal to display to the users when they use the Help plugin to generate plugin documentation.
requiresDirectInvocation
If you set this to true
, the goal can
be executed only 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
.
requiresProject
This 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
.
requiresReports
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
.
aggregator
A Mojo descriptor with aggregator
set
to true
is supposed to run only
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 be run against only 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.
requiresOnline
This specifies that a given goal cannot be executed if
Maven is running in offline mode (e.g., the -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 were executed in offline mode. The
default for requiresOnline
is
false
.
inheritedByDefault
If inheritedByDefault
is set to
true
, a Mojo that 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.
phase
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.
implementation
This element tells Maven which class to instantiate for
this Mojo. This is a Plexus component property
(defined in Plexus
ComponentDescriptor
).
language
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
.
instantiationStrategy
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.
executionStrategy
The execution strategy tells Maven when and how to
execute a Mojo. The valid values are
once-per-session
and
always
. In truth, the valid values can be
anything. This particular property doesn’t do a thing; it is a
holdover from an early design of Maven. This property is
slated for deprecation in a future release of Maven.
parameters
This element describes all of the parameters for this Mojo. What is the name of the parameter? What is the type of parameter? Is it required? Each parameter has the following elements:
name
type
This is the type (Java class) of the parameters
(i.e.,
java.io.File
).
required
Is the parameter required? If true
, the parameter
must be nonnull when the goal is executed.
editable
If a parameter is not editable (if editable
is set to
false
), 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.
description
This is a short description to use when generating plugin documentation (using the Help plugin).
configuration
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 hardcoded default or a Maven property
reference.
requirements
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.
Although 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 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 that 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 a 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
org.sonatype.mavenbook.plugins
and an
artifactId
of
first-maven-plugin
:
$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.plugins -DartifactId=first-maven-plugin -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo
The Archetype plugin will create a directory named my-first-plugin, which contains the POM shown in Example 17-2.
<?xml version="1.0" encoding="UTF-8"?><project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.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 important element in a plugin project’s
POM is the packaging
element that 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 introduced in the section Maven Plugin” in Chapter 10.
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 will introduce a Maven Mojo written in
Java. Each Mojo in the project will implement the
org.apache.maven.plugin.Mojo
interface.
The Mojo
implementation shown in
the upcoming 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 will 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 will 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.MojoExecutionException
This 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 shows a trivial
Mojo
implement that simply prints out a
message to the console.
package org.sonatype.mavenbook.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 * @requiresProject false */ public class EchoMojo extends AbstractMojo { /** * Any Object to print out. * @parameter expression="${echo.message}" default-value="Hello Maven 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 org/sonatype/mavenbook/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 org.sonatype.mavenbook.plugins:first-maven-plugin:1.0-SNAPSHOT:echo
That large command is mvn
followed by the
groupId
:
artifactId
:
version
:
goal
.
When you run this command, 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:
$ mvn org.sonatype.mavenbook.plugins:first-maven-plugin:1.0-SNAPSHOT:echo -Decho.message="The Eagle has Landed"
This command will 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
the command 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 such as mvn
hibernate3:hbm2ddl, Maven will scan the repository
metadata for the appropriate plugin prefix. First, Maven will 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 that 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 shows a snippet of the
maven-metadata-central.xml file
from the org.apache.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 this example, this maven-metadata-central.xml file in your
local repository is what makes it possible for you 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 want to be able to run the
first-maven-plugin
’s echo goal by running
first:echo
, add the
org.sonatype.mavenbook.plugins
groupId
to your ~/.m2/settings.xml, as shown in Example 17-5. This will prepend the
org.sonatype.mavenbook.plugins
to the list of
groups that Maven scans for Maven plugins.
<settings> ... <pluginGroups> <pluginGroup>org.sonatype.mavenbook.plugins</pluginGroup> </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 works because the project adheres to a naming
convention for Maven plugins. If your plugin project has an
artifactId
that 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 one of the following patterns:
${prefix}-maven-plugin
maven-${prefix}-plugin
If you would like to set an explicit plugin prefix, you’ll
need to configure the Maven Plugin plugin. This is plugin 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 build
element shown in Example 17-6 to the
first-maven-plugin
project’s pom.xml.
<?xml version="1.0" encoding="UTF-8"?><project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.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>
This example sets the plugin prefix to
blah
. If you’ve added the
org.sonatype.mavenbook.
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 will list only 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 that
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’re building a plugin that compiles 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
are trying to run a plugin that compiles Ruby source code and there
is 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 are writing a Mojo to compile some Java
code and the compiler isn’t available, you’d print a message to the
error
level and possibly pass
along an exception that Maven can 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 the first-maven-plugin
example, 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
specifies only the
@goal
annotation. Here is a list of other
annotations you can place on your Mojo
implementation:
@goal
<
goalName
>
The only required annotation that gives a name to this goal that is unique to this plugin.
@requiresDependencyResolution
<
requireScope
>
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.
@requiresProject
(true|false)
Marks that this goal must be run inside of a project.
The default is true
. This is opposed to plugins
such as archetypes that do not.
@requiresReports
(true|false)
If you were creating a plugin that relied on the
presence of reports, you would need to set
requiresReports
to true
.
The default value of this annotation is
false
.
@aggregator
(true|false)
A Mojo with aggregator
set
to true
is supposed to run only
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 be run against only the
top-level project in a Maven build. The default value of
aggregator
is
false
.
@requiresOnline
(true|false)
When set to true
, Maven must not be
running in offline mode when this goal is
executed. Maven will throw an error if you attempt to execute
this goal offline. The default is
false
.
@requiresDirectInvocation
When set to true
, the goal can be
executed only 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
.
@phase
<
phaseName
>
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.
@execute
[goal=
goalName
|phase=
phaseName
[lifecycle=
lifecycleId
]]
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 only 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 is 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? Because Maven 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 exceptions is both subtle and
important, and it relates to what happens when a goal execution
“fails.” A MojoExecutionException
is a fatal
exception: it means something unrecoverable happened. You will throw
a MojoExecutionException
if something happens
that warrants a complete stop in a build: you are trying to write to
disk, but there is no space left, or you are 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: that is, 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 an
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. These are described next.
When you run a Maven build, it can involve a series of projects, each of which can succeed or fail. You have the option of running Maven in three failure modes:
mvn -ff
Fail-fast mode: Maven will fail (stop) at the first build failure.
mvn -fae
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.
mvn -fn
Fail never: Maven won’t stop for a failure and won’t report a failure.
You might want to ignore failures if you are running a
continuous integration build and you want to attempt a build,
regardless of the success or 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 is the fact that a Mojo is
configured via parameters. This section deals with some configuration
and topics surrounding Mojo parameters.
In EchoMojo, we declare 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.
We can populate the message
parameter in the EchoMojo
in a few ways.
First, we can pass in a value from the command line like this
(assuming that you’ve added org.sonatype.mavenbook.plugins
to
your pluginGroups
):
$ mvn first:echo -Decho.message="Hello Everybody"
We can 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 can 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, which bypasses the echo.message
property and populates the
Mojo parameter in plugin configuration:
<project> ... <build> <plugins> <plugin> <groupId>org.sonatype.mavenbook.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 if 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>org.sonatype.mavenbook.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> <phase>validate</phase> <goals> <goal>echo</goal> </goals> <configuration> <message>${project.version}</message> </configuration> </execution> </executions> </plugin> </plugins> </build> </build>
Although this last configuration example seems very verbose,
it illustrates the flexibility of Maven. In the previous
configuration example, you bound the EchoMojo
to both the validate
and
generate-resources
phases in the default Maven
lifecycle. The first execution is bound to
generate-resources
; it supplies a string value to
the message
parameter of “The
Eagle has Landed!”. The second execution is bound to the
validate
phase; it supplies a property reference
to ${project.version}
. When you run mvn install for this project, you’ll see
that the first:echo
goal executes twice and
prints out two different messages.
Plugins can have parameters that accept more than one value.
Take a look at the ZipMojo
shown in
Example 17-7. Both the
includes
and excludes
parameters are multivalued String
arrays that specify the inclusion and exclusion patterns for a
component that creates a ZIP file.
package org.sonatype.mavenbook.plugins /** * Zips up the output directory. * @goal zip * @phase package */ public class ZipMojo extends AbstractMojo { /** * The Zip archiver. * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#zip}" */ private ZipArchiver zipArchiver; /** * Directory containing the build files. * @parameter expression="${project.build.directory}" */ private File buildDirectory; /** * Base directory of the project. * @parameter expression="${basedir}" */ private File baseDirectory; /** * A set of file patterns to include in the zip. * @parameter alias="includes" */ private String[] mIncludes; /** * A set of file patterns to exclude from the zip. * @parameter alias="excludes" */ private String[] mExcludes; public void setExcludes( String[] excludes ) { mExcludes = excludes; } public void setIncludes( String[] includes ) { mIncludes = includes; } public void execute() throws MojoExecutionException { try { zipArchiver.addDirectory( buildDirectory, includes, excludes ); zipArchiver.setDestFile( new File( baseDirectory, "output.zip" ) ); zipArchiver.createArchive(); } catch( Exception e ) { throw new MojoExecutionException( "Could not zip", e ); } } }
To configure a multivalued Mojo parameter, you use a series of
elements for each value. If the name of the multivalued parameter is
includes
, you would use an element
includes
with child elements
include
. If the multivalued parameter is
excludes
, you would use an element
excludes
with child elements
exclude
. To configure the ZipMojo
to ignore all files ending
in .txt and all files ending in
a tilde, you would use the following plugin
configuration:
<project> ... <build> <plugins> <plugin> <groupId>org.sonatype.mavenbook.plugins</groupId> <pluginId>zip-maven-plugin</pluginId> <configuration> <excludes> <exclude>**/*.txt</exclude> <exclude>**/*~</exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
A Mojo is a component managed by an IoC
container called Plexus. A Mojo can depend on other
components managed by Plexus by declaring a Mojo parameter and using
the @parameter
or the
@component
annotation. Example 17-8 shows a
ZipMojo
that depends on a Plexus component
using the @parameter
annotation. This
dependency could be declared using the
@component
annotation.
/** * The Zip archiver. * @component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip" */ private ZipArchiver zipArchiver;
When Maven instantiates this Mojo, it will then attempt to
retrieve the Plexus component with the specified role and role hint.
In this example, the Mojo will be related to a
ZipArchiver
component that will allow the
ZipMojo
to create a ZIP
file.
Unless you insist on writing your plugin descriptors by hand,
you’ll never have to write that XML. Instead, the
Maven Plugin plugin has a plugin:descriptor
goal
bound to the generate-resources
phase. This goal
generates the plugin descriptor from annotations on your Mojo. To configure
a Mojo parameter, you should use the following annotations on the
private member variables for each of your Mojo’s parameters. You can
also use these annotations on public setter methods, but the most
common convention for Maven plugins is to annotate private member
variables directly:
@parameter
[alias=”
someAlias
“]
[expression="${
someExpression
}"]
[default-value=”
value
“]
Marks a private field (or a setter method) as a
parameter. The alias
provides the name
of the parameter. If alias
is omitted,
Maven will use the name of the variable as the parameter name.
The expression
is an expression that Maven
will evaluate to obtain a value. Usually the expression is a
property reference such as ${echo.message}
.
default-value
is the value that this Mojo
will use if no value can be derived from the expression or if
a value was not explicitly supplied via plugin configuration
in a POM.
@required
If this annotation is present, a valid value for this parameter is required prior to Mojo execution. If Maven tries to execute this Mojo and the parameter has a null value, Maven will throw an error when it tries to execute this goal.
@readonly
If this annotation is present, the user cannot directly
configure this parameter in the
POM. You would use this annotation with the
expression
attribute of the
parameter annotation. For example, if you wanted to make sure
that a particular parameter always had the value of the
finalName
POM property,
you would list an expression of
${build.finalName}
and then add the
@readOnly
annotation. If this were the
case, the user could change the value of this parameter only
by changing the value of finalName
in the
POM.
@component
Tells Maven to populate a field with a Plexus component.
A valid value for the
@component
annotation would be:
@component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip"
This would have the effect of retrieving the
ZipArchiver
from Plexus. The ZipArchiver
is the archiver
that corresponds to the role hint zip
.
Instead of component
, you could also use
the @parameter
annotation with an
expression
attribute
of:
@parameter expression="${component.org.codehaus.plexus.archiver.Archiver#zip}"
Although the two annotations are effectively the same,
the @component
annotation is the
preferred way to configure dependencies on Plexus
components.
@deprecated
The parameter will be deprecated. Users can continue configuring this parameter, but a warning message will be displayed.
In Chapter 10, you learned that lifecycles can be customized by packaging types. A plugin can both introduce a new packaging type and customize the lifecycle. In this section, you will learn how you can customize the lifecycle from a custom Maven plugin. You will also see how you can tell a Mojo to execute a parallel lifecycle.
Let’s assume you write some goal that depends on the output
from a previous build. Maybe the
ZipMojo
goal can run only if there is output
to include in an archive. You can specify something like a
prerequisite goal by using the @execute
annotation on a Mojo class. This annotation will cause Maven to
spawn a parallel build and execute a goal or a lifecycle in a
parallel instance of Maven that isn’t going to affect the current
build. Maybe you wrote some Mojo that you can run once a day that
runs mvn install and then
packages up all of the output in some sort of customized
distribution format. Your Mojo descriptor could tell Maven that
before you execute your CustomMojo
, you’d
like it to execute the default lifecycle up to the
install
phase and then expose the results of that
project to your Mojo as the property
${executedProject}
. You could then reference
properties in that project before some sort of
postprocessing.
Another possibility is that you have a goal that does
something completely unrelated to the default lifecycle. Let’s
consider something completely unexpected. Maybe you have a goal that
turns a WAV file into an MP3
using something like LAME, but before you do
that, you want to step through a lifecycle that turns a
MIDI file to a WAV. (You can
use Maven for anything; this example isn’t that “far out.”) You’ve
created a midi-sound
lifecycle, and you want to
include the output of the midi-sound
lifecycle’s
install
phase in your web application project,
which has a war
packaging type. Since your
project is running in the war
packaging
lifecycle, you’ll need to have a goal that effectively forks off an
isolated build and runs through the midi-source
lifecycle. You would do this by annotating your mojo with
@execute lifecycle="midi-source"
phase="install"
:
@execute
goal="<
goal
>”
This will execute the given goal before the execution of
this one. The goal name is specified using the
prefix:goal
notation.
@execute
phase="<
phase
>”
This will fork an alternate build lifecycle up to the specified phase before continuing to execute the current one. If no lifecycle is specified, Maven will use the lifecycle of the current build.
@execute
lifecycle="<
lifecycle
>”
phase="<
phase
>”
This will execute the given alternate lifecycle. A custom lifecycle can be defined in META-INF/maven/lifecycle.xml.
A custom lifecycle must be packaged in the plugin under
the META-INF/maven/lifecycle.xml file. You
can include a lifecycle under src/main/resources in META-INF/maven/lifecycle.xml. The
lifecycle.xml shown in Example 17-9 declares a lifecycle named
zipcycle
that contains only
the zip
goal in a package
phase.
<lifecycles> <lifecycle> <id>zipcycle</id> <phases> <phase> <id>package</id> <executions> <execution> <goals> <goal>zip</goal> </goals> </execution> </executions> </phase> </phases> </lifecycle> </lifecycles>
If you wanted to execute the zipcycle
phase
within another build, you could then create a
ZipForkMojo
that uses the
@execute
annotation to tell Maven to step
through the zipcycle
phase to package when the
ZipForkMojo
is executed. See Example 17-10.
/** * Forks a zip lifecycle. * @goal zip-fork * @execute lifecycle="zipcycle" phase="package" */ public class ZipForkMojo extends AbstractMojo { public void execute() throws MojoExecutionException { getLog().info( "doing nothing here" ); } }
Running the ZipForkMojo
will fork the
lifecycle. If you’ve configured your plugin to execute with the goal
prefix zip
, running zip-fork
should produce something similar to the following output:
$ mvn zip:zip-fork [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'zip'. [INFO] ------------------------------------------------------------------- [INFO] Building Maven Zip Forked Lifecycle Test [INFO] task-segment: [zip:zip-fork] [INFO] ------------------------------------------------------------------- [INFO] Preparing zip:zip-fork [INFO] [site:attach-descriptor] [INFO] [zip:zip] [INFO] Building zip: ~/maven-zip-plugin/src/projects/zip-lifecycle-test/ target/output.zip [INFO] [zip:zip-fork] [INFO] doing nothing here [INFO] ------------------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------- [INFO] Total time: 1 second [INFO] Finished at: Sun Apr 29 16:10:06 CDT 2007 [INFO] Final Memory: 3M/7M [INFO] -------------------------------------------------------------------
Calling zip-fork
spawns another lifecycle.
Maven executes the zipcycle
lifecycle, and then
it prints out the message from ZipFormMojo
’s
execute method.
Once you’ve created your own lifecycle and spawned it from a
Mojo, the next question you might have is: How do you
override the default lifecycle? How do you create custom lifecycles
and then attach them to projects? In Chapter 10,
we saw that the packaging of a project defines the lifecycle of a
project. There’s something different about almost every packaging
type: war
attaches different goals to package,
custom lifecycles such as swf
from the Israfil
Flex 3 plugin attach different goals to the compile
phase. When you create a custom
lifecycle, you can attach that lifecycle to a packaging type by
supplying some Plexus configuration in your plugin’s archive.
To define a new lifecycle for a new packaging type, you’ll
need to configure a LifecycleMapping
component in
Plexus. In your plugin project, create a META-INF/plexus/components.xml under
src/main/resources. In
components.xml, add the content
from Example 17-11. Set the name of the
packaging type under role-hint
, and the set of
phases containing the coordinates of the goals to bind (omit the
version). Multiple goals can be associated with a phase using a
comma delimited list.
<component-set> <components> <component> <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role> <role-hint>zip</role-hint> <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping </implementation> <configuration> <phases> <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources </process-resources> <compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile> <package>org.sonatype.mavenbook.plugins:maven-zip-plugin:zip</package> </phases> </configuration> </component> </components> </component-set>
If you create a plugin that defines a new packaging type and a
customized lifecycle, Maven won’t know anything about it until you
add the plugin to your project’s POM and set the
extensions element to true
. Once
you do this, Maven will scan your plugin for more than just Mojos to
execute; it will look for the components.xml under META-INF/plexus, and it will make the
packaging type available to your project. See Example 17-12.
<project> ... <build> ... <plugins> <plugin> <groupId>com.training.plugins</groupId> <artifactId>maven-zip-plugin</artifactId> <extensions>true</extensions> </plugin> </plugins> </build> </project>
Once you add the plugin with the extensions
element set to true
, you can use the custom packaging
type and your project will be able to execute the custom lifecycle
associated with that packaging type.
3.138.37.20