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

  1. A regular Maven project usually invokes reporting goals of a plugin by declaring such 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>
      ...
  2. When maven-site-plugin is invoked (for example with the mvn site command), the specified plugins are loaded and the generate() method of each Mojo class that implements MavenReport is executed.
  3. 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.
  4. 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.
  5. The result produces an HTML file in the ./target/site directory of your project.

More details about Doxia Site Renderer

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 produced
  • public String getName(Locale locale): returns the display name of the report
  • public String getDescription(Locale locale): returns the description of the report
  • protected 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.2</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() and sink.head_()
  • sink.paragraph() and sink.paragraph_()
  • sink.section1() and sink.section1_()
  • sink.bold() and sink.bold_()
  • etc.

Do not forget to close elements!

At the very least, a document should include the following:

  • Head and title (sink.head() and sink.title())
  • Body (sink.body())
  • Section 1 with title (sink.section1() and sink.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

  1. Guide to Developing Java Plugins: Starting point, since a reporting plugin is a plugin...
  2. Maven Reporting API: The Reporting API to implement when a Mojo provides reporting for site.
  3. Maven Reporting Implementation: Base implementation of both Reporting API and Plugin API.
  4. Doxia Sink API: API to generate content.