App Data SPI
SPI API reference
If you need more technical information on the latest SPI packages and interfaces, please read the complete API reference.
Get started
Jira apps can introduce custom functionality that extends standard Jira functions. Examples can be found in the ScriptRunner app that provides functions, such as Listeners, which are “listening” for a specific event and triggering an action, and Behaviors customizing how fields “behave” based on the context, etc. Since this type of app functionality is not categorized and guided by the Jira application development platform, we refer to it plainly as “app data.”
The Service Provider Interface for app data provides the tools to move such configurations between different Jira instances. When an app implements this SPI integration point, Configuration Manager for Jira will allow its users to include the app data when creating a snapshot. You can learn more about migrating app data with CMJ from this document.
This integration point is available since SPI v. 1.8.0 and requires Configuration Manager v. 6.11.0
App Data interfaces
The SPI for app data consists of four interfaces:
AppDataHandler
/**
* Implement this interface to handle export/import for app data.
* <p>
* The implementation of this interface is responsible for serializing/deserializing app data and recording references to Jira configuration
* elements like app filter type option IDs, etc.
* <p>
* In case the annotation-based approach for handler declaration is chosen, the implementation of this interface should be annotated with
* {@link com.botronsoft.cmj.spi.annotations.handlers.ОbjectsEnumeratedBy} to describe app's types and objects that could be
* exported/imported.
*
* @see ConfigurationReferenceCollector
* @see ConfigurationReferenceLookup
*/
@PublicSpi
public interface AppDataHandler {
/**
* Invoked when an app data is being exported. This method will be called by Configuration Manager for each app with implementation of
* this handler.
* <p>
* Implementers may call {@link com.botronsoft.cmj.spi.configuration.ConfigurationReferenceCollector} methods to handle references to
* other configuration objects.
*
* @param selectedObjectIds
* map of the selected objects for export by the user with their type as a key (see {@link AppDataObjectType#getId()}) and
* object ids as values (see {@link AppDataObject#getObjectId()}).
* @param exportContext
* context of the export operation.
* @return the exported app data serialized as a string.
* @see ExportContext
*/
Optional<String> exportData(Map<String, List<String>> selectedObjectIds, ExportContext exportContext);
/**
* Invoked when an app data is being imported. This method will be called by Configuration Manager for each app with implementation of
* this handler.
* <p>
* Implementers can call {@link com.botronsoft.cmj.spi.configuration.ConfigurationReferenceLookup} methods to retrieve the respective
* matching app objects referred with {@link com.botronsoft.cmj.spi.configuration.ConfigurationReferenceCollector}.
*
* @param data
* the app data as serialized by {@link AppDataHandler#exportData(Map, ExportContext)
* AppDataHandler#exportData(Map<String, List<String>> selectedObjectIds, ExportContext exportContext)}.
* @param importContext
* context of the import operation.
* @see ImportContext
*/
void importData(String data, ImportContext importContext);
An app may declare only one implementation of this interface.
Below, you will find details about how the different methods of this interface will be invoked by Configuration Manager.
exportData is invoked when a project snapshot is created, and the app data of an app that implements this interface is selected for export. The method will be invoked during a system snapshot creation if the "Export Global App Data" option is selected. The method should return an empty Optional if no app data currently implements the AppDataObject interface. Otherwise, the export method will receive the ids of the selected objects for each object type and should return all data objects serialized as а string.
importData is invoked when a snapshot with app data for a given app is being deployed, i.e., if exportData returned a non-empty Optional during snapshot creation.
Why is the exportData method called during deployment?
A major phase of the deployment process in CMJ is the deep change and impact analysis. To perform it, CMJ compares Jira's configuration between the source and the target instances. The snapshot being imported is a model of the source Jira configuration built at the time when it was exported. So, CMJ creates another model of the target instance by exporting Jira configuration on that instance at the time of the deployment.
The exportData method is called during deployment so that the app data on the target instance is included in the target instance model and compared to the app data in the snapshot during the Analyze phase.
AppDataObjectType
The AppDataObjectType interface needs to be implemented to represent the types of objects an app has. For example, in the ScriptRunner app, Listeners, Behaviors, and REST Endpoints are app data object types (or simply “object types”). A single implementation can be used for all types provided by a given app.
/**
* Represents an app's object type.
*
* @see AppDataObjectEnumerator
*/
public interface AppDataObjectType {
/**
* Returns the id of the specific object type. The id of the type must be unique across all types for this app.
*
* @return the id of the object type
*/
@NotNull
String getId();
/**
* Returns the name of the specific object type.
*
* @return the name of the object type.
*/
@NotNull
String getName();
/**
* An icon for the object type. It should be in Base64 format.
*
* @return the icon of the object type in Base64 format.
*/
@Nullable
String getIcon();
}
ID - the ID provided for each object type must be unique across all objects type.
Name - the name of the object type will appear on the apps' selection screen during snapshot export in CMJ.
(optional) An icon - the icon will be displayed together with the name of the object type in CMJ. The expected format for the icon is a base64 encoded image.
AppDataObject
The AppDataObject interface needs to be implemented to represent specific app data objects (also referred to as “objects” in this document) of any type. For example, in the ScriptRunner app, these are all the listeners that are defined by a user under the Listeners type.
A single implementation can be used for all objects provided by a given app.
/**
* Represents an app's object.
*
* @see AppDataObjectEnumerator
*/
@PublicSpi
public interface AppDataObject {
/**
* Returns the id of the app's object type that this object is associated with.
*
* @return the id of the app's object type.
*/
@NotNull
String getTypeId();
/**
* Returns the id of the app's object. The id of the object must be unique within its type. A list of the selected objects' ids with
* their types' ids will be provided to the app's handler in {@link AppDataHandler#exportData(Map, ExportContext)}
* AppDataHandler#exportData(Map<String, List<String>> selectedObjectIds, ExportContext exportContext)}.
*
* @return the id of the app's object.
* @see AppDataHandler
*/
@NotNull
String getObjectId();
/**
* @return the name of the object.
*/
@NotNull
String getName();
/**
* @return the description of the object.
*/
@Nullable
String getDescription();
}
For each object, you need to provide:
ID - the ID for each object must be unique across all objects of the same object type.
typeID - the typeID property of the object is used to identify the type of the object. This means the typeID of the object must be the same as the ID of its object type.
Name - this is the name given by the users to an individual object when they defined it;
(optional) Description - the description given by the users when they defined the individual object.
AppDataObjectEnumerator
The implementation of the AppDataObjectEnumerator interface is responsible for providing a list of the app data object types and app data objects during snapshot creation.
Below, you will find details about how the different methods of this interface will be invoked by Configuration Manager.
getObjectTypes is invoked for each app during creation and should return a list of all types of objects that the app provides. A type of object is represented by an instance of the class implementing the AppDataObjectType interface, e.g. from the ScriptRunner app - Listeners, Behaviours, REST Endpoints, etc.
CMJ will display an expandable list of all app data object types for each app.getObjects is invoked for each app during export and should return a list of all objects of a given type (as specified by the type’s name from the method arguments). The objects are represented by instances of a class implementing the AppDataObject interface.
CMJ will display a list of app data objects under the related app data object types.
Configuration serialization and versioning
The SPI does not impose any restrictions on how the configuration in each property value is serialized, only that the end result should be a String.
Collect references
The global app data may contain references to other configuration elements in Jira, such as custom fields, saved filters, etc. It is important that during export, Configuration Manager is notified about these references because the IDs of these elements may be different between the source and the target instance and Configuration Manager will match them and provide the correct IDs during import.
This can be achieved by invoking the methods of the ConfigurationReferenceCollector interface, accessible via the ExportContext interface. The key must be a unique identifier, which will be used when importing the configuration to resolve the reference on the target instance. The keys need to be unique only within the type of the configuration element - i.e. you can use the same key for a resolution or a status. In this sense, the Jira internal identifiers can be used as keys as well.
Resolve references
When deploying a snapshot, all collected references to other configuration elements must be resolved, because the identifiers of these elements are most probably different on the target system.
Use the ConfigurationReferenceLookup interface, accessible via the ImportContext interface. It provides convenient methods for resolving references by the same keys which were provided when collecting these references. The methods return java.util.Optional, because, in certain situations, references may be unresolvable. SPI implementations should be developed to handle this possibility.
Register the handler with Configuration Manager
There are two options for registering the handler with Configuration Manager:
Via Java annotations
Annotate the handler class with the ConfigurationManagerSpiHandler and ОbjectsEnumeratedBy annotations. The implementations of AppDataObjectEnumerator are provided with the ОbjectsEnumeratedBy annotation.
In addition, the following entry needs to be added to the atlassian-plugin.xml which specifies the Java packages which contain the handlers:
Only one entry is needed per app - multiple packages may be defined if required. Configuration Manager will scan the classes in these packages for the SPI annotations.
Via atlassian-plugin.xml
Another approach is to declare the handler in atlassian-plugin.xml directly - create an <appDataHandler> tag with the attributes below.
Here is the list of supported attributes:
Attribute | Purpose |
---|---|
The unique key of the SPI implementation. Required: yes | |
The implementation class - must extend the com.botronsoft.cmj.spi.appdata.AppDataHandler Required: yes | |
The enumerator must implement the com.botronsoft.cmj.spi.configuration.AppDataObjectEnumerator interface. |