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 |
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:
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.
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:
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.
This tutorial contains two components:
confluence-reporting-service
) - which defines a custom report-descriptor plugin module and displays reportssystem-config-report
) - which implements the report-descriptor plugin module to display a simple report about the Confluence systemThe 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.
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 2public 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 2public 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>
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 2public 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".
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.
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.
Report
and returns the system information (similar as to Confluence Admin > System Info page) formatted as a simple (undecorated) HTML page.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 2mvn install
or (if using the plugin sdk)
1 2atlas-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>
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 2public 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" />
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.
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: