Custom Field Data SPI

On this page:

SPI API Reference

If you need more technical information on the latest SPI packages and interfaces, please read the complete API reference.

Get Started

This integration endpoint is designed for apps that provide new custom field types via the customfield-type module.

Apps need to use this SPI endpoint to migrate custom field values when:

  • Custom fields store values that contain references to other Jira objects.

  • Custom fields store values that contain references to custom app objects.

This integration point is available since SPI version 1.6.0

The custom field SPI endpoint will assure the custom field integrity is intact since the IDs of such referenced objects will be different between instances of Jira.

Apps do not need to implement this endpoint for custom field types which:

  • implement MultipleSettableCustomFieldType

  • or store data, which does not contain references to other objects and does not need to change between instances of Jira

In both cases above, Configuration Manager for Jira will automatically take care of such custom field values.

CustomFieldDataHandler

The SPI for custom field data consists of only one interface - com.botronsoft.cmj.spi.issue.CustomFieldDataHandler.

/** * Interface for migrating custom field values. Values for custom fields can be stored in two places - the current value of the issue and * also values in the issue history. For this purpose there are methods in this interface for converting both current values and history * values. * * @param <T> * the type of the value - this should be the same as T (transport object type) that is declared in the {@link CustomFieldType} * implementation in the app. */ @PublicSpi public interface CustomFieldDataHandler<T> { /** * Transforms an issue value for the given custom field to a serialized String. This method will be called by Configuration Manager for * each issue if it contains a value for this field. * * @param customField * the custom field for which the value is exported. The field is of type for which this handler has been registered. * @param issue * the issue for which the value is exported. * @param value * the value itself. * @param issueExportContext * the context of the export operation. * @return an {@link Optional} containing the exported value serialized as a String. This value will be passed to the * CustomFieldDataHandler{@link #transformIssueValueForImport(CustomField, Issue, String, IssueImportContext)} method on import. */ Optional<String> transformIssueValueForExport(CustomField customField, Issue issue, T value, IssueExportContext issueExportContext); /** * Transforms an issue history value for the given custom field to a serialized String. This method will be called by Configuration * Manager for each value in a history entry for this field. * * @param customField * the custom field for which the value is exported. The field is of type for which this handler has been registered. * @param issue * the issue for which the value is exported. * @param value * the value from the history entry. * @param valueString * the value string from the history entry. * @param issueExportContext * the context of the export operation. * @return an {@link Optional} containing the exported value serialized as a String. This value will be passed to * {@link CustomFieldType#getChangelogValue(CustomField, Object)} and * {@link CustomFieldType#getChangelogString(CustomField, Object)} when the history entry is imported. */ Optional<String> transformChangeLogValueForExport(CustomField customField, Issue issue, String value, String valueString, IssueExportContext issueExportContext); /** * Transform a previously serialized value for import. The resulting type must be the one declared in the {@link CustomFieldType} * implementation in the app. * * @param customField * the custom field for which the value is imported. The field is of type for which this handler has been registered. * @param issue * the issue for which the value is imported. * @param value * the value itself. * @param issueImportContext * the context of the import operation. * @return an {@link Optional} containing the value for this custom field type. */ Optional<T> transformIssueValueForImport(CustomField customField, Issue issue, String value, IssueImportContext issueImportContext);

This interface has one type parameter that should be set to the same class, which is used for the implementation of the CustomFieldType interface and is defined as the Transport Object type. If this is a multi-valued field type, T should be set to the collection, which defines the Transport Object.

The three methods of this interface will be invoked by Configuration Manager when:

  • transformIssueValueForExport - a project with issues is exported on the source system and there is a stored issue value for the field of the registered custom field type. The method will receive the custom field, issue, and value as parameters.

  • transformChangeLogValueForExport - a project with issues is exported on the source system and there is stored value for the field of the registered custom field type in the issue history (changelog). The format of this value may be different than the one used for issue value, and that’s why a separate SPI method is needed. The parameters value and valueString represent the value as stored in the changelog (see getChangelogString and getChangelogValue).

  • transformIssueValueForImport - a project with issues is imported into the target system. This method will receive the value as String and must return a Transport Object. The same method is invoked when importing history data.

If any of those methods return an empty java.util.Optional, the field value will be transferred as is.

Configuration Serialization and Versioning

The SPI does not impose any restrictions on how the custom field value is serialized, only that the result should be a String (see the return value of method transformIssueValueForExport and transformChangeLogValueForExport). Internally you can use XML, JSON, or whatever format is needed, as long as the app can deserialize it when the configuration is imported (see method transformIssueValueForImport).

When implementing the SPI, it is very important to think about configuration versioning in advance. Consider the following situation - source Jira has version 1.2 of your app, while target Jira has version 1.3, and there is a change in the way custom field value was serialized in between the versions. To ensure smooth user experience, make sure the SPI implementation in your app is backward compatible with previous versions.

Collecting References

Custom field values may contain references to configuration elements - either standard Jira configuration such as fields, custom field options, saved filters, etc., or custom configuration provided by the app and handled by some of the Configuration SPI endpoints.

Collecting references to standard Jira configuration elements

Configuration Manager must be notified about all references to standard Jira configuration elements during export because:

  • Configuration Manager must include these elements in the snapshot.

  • The IDs of these elements may be different between the source and target instance and Configuration Manager will match them and provide the correct IDs during import.

Collecting all references can be achieved by invoking the methods of the ConfigurationReferenceCollector interface, accessible via the IssueExportContext interface. The key must be a unique identifier that will be used when importing the issue data 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.

Collecting references to app configuration elements

The custom field values may also contain references to a configuration that is specific to the app and is handled by some of the Configuration SPI endpoints. There is no need to collect these references in this case, as the app itself will migrate them, but care must be taken to resolve them correctly when the values are being imported. More information on this is available in the next section.

Resolving References

When a snapshot is being deployed, all collected references to other configuration elements (either standard or custom) must be resolved, because the identifiers of these elements are most probably different on the target system.

Resolving references to standard Jira configuration elements

Use the ConfigurationReferenceLookup interface, accessible via the IssueImportContext 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 implemented to handle this possibility.

Resolving references to app configuration elements

Use the AppMappingContext to lookup resolve IDs of the app-specific configuration elements that have been created as part of the import by any Configuration SPI endpoints. More information on using the AppMappingContext can be found in Share references between SPI handlers.

Registering 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 annotation and the HandlesCustomFieldType annotation, providing the type key:

@ConfigurationManagerSpiHandler @HandlesCustomFieldType(typeKey = "com.mycompany.app:samplefield") public class SampleAnnotatedCustomFieldDataHandler implements CustomFieldDataHandler<String> { ... }

In addition, the following entry needs to be added to the atlassian-plugin.xml which specifies the Java packages which contain the handlers:

<!-- Configuration Manager SPI Handler Packages --> <configurationManagerSpiHandler key="configuration-manager-spi-handler-packages"> <package>com.mycompany.app.handlers</package> </configurationManagerSpiHandler>

Only one entry is needed per app - multiple packages may be defined if needed. The classes in these packages will be scanned by Configuration Manager for the SPI annotations.

Via atlassian-plugin.xml

Another approach is to declare the handler in atlassian-plugin.xml directly - create a <customFieldDataHandler> tag and the attributes below.

The configuration needed in atlassian-plugin.xml is fairly simple - create a <customFieldDataHandler> tag and the attributes below. Only one custom field data handler per custom field type is allowed.

Here is the list of supported attributes:

Attribute

Purpose

Attribute

Purpose

The name of the SPI implementation.

Required: no

The unique key of the SPI implementation.

Required: yes

The full key of the custom field type for which this SPI implementation applies.

Required: yes

The implementation class - must extend the com.botronsoft.cmj.spi.issue.CustomFieldDataHandler interface.

Required: yes