Java API Changes in JIRA 5.0

As part of clearly defining the public, supported Java API for JIRA, several third-party libraries have been upgraded, previously deprecated classes have been removed and existing plugin points have changed behaviour and/or interface.

Changes in JIRA 5.0's core Java libraries, which differ from JIRA 4.4.x and earlier, are indicated in our Clirr report. This report is likely to change throughout the development of JIRA 5.0. If you are updating or developing plugins for JIRA 5.0, you may wish to check this page periodically.

On this page:

Upgrade to Lucene 3.2

Upgrading to Lucene 3.2 has caused a number of changes to the search interfaces of JIRA. The most notable is the replacement of the HitCollector class by the Collector class. There are also a number of smaller changes.

HitCollector replaced by Collector

Search methods on the com.atlassian.jira.issue.search.SearchProvider class now take a org.apache.lucene.search.Collector where previously they took a HitCollector.

If you previously had a class that implemented HitCollector you will need to modify it to implement the Collector interface. For most applications this is simple. See the example below.

class NextPreviousHitCollector extends Collector
    {
        private final List<Integer> docIds = new ArrayList<Integer>();
        private int docBase;

        NextPreviousHitCollector(final int currentIssueDocId)
        {
            this.currentIssueDocId = currentIssueDocId;
        }

        @Override
        public void collect(final int i)
        {
            int doc = docBase + i;
            docIds.add(doc);
        }

        @Override
        public void setScorer(Scorer scorer) throws IOException
        {
            // Do nothing
        }

        @Override
        public void setNextReader(IndexReader reader, int docBase) throws IOException
        {
            this.docBase = docBase;
        }

        @Override
        public boolean acceptsDocsOutOfOrder()
        {
            return true;
        }
    }

The notable points here are that more than one reader may be used by the search and that the document index returned in the collect method is related to the document base of the reader. You need to remember the document base that is set on the call to setNextReader and add this to the value passed in the collect(int i) method.

The acceptsDocsOutOfOrder() method should generally return true as the actual ordering of the documents in the index is not relevant to JIRA in any way.

You should of course consult the Javadoc for the Collect interface.

IndexSearcher replaces Searcher

The SearchProviderFactory.getSearcher() method now returns org.apache.lucene.search.IndexSearcher instead of a Searcher. The methods of IndexSearcher are largely compatible with those of the previous searcher interface, but some require an additional parameter specifying how many results you wish to have returned. You can specify Integer.MAX_VALUE but performance may be better if the actual maximum number of results you want is specified.

SortComparatorSource is replaced by FieldComparatorSource

Implementations of the NavigableField interface must now return a FieldComparatorSource from the getSortComparatorSource() method.

The FieldComparator interface, which is returned by a FieldComparatorSource, is quite different from the previous SortComparator, but as most abstract implementations of NavigableField already implement this method, it is unlikely to affect most plugin developers.

Removal of OSUser

With the introduction of Embedded Crowd in JIRA in 4.3, many methods which referred to the old org.opensymphony.user.User and org.opensymphony.user.Group classes were deprecated and a thin emulation layer implemented to allow a simplified transition for plugin developers to the new Crowd User and Group classes.

In JIRA 5.0, this emulation layer has been removed. Consequently, org.opensymphony.user.User and org.opensymphony.user.Group and a small number of supporting classes have been removed. Furthermore, all deprecated methods have been either changed to use the new Crowd User and Group classes or removed (where they were simply duplicated by new Crowd versions).

Most plugins should just need recompiling.

Special Cases

The OpenSymphony Group class had methods for getting the members of the group and for adding and removing members. The OpenSymphony User class had similar methods for finding the groups a user was a member of. Use the new GroupManager component for getting members of a group or adding user to a group. For more low-level manipulation of groups and memberships use the CrowdService component.

Changed Exceptions

Exceptions thrown by some methods may have changed. Hence, plugin developers will need to adjust their code accordingly.

OpenSymphony classes generally threw EntityNotFoundException when a user or group was not found. The Crowd implementation will return null if a user or group is not found. So in places where you caught EntityNotFoundException, you will now need to test the return value for null.

Removal of Portlets

JIRA 4.0 introduced gadgets to replace the old portlets that were used in earlier versions of JIRA. From JIRA 4.0, we still shipped all the code for portlets to:

  • Allow customer's existing portlets to continue to function on the new dashboard introduced in JIRA 4.0 and
  • Give plugin developers time to upgrade their plugins to use gadgets instead.

In JIRA 5.0, we've finally removed all portlet-specific code from JIRA plugin points and APIs. Some plugins implement upgrade tasks to convert portlets to gadgets. These upgrade tasks may now fail if they depend on portlet specific APIs. These upgrade tasks should either be removed (since upgrading to JIRA 4.0+ should have removed portlets already anyway), or rewritten so that they do not rely on the portlet API.

Rendering of Issue Tab Panels

JIRA 5.0 changes the way in which tabs are rendered on the view issue page. Previously the <issue-tabpanel> module was always rendered in the same request as the View Issue screen. In JIRA 5.0, however, the issue tab panel HTML may returned by an AJAX call and inserted into the view issue page without triggering a full page reload. This has two important implications:

  1. calling WebResourceManager.requireResource(String) does not include the web resource in the page when the user switches tabs, and
  2. Javascript callbacks that are registered with AJS.$(document).read() will not be called when the user switches tabs.

Use of WebResourceManager.requireResource(java.lang.String)

Since IssueTabPanel.getActions(Issue, User) is now called in a separate request from the one that renders the view issue page, calling WebResourceManager.requireResource(java.lang.String) does not guarantee that the resource will be included on the view issue page. To make sure that resources are included, use the jira.view.issue web resource context in your <web-resource> definition as in the following example.

<web-resource key="myPluginViewIssue" name="MyPlugin's Javascript for the View Issue page">
    <context>jira.view.issue</context>
    <dependency>jira.webresources:viewissue</dependency>
    <resource type="download" name="myPluginViewIssue.js" location="script/myPluginViewIssue.js"/>
</web-resource>

Use of AJS.$(document).ready()

If your plugin contains Javascript that needs to execute when the tab is loaded, it should register a callback with JIRA.ViewIssueTabs.onTabReady() instead of using AJS.$(document).ready(). For example, if a JIRA 4.4 plugin contains the following Javascript file:

AJS.$(document).ready(function () {
    AJS.$('.tab-content').find('.project-activity:gt(0)').addClass('hidden');
})

This file should be changed to the following in order to work in JIRA 5.0.

JIRA.ViewIssueTabs.onTabReady(function() {
    AJS.$('.tab-content').find('.project-activity:gt(0)').addClass('hidden');
})

Custom Field Types

Class Hierarchy Changes

The class hierarchy of the various CustomFieldType classes has been changed. The following diagrams show the old hierarchy and new hierarchy. Removed relationships are marked in red, and new relationships are marked in green.

Old CustomFieldType Hierarchy

New CustomFieldType Hierarchy

What will break?
Please check your instanceof's comparisons to ensure they are still behave as expected.

Generics

CustomFieldType has now been parameterized based on its Transport Object. This should hopefully simplify implementations and make it much easier to figure out whats going on. The generics are defined as follows:

public interface CustomFieldType <T, S>

T represents the Transport Object of a Custom Field Type. This is can be thought of as the in-memory data representation, or logical representation. For example this could be a User or a Project, or maybe a Collection of Users. T can contain a single data type or an aggregate data type such as a Collection. JIRA needs to know what is inside this aggregate. S represents the single form of the T. In the case where it is a single data type, T and S will be the same.
Currently we support the following aggregate Transport Objects: Collection<S>, Map<String, S> or Map<String, Collection<S>>. N.B. Support for CustomFieldParams as the Transport Object has been deprecated since 5.0.

We have two abstract class which we recommend you use to help simplify some of this.

public abstract class AbstractSingleFieldType<T> extends AbstractCustomFieldType<T, T>
public abstract class AbstractMultiCFType<S> extends AbstractCustomFieldType<Collection<S>, S>

What will break?
Because we have typed all of our own CustomFieldType implementations, this means you may get method signature compilation problems if you are extending a concrete class. E.g. a NumberCFType now returns Doubles where it used to return Object.
Because of the large number of plugins extending the StringCFType and the TextCFType we have not genericised these classes, to assist with backwards compatability. However, we strongly suggest migrating to the new GenericTextCFType class.

Deprecations

StringCFType - use GenericTextCFType instead
TextCFType - use GenericTextCFType instead
AbstractMultiSettableCFType - implement the methods yourself.
Using CustomFieldParams as your Custom Field Transport Type (T or S) - use one of the other aggregates supported (see above)
Transform methods on the CustomFieldParams - CustomFieldParams should only be containing Strings, so therefore these should not be used.

CascadingCustomField

The CascadingCFType has had a complete overhall. It was incorrectly using the CustomFieldParams map so now has been updated to use a Map for representation of the selected Options. Database storage remains the same.  If you were extending or using this class there is a high chance of breakage. 

AbstractMultiCFType

AbstractMultiCFType received a bit of an overhall in 5.0. It used to assume that everything was stored as a String, but this is no longer the case.  This class has also been further parameterized.  This has resulted in:

  • Method signature changes (deal with Object rather than String)
  • String specific implementations have been removed (you will have to provide these now yourself if required)
  • New parametrized methods to be implemented that will hopefully simplify behaviour.
  • Method convertToStringsIfRequired no longer does the "if required" bit, it will now always convert to strings.
  • It no longer implements SortableCustomField by default.  Include this yourself if required
Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport