Howto: Writing your own report-generating plugin for Maven 2.0
Creating your own reports was no big problem in Maven 1, and it isn't in Maven 2, you just have to know how it works ;) I had to find it out by myself while working on my Bachelor thesis, and as there still is hardly any documentation on this topic, I decided to write this little howto.

If you're still asking yourself "How do I write a plugin for Maven 2 at all?" then you should refer to the Guide to Developing Java Plugins on the Maven website. You will notice that Maven 2 has abandoned the usage of Jelly scripting for its plugin system which is totally based on Java (I think there still is some support for scripting, but that's even less documented ...).

OK, so what do we need for our little plugin? I said there's no more scripting, just plain old Java. So what we need is a plain old Java class. And while a simple plugin has to extend AbstractMojo, a report generating plugin extends AbstractMavenReport, which can be found in the org.apache.maven.reporting package. We also need MavenReportException, and some other packages you will see later what we need them for. Let's say we call our class SampleReport and put it in the sample.plugin package. Here is the complete code for our sample class:

package sample.plugin;

import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.project.MavenProject;

import org.codehaus.doxia.sink.Sink;
import org.codehaus.doxia.site.renderer.SiteRenderer;

import java.util.Locale;
import java.util.ResourceBundle;

/**
 * @goal report
 * @description Create a little useless report.
 */
public class SampleReport
    extends AbstractMavenReport {

    /**
     * A message to publish in our report.
     * @parameter expression="Hello Maven report!"
     */
    private String message;

    /**
     * Specifies the directory where the report will be generated
     *
     * @parameter default-value="${project.reporting.outputDirectory}"
     * @required
     */
    private File outputDirectory;

    /**
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * @parameter expression="${component.org.codehaus.doxia.site.renderer.SiteRenderer}"
     * @required
     * @readonly
     */
    private SiteRenderer siteRenderer;

    /**
     * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
     */
    public String getName( Locale locale )
    {
        return "Sample Report";
    }

    /**
     * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
     */
    public String getDescription( Locale locale )
    {
        return "Just a sample Maven report for educational purpose.";
    }

    /**
     * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
     */
    protected String getOutputDirectory()
    {
        return outputDirectory.getAbsolutePath();
    }

    /**
     * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
     */
    protected MavenProject getProject()
    {
        return project;
    }

    /**
     * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
     */
    protected SiteRenderer getSiteRenderer()
    {
        return siteRenderer;
    }

    /**
     * This is the most important method. Here we specify what our report should do.
     * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
     */
    public void executeReport( Locale locale )
        throws MavenReportException
    {
        // we use Doxia to create our report. See further annotations below.
        Sink sink = getSink();
        sink.head();
        sink.title();
        sink.text( "Sample Report" );
        sink.title_();
        sink.head_();

        sink.body();

        sink.section1();
        sink.sectionTitle1();
        sink.text( "Sample Report" );
        sink.sectionTitle1_();

        sink.table();

        sink.tableRow();
        sink.tableHeaderCell();
        sink.text( message ); // we use our parameter here
        sink.tableHeaderCell_();
        sink.tableRow_();

        sink.table_();
        sink.section1_();
        sink.body_();
        sink.flush();
        sink.close();
    }

    public String getOutputName() {
        return "sample"; // output file will be "sample.html" in the target/site directory
    }
}



OK, so what did we do here? AbstractMavenReport specifies some methods we have to overwrite. getName() and getDescription() are used to define a name and a description for our report. Parameters are identified by Maven through the @parameter-attribute in the Javadoc. "expression=..." sets a default value if we don't specify the parameter in pom.xml. We use this for a little sample parameter called "message" we use in our report, and "outputDirectory" which specifies where our report, once created, should go. The most important method is executeReport() which is called when the report is created. There we see the new approach to creating custom reports. No more doc:jsl- or Ant-style-tags. What's used here is called Doxia ( doxia.codehaus.org, also *very good* documented ...) which is an quite easy way of creating our report-HTML-file. We get the Sink-Object by calling getSink(). Then we can build the HTML file using the Doxia Sink-API methods. You could also generate this file another way. Then you would have to override the following method of AbstractMavenReport:

public boolean isExternalReport()
{
	return true;
}

But using Doxia is quite and I think it's the best possibility if you don't want to use your plugin's code outside of Maven. Doxia has methods for creating the parts of the HTML file. The methods with the underscore like "head_()" are similiar to the HTML end-Tags. So for our little report we start with head(), create a title in which we specify the text "Sample Report" by using text(). Then we create the body with a single section. There we use a table with only one cell to show our text specified in the message-Parameter. At the end we call flush() and close(), so everything is written to the target file, which will be called "sample.html" as we defined in the getOutputName() method.

Good, now the biggest part is done. But we still have to add some entries to the pom.xml. In the dependencies-section add the following dependency, so we can use AbstractMavenReport:

<dependency>
		<groupId>org.apache.maven.reporting</groupId>
		<artifactId>maven-reporting-impl</artifactId>
		<version>2.0</version>
</dependency>

Also dont't forget maven-plugin, but this is the same as for a normal plugin. So, that's it! But wait a minute, how does Maven know that it should create a report? Well, look again at the head of our class. There we specify a goal called "report" with the @goal-attribute. The name of the goal doesn't matter, you can use a name you like. When creating the plugin Maven identifies the goal and it also realises that this is a report class, so when we later call our report-goal, Maven will call the classe's executeReport()-method, as it would call the execute()-method of a plugin implemented as AbstractMojo. OK, so go on and install the plugin by calling mvn install (or m2 or maven instead of mvn if your executable has another name). Of course we have to modify the reporting-section of the project's POM we would like to use our new report:

<reporting>
    <plugins>
        <plugin>
            <groupId>sample.plugin</groupId>
            <artifactId>sample-plugin</artifactId>
        </plugin>
    </plugins>
</reporting>

Finished! OK, that's just the first version of this tutorial, but at the moment I don't know much more than I've written here. If I find out something new, I'll add it here. If you have questions just mail me at webmaster@ai-studium.de, I don't promise I can give you an answer, but you may try at least ;) But please, Maven and Doxia developers, please add some documentation by yourselves, not everyone likes to go the hard way like I had to!

Below there is a simple list of all Doxia-methods for creating the HTML-file. Maybe I'll add some comments to these in the future ...


Doxia Sink-API
    void head();

    void head_();

    void body();

    void body_();

    void section1();

    void section1_();

    void section2();

    void section2_();

    void section3();

    void section3_();

    void section4();

    void section4_();

    void section5();

    void section5_();

    void list();

    void list_();

    void listItem();

    void listItem_();

    void numberedList( int numbering );

    void numberedList_();

    void numberedListItem();

    void numberedListItem_();

    void definitionList();

    void definitionList_();

    void definitionListItem();

    void definitionListItem_();

    void definition();

    void definition_();

    void figure();

    void figure_();

    void table();

    void table_();

    void tableRows( int[] justification, boolean grid );

    void tableRows_();

    void tableRow();

    void tableRow_();

    void title();

    void title_();

    void author();

    void author_();

    void date();

    void date_();

    void sectionTitle();

    void sectionTitle_();

    void sectionTitle1();

    void sectionTitle1_();

    void sectionTitle2();

    void sectionTitle2_();

    void sectionTitle3();

    void sectionTitle3_();

    void sectionTitle4();

    void sectionTitle4_();

    void sectionTitle5();

    void sectionTitle5_();

    void paragraph();

    void paragraph_();

    void verbatim( boolean boxed );

    void verbatim_();

    void definedTerm();

    void definedTerm_();

    void figureCaption();

    void figureCaption_();

    void tableCell();
    
    void tableCell(String width);

    void tableCell_();

    void tableHeaderCell();
    
    void tableHeaderCell(String width);

    void tableHeaderCell_();

    void tableCaption();

    void tableCaption_();

    void figureGraphics( String name );

    void horizontalRule();

    void pageBreak();

    void anchor( String name );

    void anchor_();

    void link( String name );

    void link_();

    void italic();

    void italic_();

    void bold();

    void bold_();

    void monospaced();

    void monospaced_();

    void lineBreak();

    void nonBreakingSpace();

    void text( String text );

    void rawText( String text );

    void flush();

    void close();