This article talks about writing maven 2.x plugins. You can write maven plugins in Java, Groovy, Ruby and BeanShell. This article specifically talks about writing maven plugins in Java.
Maven Plugins:
Maven plugin is just a maven artifact which has plugin descriptor (xml file) and a set of mojos. Mojo is a java class and each mojo in the artifact corresponds to one goal in the plugin. So, a plugin can have any number of goals.
Creating maven plugin project:
You can create a maven plugin using the archetype plugin. Use the following command:
mvn archetype:create -DgroupId=com.mycompany -DartifactId=myfirst-maven-plugin -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo
Run this in any directory and it will create a maven plugin project (myfirst-maven-plugin) under that directory.
Take a look at pom.xml in the base folder. packaging is set to maven-plugin indicating that is a maven-plugin project and there will be two default dependencies. One is the maven plugin api and the other one is JUnit (test scope).
At this point, you can do mvn eclipse:eclipse if you want to start developing the plugin in eclipse.
Understanding plugin descriptor:
Before we look at the mojo's, let's take a look at the plugin descriptor. Do mvn install to install your plugin into your local repository. If you look at the jar artifact in your maven repository, you can find plugin.xml in META-INF\maven folder. "Maven plugin" plugin takes care of generating this during packaging.
<plugin>
<description></description>
<groupId>com.mycompany</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<goalPrefix>first</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>touch</goal>
<description>Goal which touches a timestamp file.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>process-sources</phase>
<implementation>com.mycompany.MyMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<parameters>
<parameter>
<name>outputDirectory</name>
<type>java.io.File</type>
<required>true</required>
<editable>true</editable>
<description>Location of the file.</description>
</parameter>
</parameters>
<configuration>
<outputDirectory implementation="java.io.File">${project.build.directory}</outputDirectory>
</configuration>
</mojo>
</mojos>
<dependencies/>
</plugin>
The first few are maven plugin co-ordinates just like any other maven plugin. Goal prefix is the prefix for all the goals in the plugin. For example, when you do mvn surefire:test surefire is the goal prefix.
isolatedRealm - Deprecated
inheritedByDefault - defaults to true. Value of true indicates that any binding (i.e execution of goal in certain hase) in the parent pom will be inherited by the child poms.
mojos - Under mojos, you can have any number of mojo elements each corresponding to a goal in your plugin.
goal - name of your goal. In mvn surefire:test test is the goal name.
description - goal description to display in help
requiresDirectInvocation - defaults to false. Value of true means you can only execute the goal directly but
cannot bind it to any phase in the pom.
requiresProject - defaults to true. Value of true means you can execute the goal only within a maven project.
requiresReports - defaults to false. Value of true means the goal relies on the presence of reports section.
aggregator - Most likely will be deprecated in future.
requiresOnline - default is false. Value of true means the goal cannot be executed in offline (-o) mode.
phase - default phase where this goal binds to (if the user does not specify any binding to phase then this would be phase where goal would bind to). If this is not specified, then user is required to provide the binding phase.
implementation - class name of mojo
language - default is java, but can be Groovy, Ruby or BeanShell.
instantantiationStrategy - instantiation strategy to load mojo
executionStrategy - Most likely will be deprecated in future release
parameters - This list all the parameters of the mojo.
parameter name - name of the parameter
parameter type - type of the parameter (java.lang.String, java.io.File etc.,)
parameter required - if true, the parameter must be specified to run the goal
parameter editable - if false, user cannot override parameter value in the pom (the will always come from plugin descriptor)
parameter description - parameter description to display during help
parameter configuration - provides default values for all of the parameters
dependencies - maven plugin dependencies. Maven will download these dependencies before executing this goal.
Details of plugin descriptor can be daunting. But the good news is we don't have to write the plugin descriptor by hand. Maven generates them based on the annotations in the mojos. But, plugin descriptors are good to understand anyway.
Understanding Mojo:
When you open the source, you can see "MyMojo" class present there. All mojos extend from AbstractMojo which implements Mojo interface. The interface has setLog, getLog and execute() methods. Base class implements set and get log methods and the individual mojos have to implement the execute method. Simply the mojo cares only about execute and what it needs to do, when user executes this plugin.
MyMojo java class:
package com.mycompany;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Goal which touches a timestamp file.
*
* @goal touch
*
* @phase process-sources
*/
public class MyMojo extends AbstractMojo {
/**
* Location of the file.
* @parameter expression="${project.build.directory}"
* @required
*/
private File outputDirectory;
public void execute() throws MojoExecutionException {
File f = outputDirectory;
if (!f.exists()) {
f.mkdirs();
}
File touch = new File(f, "touch.txt");
FileWriter w = null;
try {
w = new FileWriter(touch);
w.write("touch.txt");
} catch (IOException e) {
throw new MojoExecutionException("Error creating file " + touch, e);
} finally {
if (w != null) {
try {
w.close();
} catch (IOException e) {
// ignore
}
}
}
}
}
As said above, we don't have to write the plugin descriptor by ourselves, but it comes from annotations from the mojo java class. If you look at the javadoc for class, it has description and then @goal goalName to indicate that this mojo class defines a goal and also @phase (which is optional) to indicate which phase the goal binds to by default.
javadoc on the variable outputDirectory has @required which means the parameter is required to run the goal and it also has @parameter to indicate that this variable is a parameter which user can use to configure the goal. The parameter also has default configuration which comes from default-value attribute. You can also specify alias for your parameter using alias attribute.
Parameter example:
@parameter [alias="someAlias"] [expression="${someExpression}"]So, when the plugin is built, it builds the plugin descriptor using these and the rest of the parameters
[default-value="value"]
You can also add @readOnly attribute, which makes the parameter read only
and not configurable by the user. To deprecate a parameter, use @deprecated
attribute.
@requiresDependencyResolution- Fla gs this goal as requiring the
dependencies in the specified scope i.e all the dependencies in this scope
would first be resolved before executing the goal.
use defaults.
Running the goal:
mvn com.mycompany:myfirst-maven-plugin:1.0-SNAPSHOT:touch (which is mvn groupId:artifactId:version:goal)
Notice that this goal runs only within any of your maven
projects. If you run it outside, then it will error out,
since @requiresProject is true by default. You can turn
it off in the mojo, if you want to run the goal in any
directory.
If you want to customize/configure the params you can do
so using the -D parameter.
Plugin Prefix:
As you can see typing the above goal is long and cumbersome.
To avoid typing this everytime, maven assigns plugin prefix.
For our plugin, prefix is "myfirst". This is because maven
automatically generates plugin prefix for your plugin if
your plugin name follows the maven syntax of
$name-maven-plugin or maven-$name-plugin. You can see in
maven-metadata-local.xml in your group id directory in M2
Repo to get an idea of how the prefixes are assigned.<plugin>
<
name>myfirst maven Plugin
Maven Mojo<
/name>
<
prefix>myfirst
<
/prefix>
<
artifactId>myfirst-maven-plugin
<
/artifactId>
<
/plugin>
Here myfirst is the prefix for myfirst maven plugin.
But by default, maven searches in the
org.apache.maven.plugins (core plugins) and
org.codehaus.mojo (extra plugins) groups to see if the
plugin is assigned a prefix. It looks at
maven-metadata-central.xml to find this.If we want our
group id to be also searched for prefixes, then we can
do so by adding our plugin group in settings.xml.
Add this block to your ~\.m2\settings.xml:<pluginGroups> <
pluginGroup>com.mycompany
<
/pluginGroup>
<
/pluginGroups>
<build>
Now, you should be able to run the same above goal like
this: mvn myfirst:touch
If you do not like the maven generated plugin prefix, you
can also set your own by configuring maven plugin plugin in
your pom. For, example you can add the following to your
pom:
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>2.3</version>
<configuration>
<goalPrefix>mycustomprefix</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>Now, you should be able to run the same above goal like
this:
mvn mycustomprefix:touchLogging and Exceptions:
During execution, the mojo can get the log using getLog and
log messages. This log is very similar to log4j and has
debug, info, warn, error etc., Mojo should log error
messages if it is recoverable and can still continue. But,
if it cannot continue, it should throw exception. There are
two types of exceptions available and a mojo should decide
on which exception to throw at what time:
MojoExecutionException - Throw this for fatal exceptions
and the build cannot continue. This will cause BUILD ERROR
message to be displayed.
MojoFailureException - The goal cannot continue, but still
the build can continue.
Customizing Mojo:
You can customize the mojo using mojo parameters. You can
customize the parameter using the following ways:
1) Using -D option in command line while executing the goal
(-DparamName=paramValue)
2) Using the properties section in settings.xml
<properties>paramValue
</properties>
3) Adding the goal to the build section of the POM in your
project (by using executions and configuration). You can
even bind your goal to multiple phases and configure them
differently while executing in each phase.
settings.xml takes precendence over -D property and the one
in pom takes precedence over settings.xml.
Parameter Types:
The parameter type can be String, StringBuffer, Character,
Boolean (true or false), Integer, Double, Float, Date
(Example format: "yyyy-MM-dd HH:mm:ssa" (a sample date is
"2009-08-15 3:43:23PM"), URL (http:\\www.mycompany.com),
Files and Directories (C:\temp.txt)
We can also have multi-valued parameters.
1) For array - private String[] files;
<files>
<param>c:\test.txt</param>
<param>c:\test2.txt</param>
</files>
2) Collection (Any implementation of collection interface
like LinkedList, HashSet etc.,)
private HashSet mySetOfFiles;
<mySetOfFiles>
<param>c:\test.txt</param>
<param>c:\test2.txt</param>
</mySetOfFiles>
3) Map (Any implementation of Map interface like HashMap,
LinkedHashMap etc.,)
private Map myMap;
<myMap>
<key1>value1</key1>
<key2>value2</key2>
</myMap>
4) Properties - java.util.properties
private Properties myProps;
<myProps>
<property>
<name>propertyName1</name>
<value>propertyValue1</value>
<property>
<property>
<name>propertyName2</name>
<value>propertyValue2</value>
<property>
</myProps>
5) Any other object - private Tag tag;
(name and desc are fields of Tag)<configuration>
...
<tag>
<name>tagname</name>
<desc>tagdesc</desc>
</tag>
</configuration>...
The rules for mapping complex objects are as follows:
- There must be a private field that corresponds to name of the element being mapped. So in our case the tag element must map to a tag field in the mojo. If the private variable has _ in it (for example: name_) and if you want name to be the parameter then add a setter (setName) so that this can work.
- The object instantiated must be in the same package as the Mojo itself. So if your mojo is in com.mycompany.mojo.query then the mapping mechanism will look in that package for an object named Tag.
- If you wish to have the object to be instantiated live in a different package or have a more complicated name then you must specify this using an implementation attribute like this:
... <configuration implementation="com.mycompany.xyz.Tag">
<tag>
<name>tagname</name>
<desc>tagdesc</desc>
</tag>
</configuration>...
Handling pre-requisites:
You can use @executes goal to achieve this:
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.
Few examples:
@execute phase="package" - Executes a parallel lifecycle
ending in the specified phase. Results of this execution is
available via maven property${executedProperty}.
@execute goal="eclipse:eclipse" - Executes this goal in
parallel. Results of this does not affect the current build.
Conclusion:
We saw how to create a maven plugin, went through plugin
descriptor file (plugin.xml), and mojo's. We then saw how to
install the maven plugin, how plugin prefix mechanism works,
how to customize mojo parameters and different types of
parameters including custom objects. Finally, we also briefly
touched upon how to execute pre-conditions for the goal. Hope
this helps in understanding how to write maven plugins and
gives you a quick start on writing your own maven plugin.
No comments:
Post a Comment