Introduction
This guide is intended to assist users in developing reporting plugins for Maven in Java, that will contribute to sites generated by maven-site-plugin
or site PDF documents generated by maven-pdf-site
.
First and foremost, a report plugin is a Maven plugin and it is strongly advised to first read the Guide to Developing Java Plugins to properly understand its core mechanisms.
A plugin is actually not a report plugin in itself. But one (or several) of its goals or Mojos may be specialized to be invoked by maven-site-plugin
, typically during the site
build life cycle.
A Maven plugin can therefore implement regular goals and report goals. The below details how to write a Mojo that will get invoked as a report by maven-site-plugin
.
How It Works
- A regular Maven project usually invokes reporting goals of a plugin by declaring such plugin in the
<reporting>
section of itspom.xml
as in the example below:<project> ... <reporting> <plugins> <plugin> <groupId>com.mycompany.maven</groupId> <artifactId>simple-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> </plugin> </plugins> </reporting> ...
- When
maven-site-plugin
is invoked (for example with themvn site
command), the specified plugins are loaded and thegenerate()
method of each Mojo class that implementsMavenReport
is executed. - The
generate()
method generates a document through Maven's Doxia Sink API. This document is comprised of basic elements like title, headings, text, links, tables, etc. This is where you will put the logic of your report, assembling elements, based on the content of the Maven project. - These document elements are passed to Doxia to generate an HTML document, which itself gets wrapped into a Maven Skin, as specified in the projects
./src/site/site.xml
. - The result produces an HTML file in the
./target/site
directory of your project.
Basic Requirements of a Report Mojo
Each goal or Mojo is implemented with a separate Java class. For a Mojo to become a report Mojo, it needs to implement org.apache.maven.reporting.MavenReport
(in addition to org.apache.maven.plugin.Mojo
).
An easy way to implement both Mojo
and MavenReport
interfaces is to extend the org.apache.maven.reporting.AbstractMavenReport
class provided by maven-reporting-impl
(instead of org.apache.maven.plugin.AbstractMojo
for a regular Mojo).
The class will need to implement the following methods:
public String getOutputName()
: returns the name of page that will be producedpublic String getName(Locale locale)
: returns the display name of the reportpublic String getDescription(Locale locale)
: returns the description of the reportprotected void executeReport(Locale locale) throws MavenReportException
: produces the actual report
To build a Maven plugin that includes report Mojos, the pom.xml
of your project will need declare the project as a regular plugin, and include specific dependencies required by the report Mojos:
A (Very) Simple Report
Let's write a very simple report Mojo in a very simple Maven plugin:
- Plugin's name: Simple Plugin
- Plugin's artifact coordinates:
com.mycompany.maven:simple-maven-plugin:1.0-SNAPSHOT
- One goal: simple
- One result:
simple-report.html
Our Maven plugin project has 2 files only:
./pom.xml
./src/main/java/com/mycompany/maven/SimpleReport.java
The below examples can be copied and pasted as a template.
./pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.maven</groupId>
<artifactId>simple-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Simple Plugin</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<doxiaVersion>1.10</doxiaVersion>
<doxiaSitetoolsVersion>1.10</doxiaSitetoolsVersion>
</properties>
<dependencies>
<!-- Doxia API and Doxia Sitetools -->
<dependency>
<groupId>org.apache.maven.doxia</groupId>
<artifactId>doxia-sink-api</artifactId>
<version>${doxiaVersion}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.doxia</groupId>
<artifactId>doxia-site-renderer</artifactId>
<version>${doxiaSitetoolsVersion}</version>
</dependency>
<!-- reporting API -->
<dependency>
<groupId>org.apache.maven.reporting</groupId>
<artifactId>maven-reporting-impl</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.reporting</groupId>
<artifactId>maven-reporting-api</artifactId>
<version>3.0</version>
</dependency>
<!-- plugin API and plugin-tools -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.5.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.13.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-shared-utils</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.13.1</version>
<configuration>
<goalPrefix>simple</goalPrefix>
</configuration>
<executions>
<execution>
<id>generated-helpmojo</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
./src/main/java/com/mycompany/maven/SimpleReport.java
package com.mycompany.maven;
import java.util.Locale;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
/**
* Builds an simple report page as an example.
*
* <p>
* This example show how easy it is to build your own reporting plugin
* (or, for that matter, your own reporting Mojo)
*
*/
@Mojo(
name = "simple",
defaultPhase = LifecyclePhase.SITE,
requiresDependencyResolution = ResolutionScope.RUNTIME,
requiresProject = true,
threadSafe = true
)
public class SimpleReport extends AbstractMavenReport {
public String getOutputName() {
// This report will generate simple-report.html when invoked in a project with `mvn site`
return "simple-report";
}
public String getName(Locale locale) {
// Name of the report when listed in the project-reports.html page of a project
return "Simple Report";
}
public String getDescription(Locale locale) {
// Description of the report when listed in the project-reports.html page of a project
return "This simple report is a very simple report that does nothing but "
+ "shows off Maven's wonderful reporting capabilities.";
}
/**
* Practical reference to the Maven project
*/
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
@Override
protected void executeReport(Locale locale) throws MavenReportException {
// Get the logger
Log logger = getLog();
// Some info
logger.info("Generating " + getOutputName() + ".html"
+ " for " + project.getName() + " " + project.getVersion());
// Get the Maven Doxia Sink, which will be used to generate the
// various elements of the document
Sink mainSink = getSink();
if (mainSink == null) {
throw new MavenReportException("Could not get the Doxia sink");
}
// Page title
mainSink.head();
mainSink.title();
mainSink.text("Simple Report for " + project.getName() + " " + project.getVersion());
mainSink.title_();
mainSink.head_();
mainSink.body();
// Heading 1
mainSink.section1();
mainSink.sectionTitle1();
mainSink.text("Simple Report for " + project.getName() + " " + project.getVersion());
mainSink.sectionTitle1_();
// Content
mainSink.paragraph();
mainSink.text("This page provides simple information, like its location: ");
mainSink.text(project.getBasedir().getAbsolutePath());
mainSink.paragraph_();
// Close
mainSink.section1_();
mainSink.body_();
}
}
Building the Simple Plugin
Building the plugin is done by executing the below command in the root directory of the plugin project:
$ mvn install
This command will:
- compile your code
- produces the plugin JAR artifact (
./target/simple-maven-plugin-1.0-SNAPSHOT.jar
) - copy the artifact to your local repository so that it can be "consumed" by other projects (which is the purpose of a plugin, right?).
To make sure everything went well and is properly declared, you can now execute the below command in any other Maven project directory:
$ mvn com.mycompany.maven:simple-maven-plugin:1.0-SNAPSHOT:help [INFO] --- simple-maven-plugin:1.0-SNAPSHOT:help (default-cli) @ hardware-connectors --- [INFO] Simple Plugin 1.0-SNAPSHOT This plugin has 2 goals: simple:help Display help information on simple-maven-plugin. Call mvn simple:help -Ddetail=true -Dgoal=<goal-name> to display parameter details. simple:simple Builds an simple report page as an example. This example show how easy it is to build your own reporting plugin (or, for that matter, your own reporting Mojo) [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
Invoking the Simple Plugin
To invoke the report Mojo of our plugin in another Maven project, we just need to declare the plugin in the <reporting>
section of its pom.xml
as in the example below:
<project>
...
<reporting>
<plugins>
<plugin>
<groupId>com.mycompany.maven</groupId>
<artifactId>simple-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
</plugin>
</plugins>
</reporting>
...
</project>
Note: When no specific report is specified, all of the Mojos in the plugin, that are declared as "reporting" will be executed. More information about configuring reports.
More Information
The Doxia Sink API
In your executeReport()
method, you will leverage the Doxia Sink API to add elements to the report document.
You will use the Sink
object associated to the report:
Sink sink = getSink();
This object allows you to append new elements to the report document (initially empty). Unlike some DOM manipulation APIs, you cannot insert elements in already existing elements, or remove elements.
The elements that you append to the document will look familiar if you have basic knowledge of HTML. Most of the elements have opening and closing tags, like sink.body()
(opening) and sink.body_()
(closing).
sink.head()
andsink.head_()
sink.paragraph()
andsink.paragraph_()
sink.section1()
andsink.section1_()
sink.bold()
andsink.bold_()
- etc.
Do not forget to close elements!
At the very least, a document should include the following:
- Head and title (
sink.head()
andsink.title()
) - Body (
sink.body()
) - Section 1 with title (
sink.section1()
andsink.sectionTitle1()
)
The Sink
object allows you to add raw text with the rawText() method. More precisely, it allows you to add raw HTML code into the document for full flexibility. However, you should limit the usage of this method as you may add elements that are not supported by non-HTML renderers (like maven-pdf-site
).
The Doxia Sink API allows you to specify SinkEventAttributes to each element, i.e. HTML properties, notably the class and the ID of an object, which allows for easy customization with an appropriate CSS (either provided by the specified Maven Skin, or by the project itself).
Creating more than one document
You may need to create not just one HTML file, but several of them (like Javadoc produces one HTML file for each Java class). To do so, you will need to get a new Sink for each HTML file you need to produce. This is achieved by using the SinkFactory
object that you can easily obtain with the getSinkFactory()
method of your AbstractMavenReport
instance, as in the example below.
public class SimpleReport extends AbstractMavenReport {
...
/**
* Where the HTML pages of the report will be created.
*/
@Parameter( defaultValue = "/home/jenkins/82467a7c/workspace/aven_maven-box_maven-site_master/target/site", property = "outputDirectory", required = true )
private File outputDirectory;
...
@Override
protected void executeReport(Locale locale) throws MavenReportException {
...
// Create a new sink
String pageFilename = "other-report.html";
Sink otherSink;
try {
otherSink = getSinkFactory().createSink(outputDirectory, pageFilename);
} catch (IOException e) {
throw new MavenReportException("Could not create sink for " + pageFilename + " in " + outputDirectory.getAbsolutePath(), e);
}
// Create the "other" report
otherSink.head();
otherSink.title();
otherSink.text("The Other Report");
...
The above example will create a other-report.html
HTML file along with simple-report.html
.
Note: Despite the fact that you will be creating additional HTML files, the Velocity variable guides/plugin/guide-java-report-plugin-development.html
passed to the site.vm
script of the Maven Skin will keep the name of the original report (i.e. the result of your getOutputName() method). More information about the Velocity variables.
Resources
- Guide to Developing Java Plugins: Starting point, since a reporting plugin is a plugin...
- Maven Reporting API: The Reporting API to implement when a Mojo provides reporting for site.
- Maven Reporting Implementation: Base implementation of both Reporting API and Plugin API.
- Doxia Sink API: API to generate content.