"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
How to Create an Apache Maven Plugin in Java
1. HOW TO WRITE A NEW APACHE MAVEN PLUGIN
This tutorial explain how to create an Apache Maven 2.x plugin using Java 1.5 annotations.
Requirements:
Elipse IDE (optional)
Java 1.5 (or greater)
Apache Maven 2.x
Is assumed that the above tools are already installed and the reader have a minimum knowledge about them (in
particular with Maven).
Why this guide
I have written it during the development of my plugin as “collections of notes”.
It can be find at:
http://code.google.com/p/maven-properties-checker/
You could use it as example associated with this guide.
It won't be a replacement for the official documentations, but only a quick start with tips and tricks and clarify
some possible doubts of a beginner Maven plugin developer.
Base concepts
A Maven plugin is composed of one or more "unit" named Mojo, each one is mapped to a plugin goal.
In Maven terminology, a goal represents a specific task that can be bound to zero or more build phases.
If is not bound with a build phase, the plugin can be executed only invoking it directly.
The general sintax to invoke a specific plugin goal is:
mvn plugin-name:goal-name
It execute the Mojo owned by "plugin-name" and associated with goal named “goal-name”.
A Maven plugin is packaged as a JAR file.
At least, a plugin is composed of two Mojo : one that perform the plugin business logic, and one with the help page
(shown with mvn plugin-name:help).
The "help Mojo" can be generated in automatic (see below) so that the simplest Maven plugin can have only one
Mojo.
The Maven specifications require the presence of a plugin descriptor named plugin.xml and placed in the META-
INF/maven folder of the final JAR file.
Don't worry about it: will be generated in automatic using an existing Maven plugin that exploit your javadoc
comments placed in the Mojo class.
Steps to create your first plugin
Let's see the single steps necessary to create a plugin.
step 1) create a Maven project for the new plugin with:
mvn archetype:generate -DgroupId=property.maven.plugin -DartifactId=maven-property-checker -DarchetypeArtifactId=maven-archetype-mojo
Obviously "groupId" and "artifactId" variables should set with custom values according with requirements.
Follow the wizard providing the required informations (they can be modifyed after editing the pom.xml).
step 2) If the previous command was executed successfully, a folder named with the provided “artifactId” is
created. Also in the source folder a simple Mojo is created.
Now open the generated pom.xml file to apply some customizations.
2. a) As you can note, the previous command has already added the necessary dependency with this plugin:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
don't modify it because offer the necessary API to create a Maven 2.0 plugin.
Inside the <dependencies> section add some new required dependencies:
<!-- A lightweight IOC container already used by Maven for dependency injection -->
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.9</version>
</dependency>
<!-- Necessary to use Java 5 annotations in plugin Mojos (See below for more informations) -->
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.2</version>
<scope>provided</scope>
</dependency>
c) Inside the <plugins> section add this plugin:
<!-- Necessary to import the project in Eclipse -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<!--
Plugin that create in auto the plugin.xml descriptor that will be placed under META-INF/maven folder
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<!-- Use a versione equal or greater to 3.0 to use java 5 annotation (See below for more info) -->
<version>3.2</version>
<configuration>
<!-- see http://jira.codehaus.org/browse/MNG-5346 -->
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
</configuration>
<executions>
<!-- This goal is used to generate in automatic the plugin descriptor -->
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
<!-- To generate in auto the Mojo associated at the help goal using your javadoc comments -->
<execution>
<id>help-goal</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
3. <!-- optional: add this plugin to use the "Modello" project (See dedicated section below for more info) -->
<plugin>
<groupId>org.codehaus.modello</groupId>
<artifactId>modello-maven-plugin</artifactId>
<version>1.7</version>
<!-- The location of the file with the Modello definitions -->
<configuration>
<models>
<model>src/main/mdo/checks.mdo</model>
</models>
<version>1.0.0</version>
</configuration>
<executions>
<!-- this phase will execute the goals "java" and "xpp3-reader" to create java bean and xpp reader object -->
<execution>
<id>myModel</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
<goal>xpp3-reader</goal>
</goals>
</execution>
<!-- During this phase will be executed the goals "xsd" which generate an XML Schema for the model -->
<execution>
<id>myModel-site-xsd</id>
<phase>pre-site</phase>
<goals>
<goal>xsd</goal>
</goals>
<configuration>
<!-- (optional) the folder where place the generated xsd -->
<outputDirectory>target/generated-site/resources/xsd</outputDirectory>
</configuration>
</execution>
<execution>
<id>myModel-site-doc</id>
<phase>pre-site</phase>
<goals>
<goal>xdoc</goal>
</goals>
</execution>
</executions>
</plugin>
step 3)
Create the Eclipse project so that when can develop in an IDE. Open the command line and execute:
mvn eclipse:clean eclipse:eclipse
Now import the project in Eclipse (File --> Import --> Existing project into workspace...)
step 4) Working inside Eclipse project, we can create our first Mojo and develop our plugin.
The creation command has created a simple Mojo that you can customize instead of create a new one, but
i suggest to create a new one to have a clean situation.
To create a Mojo, is necessary implements a class that extends "org.apache.maven.plugin.AbstractMojo" and implement
the "execute" method, which will contains the business logic of the Mojo.
The naming convention recommend to append the “Mojo” suffix at the class name.
The next two sections will explain how pass input parameters to a plugin and how bind a Mojo with a goal.
Plugin input parameters
At this point of the tutorial we have configured the plugin project in Eclipse and created a base Mojo.
The next step is understand how work the parameters injection system for plugin.
To use our new plugin is necessary that another Maven project declare the dependency in his pom.xml (ie using a
<plugin> block).
To pass input parameters at our plugin, that <plugin> block must contains a child node named <configuration> where
place that parameters.
For example, the pom.xml of the project that want use our plugin will contain:
<plugin>
<!-- group, artifactId, version of our plugin omitted for simplicity -->
<configuration>
<!-- Put here the input parameters for our plugin -->
4. </configuration>
</plugin>
The input parameters are injected in the plugin Mojo fields by Plexus framework (http://plexus.codehaus.org/) already
used by Maven for other tasks.
In order that the injection work correctly is necessary respect some rules and naming conventions in the Mojo and
in the above <configuration> block.
In the Mojo class is necessary mark the fields with custom annotations; this can be done using a java 5 or javadoc
style.
This tutorial focus on the use of java 5 annotations (for that reason is necessary use the plugin "maven-plugin-plugin"
with version greater 3.0 and declare the use of the plugin "maven-plugin-annotations").
Example:
suppose that our Mojo have that field:
@Parameter
private File verificationFile;
In the pom.xml the <configuration> block must be:
<plugin>
<!-- other plugin data omitted for simplicity -->
<configuration>
<verificationFile>
src/test/verifier/property-check-input.xml
</verificationFile>
</configuration>
</plugin>
As you can note, the Mojo field name is identical at the tag name inside the <configuration> block (ie
“verificationFile”). In this manner the field will be set to the value “src/test/verifier/property-check-input.xml” and you
can use it in the Mojo class (remember the setter method in your Mojo class).
Don't worry about the parameter type conversion, just assign the desired type at the field.
See: http://maven.apache.org/guides/plugin/guide-java-plugin-development.html for more examples with different parameters
type (eg. use a parameter with multiple values).
Is possible inject in a Mojo field also properties values already offered by Maven, not only custom values. For
example, we want the project base directory which is kept in the Maven property named "basedir", in that case we
have:
@Parameter (property = "basedir")
private File projectBasedir;
To pass that types of properties is not necessary add a tag in the <configuration> block (as above) but only annote
the field and provide the setter method.
See: https://cwiki.apache.org/MAVEN/java-5-annotations-for-plugins.html for more info about the available annotations.
Is possible provide a default value in case of a parameter is missing in the <configuration> block, for example:
@Parameter( property = "myFileld", defaultValue = "this is default value" )
private String myFileld;
Note: the javadoc comments that you place in the Mojo class and fields will be inserted in the plugin descriptor
(plugin.xml) by "maven-plugin-tools" plugin.
In case we want execute our Mojo by command line rather than invoke it from another project, as explained
above, the input prameters can be passed from the command line as "system property". In the source code is
necessary use the attribute "property" for the "parameter" annotation, for example:
@Parameter(property = "query.url")
private String myParameter;
5. To inject a value in the above parameter is necessary build the project using the the "-D" option followed by the
property name (ie “query.url”):
mvn clean package -Dquery.url=http://maven.apache.org
In case of more input parameters we have many -D<property-name>=<property-value> block.
Tip: the name of the system property can be different from the field name (ie “myParameter”) but is not a good
idea.
Annotation for the Mojo class
This section is about the annotations to use at class level in the Mojo.
For example:
@Mojo( name = "check" )
@Execute( goal = "verify", phase = LifecyclePhase.COMPILE)
public class PropertiesCheckerMojo extends AbstractMojo
{
//fields and methods omitted for simplicity
}
The class "PropertiesCheckerMojo" is a Mojo.
The attribute "name" of the @Mojo annotation define the name of the goal which the Mojo is associated (remember
that a Mojo is binded with a goal).
In the example above the Mojo named “check” is associated with the goal “verify”.
So that, to execute the above Mojo directly from the command line, the sintax is:
mvn <plugin-group-id>:<plugin-name>:<plugin-version>:check
Tip: that command can be shorted (See: http://maven.apache.org/guides/plugin/guide-java-plugin-development.html section
"Shortening the Command Line").
Or that command:
mvn <plugin-group-id>:<plugin-name>:<plugin-version>:help -Ddetail=true
executes the Mojo associated with the goal named "help" (remember that “help” Mojo is generated in automatic,
this means that there insn't the associate Mojo class in the plugin source tree).
The not mandatory option “-Ddetail=true” display also informations about Mojo input fields.
The “@Execute” annotation define also the Maven build lifecycle phase when the plugin (invoked by command line)
must be executed.
To invoke the plugin from another Maven project is necessary define his dependency and provide an <execution>
block to attach the goal of our plugin to a particular phase of the Maven build lifecycle.
So that when the user execute "mvn clean package install" will be executed the goal of our plugin, for example:
<plugin>
<groupId>property.maven.plugin</groupId>
<artifactId>maven-property-checker</artifactId>
<version>1.0-SNAPSHOT</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<verificationFile>src/test/verifier/property-check-input.xml</verificationFile>
</configuration>
</plugin>
Looking at the above code, the goal "check" of the plugin "maven-property-checker" is binded with the "compile" phase:
when that phase will be executed also our plugin goal will be executed too.
6. Use the Maven logging system
Inside our Mojo is possible use the logging system already provided by Maven to print messages in the console
during the execution.
To use it simply invoke the inherited "getLog" method, for example:
getLog().info("Hello world !");
Are also available others priority level (like log4j framework).
Apache Maven and Modello project
What is
Modello is a “Data Model toolkit” (http://modello.codehaus.org/) already used inside Maven.
It is used only during the build time of your project (not a runtime)
As input receives a model file (written with an xml-like sintax) and generates the Java pojo that respect it
structure (like Jaxb framework) .
Why use Modello instead of existing similar tools? because, as state of his web site, "Modello was created to
serve the needs of Apache Maven 2“.
When use it
Suppose that our plugin must receives in input many parameters that must be structured to
reflect a precise relation between elements.
Using the traditional <configuration> block is not applicable for obvious reasons.
The solution is pass in input an xml file and read it from the Mojo.
But must be validated and parsed to read his values ! This gap is filled by Modello.
If your plugin need only few not structerd parameters, Modello is not neccessary, is enough the
<configuration> block, so that you can skip this section.
How use it and how it works
a) In the plugin source code tree:
- Create a "model" file (.mdo) (see below) that describe the structure of the xml input file (think the .mdo file like
an XSD). Usually that file is placed in a folder named "src/main/mdo/checks.mdo".
- Configure the "modello-plugin" passing it the path of the .mdo file (See sections “Steps to create your first plugin”)
- Build your plugin (mvn clean package)
- During the build, Modello read the ".mdo" file and generates Java pojo that will be used to access at the input
xml from the Mojo. The pojo structure reflect the relations expressed in the .mdo file.
The generated pojo will be placed by default in the folder "target/generated-sources/modello" of the project source
tree (or in the one set inside <outputDirectory> configuration block of the modello-plugin).
That folder will be added in automatic as a source folder of the Eclipse project (press F5 to refresh the
workspace).
- Now in your Mojo we can use the Java pojo representing xml file content.
- Modello creates also a class that act as dedicated “reader” for the xml content (See below for example).
b) In the pom.xml of the project that uses our plugin:
- configure the dependency to our plugin and his <configuration> section providing the relative path at input xml:
<plugin>
<!-- out plugin data omitted for simplicity →
<configuration>
<!-- This is the input file that will be mapped to java pojo previously created by Modello using .mdo file -->
<verificationFile>src/test/verifier/property-check-input.xml</verificationFile>
</configuration>
.....
<plugin>
Obviously the provided xml file must be conform with the .mdo model, otherwise his content can't be mapped in
the previously generated java pojo and the build fail when our Mojo will be executed.
When you build the project and our plugin Mojo will be executed, the content of the provided xml file will be
7. mapped in automatic in the Java pojo contained in the plugin (previously generated by Modello) and our plugin
Mojo can read xml content and perform his business logic.
The xml reading is done using some readers generated by Modello when generates the pojo (See section “Using the
Modello readers from Mojo”).
An example Modello file
The following is a mdo file that describe the concept "a CheckBlock object has associated many Check".
Translating it in xml file, we have one ore more <checkBlock>, with one or more child <check> blocks.
<model>
<!-- unique identifier of this model (can be empty). -->
<id>propertty-checker-model</id>
<!-- the prefix assigned at the generated reader for xml input (In this case will be PropertyCheckerXpp3Reader.java) -->
<name>PropertyChecker</name>
<description>
A description for our model file
</description>
<!--
A list of many <default> block, each one is composed of <key><value> pair
The values allowed inside the <key> block must be contained in a default list
-->
<defaults>
<default>
<!-- this pair key-value specify the PACKAGE where put the generated java pojo
(NOTE: the folder that contains it is specified in the "Modello plugin " configuration)
-->
<key>package</key>
<value>org.apache.maven.plugin.verifier.model</value>
</default>
</defaults>
<!-- This section contains a set of <class> </class> blocks each one will be mapped in a java class -->
<classes>
<!-- The class that represents the most outer xml node must have the rootElement="true".
The"xml.tagName" attribute state the name of the xml tag map this object: use it in case of you want use a
java class name different from the xml tag name.
-->
<class rootElement="true" xml.tagName="propertyChecker">
<name>PropertyChecker</name>
<version>1.0.0</version>
<description>
Root element of the property checks file.
</description>
<!-- The fields are the child nodes of the element -->
<fields>
<field>
<name>checkBlocks</name>
<version>1.0.0</version>
<description>
<![CDATA[ A block the contains the file location an the checks to performs on it. ]]>
</description>
<!-- Means that this type is associated with another type -->
<association xml.itemsStyle="wrapped">
<!-- The type name CheckBlock is defined with another class (see below )-->
<type>CheckBlock</type>
<multiplicity>*</multiplicity>
</association>
</field>
</fields>
</class>
<!-- represents a "<checkBlock>" xml block -->
<class>
<name>CheckBlock</name>
<version>1.0.0</version>
<fields>
<field>
<name>checks</name>
<version>1.0.0</version>
<required>true</required>
<description>
<![CDATA[ The name of the check to apply at the properties file picked up from a default list ]]>
</description>
<association xml.itemsStyle="wrapped">
<!-- Means that there is an xml tag with named "check" enveloped inside a tag named "Checks" -->
<type>Check</type>
<multiplicity>*</multiplicity>
</association>
</field>
</fields>
</class>
8. <!-- Represents a "check" xml block -->
<class>
<name>Check</name>
<version>1.0.0</version>
<fields>
<field>
<name>checkName</name>
<version>1.0.0</version>
<required>true</required>
<description>
<![CDATA[ The field description ]]>
</description>
<!-- See http://modello.codehaus.org/Data+Model for supported data type -->
<type>String</type>
</field>
</fields>
</class>
</classes>
<model>
Note the attribute 'xml.itemsStyle="wrapped"' in the association. It means that the tag is enveloped in a tag with his
same name, but plural:
<checks>
<check>
....
</check>
....
</checks>
In case of a tag is not “enveloped”, use the option 'xml.itemsStyle="flat"'.
This is an example xml file compliant with the .mdo file describe above:
<propertyChecker>
<checkBlocks>
<checkBlock>
<checks>
<check>
<checkName>checkName1</checkName>
</check>
<check>
<checkName>checkName2</checkName>
</check>
</checks>
</checkBlock>
</checkBlocks>
</propertyChecker>
If you look at the Java pojo generated by Modello you should find a class like this:
public class CheckBlock {
private List<Checks> Check;
…....
}
This is the same concepts in the mdo file, but expressed with Java sintax.
See: http://modello.codehaus.org/Data+Model for more examples about model file.
Using the Modello readers from Mojo
Modello, together with the Java pojo, generates also an utility class that works as "java reader" for the xml input.
His name follow the naming convention <prefix>Xpp3Reader where <prefix> is the value of the tag <name> of the .mdo
file.
Suppose that our Mojo receives in input a File (field "verificationFile" in the example below) and we want read it:
......
// "verificationFile" is a file path passed to the Mojo with the <configuration> block
Reader reader = new FileReader(this.verificationFile);
//Using the reader class generated by Modello is possible access at the provided input file
9. PropertiesCheckerNameXpp3Reader xppReader = new PropertiesCheckerNameXpp3Reader();
// "PropertyChecker" is a pojo generated by Modello that corresponds at the most external tag in the input xml (ie <propertyChecker>)
PropertyChecker propertyChecker = xppReader.read( reader );
......
Now, using the "propertyChecker" getters methods, is possible access at the inner xml elements with an object
oriented manner.
External links
For more detailed informations about Maven plugin development, see:
http://docs.codehaus.org/display/MAVENUSER/Mojo+Developer+Cookbook
Author: fulvio999@gmail.com