Converting a Plugin to Plugin Framework 2

Confluence 2.10 and later includes a new plugin framework is based on OSGi and Spring Dynamic Modules. The new plugin framework has all the advantages of OSGi, plus it will be included into all Atlassian products. This means that writing plugins for different products would be more consistent and porting the same plugin to different Atlassian applications will require fewer changes than it did in the past. For the full documentation of the Atlassian Plugin Framework 2, refer to the Plugin Framework Documentation.

Plugins that were written for the old framework will continue to work, but to leverage the new functionality you will need to convert your plugin to the new framework. This page describes how to migrate an existing plugin to the new Confluence plugin framework.

1. Set your plugin 'plugins-version' flag to version 2

As described in the documentation there are two types of plugins in the Atlassian Plugin Framework 2:

  • Version 1 — These may be static (deployed in WEB-INF/lib) or dynamic (via the web UI, only in Confluence) and should work the same as they did in version 1 of the Atlassian Plugin Framework. The capabilities and features available to version 1 plugins vary significantly across products.
  • Version 2 — These plugins are dynamically deployed on an internal OSGi container to provide a consistent set of features and behaviours, regardless of the application the plugin is running on. Version 2 plugins have to be specifically declared as such, using the plugins-version="2" attribute in atlassian-plugin.xml.

So the first step of migration is to make your plugin a Version 2 plugin by setting plugins-version="2" attribute in atlassian-plugin.xml:

<atlassian-plugin name="plugin name" key="plugin key" enabled="true" plugins-version="2">

For the remainder of this document, Version 2 plugin and OSGi plugin should be considered synonymous.

2. Check that packages used by your plugin are available to OSGi plugins

OSGi plugins — plugins with 'plugins-version' set to 2 — are subject to certain restrictions. In particular, an OSGi plugin can access only those external classes that Confluence (or other plugins) explicitly expose. This means that you can no longer assume that all classes on the Confluence classpath will be accessible to your plugin.

Refer to the list of packages that Confluence exposes, and ensure that all classes used by your plugin are covered by this list. The list of packages should be sufficient to access all the functionality of Confluence from within your plugin.

However, many of the third party packages that ship with Confluence are not exported. If your plugin needs any of these libraries, you will need to package them within the plugin. This has been done to provide better compatibility for plugins if Confluence upgrades those libraries in the future (eg. API incompatibilities that require code changes). The easiest way to package dependencies with your plugin is to use the Working with the SDK.

It is very important to ensure that plugin code does not depend on packages that are not exposed, as the problem will only manifest itself during runtime.

2.1 Rely on automatic package imports

OSGi plugins have their required packages imported transparently. You do not need to do anything to have required packages imported, but it may help to understand how this works.

Normally, an OSGi bundle needs to explicitly import all packages it needs. To work around this requirement, the plugin framework generates the list of required packages by scanning the class files inside your plugin and determining which packages they use. Once this list of packages is determined, the plugin system generates an OSGi manifest and inserts it into your plugin prior to loading it into the OSGi container. For more information, refer to OSGi Basics and how OSGI bundles are generated.

2.2 Customise package imports and exports with a bundle manifest

If you want to have a full control over what packages are imported by your plugin you can package the plugin as an OSGi bundle. To do this, you need to specify all necessary entries in a Manifest file inside your plugin JAR file. Using the Bundle Plugin for Maven makes the process of generating a valid OSGi manifest much simpler.

You might also want to configure a bundle manifest yourself if you want expose a set of packages as being exported from your plugin.

3. Check that components used by your plugin are available to OSGi plugins

The other important restriction for OSGi plugins is that they are only allowed to access those Spring components that are explicitly exposed by Confluence or other plugins. You can find the list of all components that are available to OSGi plugins under http://<baseURL>/admin/pluginexports.action. In this list, each Spring component is listed against the interfaces that it provides. In OSGi, every component must specify the interfaces it provides.

As with the exposed packages, the list of components attempts to cover all Confluence functionality but not to expose all the internals of the application. If your plugin uses the beans that are not exposed you should be able to find an exposed bean that provides the same functionality. As with the packages, this list is intentionally limited to try to improve plugin compatibility across releases of Confluence.

It is very important to ensure that plugin code does not depend on beans that are not exposed, as the problem will only manifest itself during runtime. The easiest way to ensure that there are no dependencies on beans which are not exposed is to use constructor injection. Using constructor injection will ensure that the plugin fails during the loading if any of the dependencies are not satisfied.

As OSGi plugin components live in their own Spring context separate from Confluence's Spring container, you cannot use ContainerManager.getComponent() to retrieve your own plugin components (see PLUG-280)

3.1 Specify qualifiers on ambiguous Spring dependencies

In some cases, Confluence exposes more than one bean under the same interface. When this happens, Spring can't determine exactly which bean to use to satisfy a dependency on that interface. For example, there are two exposed beans that implement the PluginController interface. Spring will fail to inject the right dependency unless you provide a Spring @Qualifier annotation.

// Confluence has two beans that implement PluginController, so we add a qualifier to specify which one we want
public void setPluginController(@Qualifier("pluginController") PluginController pluginController)
{
this.pluginController = pluginController;
}

3.2 Expose your plugin components to other plugins

In order to make a component in your plugin available to other plugins you can simply add the public="true" attribute to the component in your plugin descriptor file. You will need to specify one or more interfaces under which this bean will be exposed.

<component key="pluginScheduler" class="com.atlassian.sal.core.scheduling.TimerPluginScheduler" public="true" >

<interface>com.atlassian.sal.api.scheduling.PluginScheduler</interface>

</component>

3.3 Import components exposed by other plugins

Components that are exposed by other plugins are treated a little differently to beans that are exposed by Confluence itself. Your plugin needs to specifically import components which come from other plugins. To do this, include a <component-import> tag inside atlassian-plugin.xml file.

<component-import key="loc" interface="com.atlassian.sal.api.license.LicenseHandler" />

You will also need to ensure that the component class is imported, which usually happens transparently.

4. Advanced configuration with Spring configuration files

The new plugin framework provides the ability to create plugin components using complete Spring configuration files. If you provide Spring Dynamic Modules (Spring DM) configuration files in META-INF/spring/, these will be loaded into your plugin OSGi bundle by the Spring DM loader. Using this option for configuration provides you with a lot of flexibility about how your plugin components are created and managed.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"
  default-autowire="autodetect">

  <beans:bean id="repositoryManager" class="com.atlassian.plugin.repository.logic.ConfluenceRepositoryManager"/>
  ...
</beans:beans>

To include a Spring configuration file, ensure it is included in the META-INF/spring/ directory inside your plugin JAR file. All files matching *.xml inside this directory will be loaded as Spring configuration files when your plugin is loaded. For more details on Spring configuration, see the Spring documentation.

5. Confluence API limitations

  • As mentioned above, you cannot use ContainerManager.getComponent() to retrieve your own plugin components. Instead, you should use dependency injection.
  • VelocityUtil.getRenderedTemplate() uses Confluence's Class loader. Therefore, you cannot use it to access your plugin's templates. See CONF-14459 for a workaround.
RELATED TOPICS

Writing Confluence Plugins

Plugin Framework Developer Guide

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport