JIRA 4.0 : Writing a Plugin Upgrade Task for JIRA 4.0
This page last changed on Oct 01, 2009 by andreask@atlassian.com.
OverviewJIRA 4.0 will introduce a new dashboard, effectively making the Portlet Plugin Module obsolete. Legacy portlets will still be supported via a Legacy Gadget bridge; however, they will miss out on a lot of the new features that gadgets offer (e.g. the ability to share gadgets with other apps such as iGoogle). It therefore makes sense to convert portlets over to gadgets. Information about how to write a gadget can be found in the Gadget Development Hub, and specifically the page about gadgets and JIRA portlets. If you've converted a portlet to a gadget, you will most likely need an upgrade task to convert existing data of your users into the new format used by the gadget you have written. This page describes the process of creating such an upgrade task. Why an upgrade task?Portlets generally have some configuration data associated with them by their users. For example, the First Response Time chart portlet, available in the Charting Plugin, allows users to configure how many days previous to draw the chart for (among other things). For efficiency reasons, gadgets do not use the same storage mechanism as portlets do to store these user preferences. An upgrade task is thus needed to convert existing user data over to the new format required by the new gadget. Upgrade frameworkJIRA 4.0 introduces a new plugin framework (version 2.2 or later of the Atlassian Plugin Framework, affectionately known as 'Plugins2'), which provides an events system that lets plugins register to listen for certain events (such as a 'Framework started' event). JIRA 4.0 also bundles SAL, which already includes a plugin upgrade framework. SAL provides a plugin upgrade manager that listens for the 'Framework started' event and will look for Plugin Upgrade Tasks to run in order to upgrade data for plugins. What does all this mean? Effectively, plugin writers don't have to worry about providing an upgrade task framework. They can simply provide a Plugin Upgrade Task component and SAL will guarantee that their upgrade task is run on startup. ExampleLet's look at what needs to be done to run an upgrade task to convert the First Response Time chart portlet data over to gadget data. 1. Convert your Portlet to a GadgetPlease follow the documentation available in the Gadget Development Hub for this step, and specifically the page about gadgets and JIRA portlets. 2. Add dependency on SALFirst we'll need access to the SAL API in the charting plugin project. Add the following dependency to the plugin's pom.xml: pom.xml ... <dependency> <groupId>com.atlassian.sal</groupId> <artifactId>sal-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> ... Re-generate your IDE's project descriptor (mvn idea:idea or mvn eclipse:eclipse) after this step to allow you to access the new SAL API classes in your project. 3. Convert your plugin to Plugins2SAL is a Plugins2 bundle and your plugin will have to be converted to the Plugins2 format first before you can write an upgrade task that will be picked up by the PluginUpgradeManager. Gadgets are also only supported in Plugins2 bundles. There are generic instructions available for how to do this, but let's look specifically at the Charting plugin example. The only thing that is needed is to add the plugins-version="2" attribute in atlassian-plugins.xml: atlassian-plugins.xml <atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" system="true" plugins-version="2"> ... 4. Writing your upgrade taskNow that all the prerequisites are done, the Upgrade task for the plugin can be written. This class simply needs to implement the PluginUpgradeTask interface provided by SAL. Here's an example implementation: GadgetUpgradeTask.java package com.atlassian.jira.ext.charting.upgrade; import com.atlassian.configurable.ObjectConfigurationException; import com.atlassian.gadgets.dashboard.Color; import com.atlassian.jira.ComponentManager; import com.atlassian.jira.portal.OfbizPortletConfigurationStore; import com.atlassian.jira.portal.PortletConfiguration; import com.atlassian.jira.portal.PortletConfigurationImpl; import com.atlassian.jira.portal.PortletConfigurationStore; import com.atlassian.jira.propertyset.JiraPropertySetFactory; import com.atlassian.jira.upgrade.util.SimpleLegacyPortletUpgradeTask; import com.atlassian.jira.util.Consumer; import com.atlassian.jira.util.NotNull; import com.atlassian.jira.util.collect.EnclosedIterable; import com.atlassian.sal.api.message.Message; import com.atlassian.sal.api.upgrade.PluginUpgradeTask; import com.opensymphony.module.propertyset.PropertySet; import org.apache.log4j.Logger; import java.net.URI; import java.util.Collection; import java.util.Map; public class GadgetUpgradeTask implements PluginUpgradeTask { private static final Logger log = Logger.getLogger(GadgetUpgradeTask.class); private final PortletConfigurationStore portletConfigurationStore; private final JiraPropertySetFactory propertySetFactory; public GadgetUpgradeTask(JiraPropertySetFactory propertySetFactory) { //NOTE: Can't get the portletConfigStore injected here since it is not made available to plugins2 this.portletConfigurationStore = ComponentManager.getComponentInstanceOfType(PortletConfigurationStore.class); this.propertySetFactory = propertySetFactory; } /** * The build number for this upgrade task. Once this upgrade task has run the plugin manager will store this * build number against this plugin type. After this only upgrade tasks with higher build numbers will be run */ public int getBuildNumber() { return 1; } public String getShortDescription() { return "Upgrades legacy portlet configuration to new gadget user prefs."; } public Collection<Message> doUpgrade() throws Exception { final SimpleLegacyPortletUpgradeTask upgradeTask = new SimpleLegacyPortletUpgradeTask("com.atlassian.jira.ext.charting:firstresponsetime", URI.create("rest/gadgets/1.0/g/com.atlassian.jira.ext.charting:firstresponsetime/firstresponsetime.xml")); //First get all the portletConfigurations in the database. final EnclosedIterable<PortletConfiguration> iterable = portletConfigurationStore.getAllPortletConfigurations(); iterable.foreach(new Consumer<PortletConfiguration>() { public void consume(@NotNull final PortletConfiguration pc) { //for each portletconfiguration, check if it's key matches the portlet key we want to upgrade if (pc.getKey() != null && pc.getKey().startsWith(upgradeTask.getPortletKey())) { log.info("Upgrading portletconfig with id '" + pc.getId() + "'"); //first lets convert the preferences for this portlet to the new prefs format used for gadgets. final Map<String, String> prefs; try { prefs = upgradeTask.convertUserPrefs(pc.getProperties()); } catch (ObjectConfigurationException e) { throw new RuntimeException(e); } //then create essentially a copy of the old portletConfig. This new copy no longer needs to have //the portletKey and propertySet set to any values. It however does require the GadgetUri and user prefs to be set. final PortletConfiguration newConfig = new PortletConfigurationImpl(pc.getId(), pc.getDashboardPageId(), null, null, pc.getColumn(), pc.getRow(), null, upgradeTask.getGadgetUri(), Color.color8, prefs); //Now lets store this new config back to the database. portletConfigurationStore.store(newConfig); //clear out the old properties for this portlet removePropertySet(pc); } } }); return null; } private void removePropertySet(final PortletConfiguration pc) { final PropertySet livePropertySet = propertySetFactory.buildNoncachingPropertySet(OfbizPortletConfigurationStore.TABLE, pc.getId()); @SuppressWarnings ("unchecked") final Collection<String> keys = livePropertySet.getKeys(); for (String propertyKey : keys) { livePropertySet.remove(propertyKey); } } /** * Identifies the plugin that will be upgraded. */ public String getPluginKey() { return "com.atlassian.jira.ext.charting"; } } There are a few things to note about this implementation:
5. Register the upgrade taskNow we simply need to register the upgrade task as a component in the plugin: atlassian-plugin.xml ... <component key="gadgetUpgradeTask" name="Gadget Upgrade Task" class="com.atlassian.jira.ext.charting.upgrade.GadgetUpgradeTask" public="true"> <interface>com.atlassian.sal.api.upgrade.PluginUpgradeTask</interface> </component> ... The PluginUpgradeManager in SAL will automatically scan for components that implement the PluginUpgradeTask interface. Please note that they have to be declared as public="true". That's it. Simply re-package the plugin, deploy it to the instance of JIRA to upgrade and restart the JIRA instance. The plugin upgrade task should be executed when JIRA starts up.
|
![]() |
Document generated by Confluence on Oct 06, 2009 00:26 |