4. Common features of extensions

In this section, we address the technical aspects that are common to all three types of extensions: workflow, dashboard, and custom entity.

How to implement extensions

All the extensions are Java code (or other JVM-compatible language, like Groovy) to be deployed in Jira as a P2 Jira plugin (app). The extension can be an additional feature in an existing app or a standalone app. For example, a vendor of an app called "X" might develop a PCJ extension for the app as an added feature that either goes into the same .jar or into a separate .jar (called "X extension for Project Configurator") and then distributes it to the Marketplace alongside app "X". The same P2 app can contain an arbitrary combination of workflow, dashboard, or custom entity extensions.

The first option has one technical advantage: it is easier to access and manipulate objects created by app X from the same app than from a separate one. In this second case, app X would need to export the packages and classes that provide that access/manipulation functionality, and app "X extension for Project Configurator" would have to import and use them. The first option also offers a better, more frictionless user experience. End users can see that by installing compatible versions of X and PCJ, migration simply works without having to do anything else. On the other hand, if the extension is delivered in a separate app, the end user would have to install it (in addition to PCJ and X) in order to enable the migration of content from X. In favor of a separate app, if app X is already quite large in terms of project size, you may feel wary of adding a new feature to it.

How to identify a PCJ extension

All PCJ extensions will be identified by the key of the P2 app to which they belong.

Version compatibility

All PCJ extensions must declare which version of PCJ they are compatible with. This is done by adding a parameter to the app's atlassian-plugin.xml file, as shown below:

atlassian-plugin.xml

<?xml version="1.0" encoding="UTF-8"?> <atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2"> <plugin-info> <description>${project.description}</description> <version>${project.version}</version> <vendor name="${project.organization.name}" url="${project.organization.url}"/> <param name="plugin-icon">images/pluginIcon.png</param> <param name="plugin-logo">images/pluginLogo.png</param> <param name="min-pc-version-supported">3.8.0</param> </plugin-info> <!-- add our i18n resource--> <resource type="i18n" name="i18n"location="sample-extension-one"/> <!-- add our web resources--> <web-resource key="sample-extension-one-resources" name="sample-extension-one Web Resources"> <dependency>com.atlassian.auiplugin:ajs</dependency> <resource type="download" name="images/"location="/images"/> <context>sample-extension-one</context> </web-resource> </atlassian-plugin>

Note the min-pc-version-supported parameter. This will be interpreted as the extension in this app is compatible with PCJ version 3.8.0 or newer. Version strings are compared as defined in the Semantic Versioning 2.0.0 specification. This parameter is used to filter apps that contain PCJ extensions from those that do not. Therefore, if this parameter is not added to the atlassian-plugin.xml file, this app will not be treated as a valid extension by PCJ.

Maven dependencies

Your app will need access to the extension-spi jar to use all the interfaces and classes described in this document. Add this dependency to your pom.xml file:

pom.xml

... <dependency> <groupId>com.awnaba.projectconfigurator</groupId> <artifactId>extension-spi</artifactId> <version>${projectconfigurator.version}</version> <scope>provided</scope> </dependency> ...

Some extensions will have to use also the class ObjectAlias, which is defined in a different jar (operations-api). In rare cases, extensions might require other classes from this same jar. In any of these cases, add the following dependency:

pom.xml

... <dependency> <groupId>com.awnaba.projectconfigurator</groupId> <artifactId>operations-api</artifactId> <version>${projectconfigurator.version}</version> <scope>provided</scope> </dependency> ...

Importing packages

As defined in the Maven dependencies section, the extension will need to use some packages provided by Project Configurator. These will be available through OSGI. Functionally speaking, it would be inconvenient if the extension had to resolve those import packages when it is installed, as there is no guarantee that Project Configurator will be installed in Jira before the extension. The most sensible thing would be that those packages are imported when they are actually needed (this means when an export or import is going to be run by PCJ). From the OSGI point of view, this implies that those packages must be declared as "DynamicImport-Package" and not as "Import-Package".

This would be specified in the pom.xml file as:

pom.xml

The Hollywood Principle

Creating an extension consists of implementing some of the interfaces that are defined in the extension-spi-XXXX.jar. You do not have to call the methods offered by those interfaces. PCJ will call them at the right times during the export or import operations.

Remember!

"Don't call us; we'll call you."

Welcome, lazy developers!

In addition to the Hollywood Principle, you do not have to create all of the objects those methods return. PCJ offers you some components that act as factories that create many of those objects. These include:

Factory

Type it will create

Factory

Type it will create

extensionservices/SimpleReferenceProcessorFactory

extensionservices/ReferenceProcessorComposer

common/ReferenceProcessor<W>

customentities/IdentifierFactory

customentities/PartialIdentifier

customentities/InstanceIndependentIdentifier

How to create objects in an extension

The lifecycle of objects created by an extension is very important for a smooth user experience. If their creation is triggered before PCJ is installed in Jira, that would crash with a Java ClassNotFoundException, since those interfaces would not yet be available. On the other hand, we need those objects to have already been created when the export or import is running.

The SPI and the framework that uses it in PCJ are designed so that you can satisfy that lifecycle requirement following very simple guidelines, leveraging the power of the Atlassian Spring Scanner. You only need to annotate those classes to associate them with a dynamic application context. This context will be started when extensions are required by PCJ (when export, import, or reporting operations are being run by PCJ) so that those Spring beans are instantiated only when they are needed, and PCJ is guaranteed to be present and working at that moment.

Classes in the extension must be linked to the application context identified by pc4j-extensions, as shown in this code example.

More precisely, classes implementing the following interfaces must be available as Spring beans within the context pc4j-extensions:

  • HookPointCollection

  • GlobalCustomEntity

  • ChildCustomEntity

The rest of the classes in the extension may be Spring beans or not.

As this is a development within the context of a P2 Jira app, it is assumed that the extension app is using Spring and Atlassian Spring Scanner version 2.

How to inject PCJ dependencies into your extension

The PCJ extension will need to get an instance of the factory interfaces mentioned above. The implementations of these factories are available as Spring components. The usual methods of injecting a dependency in a component in Spring are sufficient. For example: