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, vendor of an app called "X" might develop a PC 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 PC, 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 PC and X) in order to enable 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 PC extension

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

Version compatibility

All PC extensions must declare which version of PC 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>
XML

Note the min-pc-version-supported parameter. This will be interpreted as the extension in this app is compatible with PC 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 PC 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 PC.

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>
...
XML

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>
...
XML

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 PC). 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

<build>
	<plugins>
		<plugin>
			<groupId>com.atlassian.maven.plugins</groupId>
			<artifactId>jira-maven-plugin</artifactId>
			<version>${amps.version}</version>
			<extensions>true</extensions>
			<configuration>
				.....
		<!-- See here for an explanation of default instructions: -->
		<!--https://developer.atlassian.com/docs/advanced-topics/configuration-of-instructions-in-atlassian-plugins -->
			<instructions>
				.....

					<!-- Add package import here -->
					<Import-Package> 
						org.springframework.osgi.*;resolution:="optional",
						org.eclipse.gemini.blueprint.*;resolution:="optional",

						<!--Note the packages from PC must be in DynamicImport-Package,
						so the following exclusion is added here -->
						!com.awnaba.projectconfigurator.*,
						*
					</Import-Package>
					<!--Packages from Project Configurator must be imported dynamically to allow the extension to interact with them, even if PC is installed later -->
					<DynamicImport-Package>
 						com.awnaba.projectconfigurator.*
					</DynamicImport-Package>
					.....
XML

The Hollywood Principle

Creating an extension consists of implementing some of the interfaces which are defined in the extension-spi-XXXX.jar. You do not have to call the methods offered by those interfaces. PC 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. PC offers you some components that act as factories that create many of those objects. These include:

FactoryType 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 PC 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 PC, 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 PC (when export, import, or reporting operations are being run by PC), so that those Spring beans are instantiated only when they are needed, and PC 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.

@Profile("pc4j-extensions")
@Component
public class MyGadgetHookPoint implements GadgetHookPoint {
....
JAVA

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

  • HookPointCollection
  • GlobalCustomEntity
  • ChildCustomEntity

The rest of 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 PC dependencies into your extension

The PC 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:

@Profile("pc4j-extensions")
@Component
public class FooWorkflowExtensions implements HookPointCollection {
	...
	private SimpleReferenceProcessorFactory processorFactory;

	@Inject
	public FooWorkflowExtensions(@ComponentImport SimpleReferenceProcessorFactory processorFactory) {
 	 	this.processorFactory=processorFactory;
	 	...
	}
	...
}
JAVA