Last updated Oct 3, 2024

Defining a pluggable service in a Confluence plugin

Level of experience: Advanced

Our tutorials are classified as 'beginner', 'intermediate' and 'advanced'. This one is at 'advanced' level. If you have never developed a plugin before, we advise you to try a beginner tutorial first.

The source code of the plugin used in this tutorial is available in the Atlassian public source repository. You can check out the source code here. You will find two projects within this directory:

confluence-reporting-service

Contains the service component and the module type definitions

system-config-report

A plugin which defines a report

Overview

This tutorial describes how to define a plugin which has a pluggable service, exposed as a component, where plugins are comprised of a custom plugin module type. This covers several items:

  • Defining a Module Type
  • Defining a Service that allows implementations of the Module Type to be listed
  • Implement an instance of the Module Type

The example we are implementing is a reporting service that provides a way for other plugins to provide reports to be viewed through a single interface.

Required Knowledge

To complete this tutorial, you must already understand the basics of Java development: classes, interfaces, methods, how to use the compiler, and so on. You should also understand:

  • How to create an Atlassian Confluence plugin project using the Atlassian Plugin SDK.
  • How to compile and install your project within Confluence.

As this is an advanced plugin tutorial we will only highlight or discuss the important parts of the implementation. You should also review the source code for the two example plugins while reading this tutorial.

Plugin Components

This tutorial contains two components:

  • Reporting Service (confluence-reporting-service) - which defines a custom report-descriptor plugin module and displays reports
  • System Information Report (system-config-report) - which implements the report-descriptor plugin module to display a simple report about the Confluence system

Report Service Plugin

The Report Service Plugin contains an user interface for viewing the registered reporters and for triggering reports. It also contains the new plugin module type that report implementing plugins can use to identify their reports.

New Module Type

We are defining a new module type called report in this plugin which allows other plugins specify reports that they are able to produce. A report consists of several properties, used to identify each report, and a method which causes the report to be generated and the results returned. Each plugin that wishes to generate reports must implement the following Report interface for each report they wish to export:

1
2
public interface Report {
    public String generateReport();
    public String getName();
    public String getDescription();
    public String getKey();
}

At this point it is possible to consider an alternative solution to the one we demonstrate here: rather than define a custom module type other plugins could explicitly and programmatically publish their Report types in a similar manner to the Observer Pattern design pattern. The difference between these approaches will be commented on at the end of the turorial.

In order that plugins can expose their Report types we define a new XML element to be used in the atlassian-plugin.xml, this is achieved by extending the AbstractModuleDescriptor

1
2
public final class ReportDescriptor extends AbstractModuleDescriptor<Report> {
    public Report getModule() {
        return ((AutowireCapablePlugin) getPlugin()).autowire(getModuleClass());
    }
}

and adding the new module type to the atlassian-plugin.xml so that other plugins can use the <report> element

1
2
<module-type key="report" class="com.adaptavist.tutorials.reportingexample.core.ReportDescriptor" name="Report Module Descriptor">
   <description>Module Descriptor for 'report' module type.</description>
</module-type>

Report Service

So that this plugin and others can query and/or generate the defined reports we implement a simple service component, ReportService, which is exposed publicly in the plugin xml file:

1
2
<component key="report-service" class="com.adaptavist.tutorials.reportingexample.core.DefaultReportService" name="Report Service" public="true">
  <interface>com.adaptavist.tutorials.reportingexample.core.ReportService</interface>
</component>

The DefaultReportService implementation of the ReportService interface uses the PluginAccessor to access all the report plugin modules:

1
2
public List<Report> getAllReports() {
    List<ReportDescriptor> reportDescriptors = pluginAccessor.getEnabledModuleDescriptorsByClass(ReportDescriptor.class);
    List<Report> reportTypes = new ArrayList<Report>(reportDescriptors.size());
    for(ReportDescriptor descriptor : reportDescriptors) {
        reportTypes.add(descriptor.getModule());
    }
    return reportTypes;
}

this means that at each invocation the list of available plugins is rebuilt, a type of dynamic lookup which is in part what allows this plugin to be "pluggable".

Report Web Interface

The admin report interface is created using a standard web work action, and web-ui module to provide a link in the Confluence admin console. If you have created a plugin with a custom action previously there should be nothing unfamiliar in the setup, if not then there are several tutorials which will easily guide you through this process.

The UI performs two basic tasks: it displays all the available reports, and displays the output of any one report. In both cases the Action simply invokes a method on the ReportService to find the appropriate Report(s). Because the actions are given a path underneath /admin default Confluence interceptors ensure that only admin users have access to the reports.

Build and install

Once these three components have been set up, you should be able to build and install the plugin in Confluence. When you access the report menu, either via the plugin configure link or the link on the admin sidebar, no reports will be listed as there are no plugins that have a report module installed yet.

System Reporting Plugin

  • Implements the Report and returns the system information (similar as to Confluence Admin > System Info page) formatted as a simple (undecorated) HTML page.

pom.xml setup

As we are going to extend the Report interface provided in the Report Service plugin we will need to add it as a dependency to our maven project file. Before you can do this we need to install the service plugin into our local maven repository.

From where the service plugin is checked out type:

1
2
mvn install

or (if using the plugin sdk)

1
2
atlas-mvn install

to install into your local repository.

This will need to be repeated anytime there is an update to the report service plugin.

Now we can add the dependency to our pom.xml for the system reporting plugin:

1
2
<dependency>
    <groupId>com.adaptavist.tutorials.reportingexample</groupId>
    <artifactId>confluence-reporting-service</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>provided</scope>
</dependency>

Creating the Report

To create a new custom report we need to create a class that implements the Report interface. Our example report SystemInformationReport provides simple information about the Confluence system it is installed on.

1
2
public String generateReport() {
    return "Global Spaces: " + systemInformationService.getUsageInfo().getGlobalSpaces() + 
           "<br/>Pages:" + systemInformationService.getUsageInfo().getCurrentContent();
}

Once the report is implemented we need to add it to our atlassian-plugin.xml config. We use the new report module to define our report:

1
2
<report 
   key="systeminfo-report-descriptor" 
   name="System Information Report"  
   class="com.adaptavist.tutorial.reporting.example.SystemInformationReport" />

Build and Install into Confluence

Because the system information report plugin depends on the module defined in the reporting service plugin it must be installed into Confluence first.

During development we have found that updating the plugin that defines a new modules type (e.g. confluence-reporting-service) won't re-register all the implementing modules. Uninstalling and reinstalling the implementing modules will normally fix it, or a full system restart. This does slow down development somewhat.

Conclusion

While this example is kept very simple it is possible to see how it could be extended in to an advanced reporting framework where plugins could export reports covering usage, configuration, or be able to output in different formats (XML, PDF, Word) or collate several reports in to one. Alternatively this mechanism of providing a easily extensible plugin can be applied to other situations.

As mentioned earlier in the tutorial this is not the only possible solution to the problem; the DefaultReportService performs a dynamic lookup each time all or one of the Report instances must be fetched which as the number of installed plugins grows, or the number of defined reports, increases in overhead. Implementing the Observer pattern may also help alleviate this, but that is out of scope for this tutorial.

Rate this page: