JIRA Plugin Lifecycle

Applicable:

This document applies to JIRA 5.0.

This page describes the lifecycle of a JIRA plugin, and the stages that a plugin can hook into to perform its initialisation tasks, closing down tasks, and so on.

For the more complex JIRA plugins, it can be difficult to know when in the JIRA plugin lifecycle it is appropriate to perform certain tasks. Also, depending on the scenario, the order of the stages in the lifecycle may change. This page will guide you through the complexities of the JIRA plugin lifecycle.

Note: Most of the information in this document is relevant to plugin framework version 2 plugins. The lifecycle of a version 1 plugin is much more limited.

On this page:

Related Content

Stages in the lifecycle

There are several stages in the plugin lifecycle, and in the lifecycle of your plugin's components, that you can hook into in your plugin.

During these stages, you can perform various tasks such as wiring up dependencies, initialising caches or programmatically configuring JIRA. It is important to note however that it is not always appropriate to perform some tasks at certain stages, because the state of JIRA and the plugins system may not be ready for certain tasks.

Note: What is a component? In this document, the term 'component' specifically relates to component plugin modules.

Below is a list of the stages, and notes about what you can, cannot or should not do at each stage.

Component initialisation

More advanced plugins will contain one or more components, typically to implement services, managers, stores or other things. When a plugin unit is loaded into the plugin system, its components must first be initialised before the plugin can be successfully registered. This happens when:

  • The plugins system is being started:
    • JIRA is started
    • A data export is being imported into JIRA
  • A plugin is being installed
  • A plugin, or the specific plugin module, is being enabled

There are three possible phases to component initialisation. The first is construction. This is typically where dependencies for your component are injected, if you are using constructor-based injection. Any basic initialisation of your component's fields also happens here. For example, defining caches.

The second and third phases are very similar: the execution of methods annotated with @PostConstruct, followed by the execution of the afterPropertiesSet method from the InitializingBean interface, in that order. Naturally, the third phase is only executed if your component implements the InitializingBean interface. These two phases are essentially equivalent, and the particular phase you wish to use depends on your Spring configuration. During these phases, all dependencies of your components have already been injected (whether you are using constructor-based or setter-based injection). For example, if your component is an event listener, you can register it with the EventPublisher (that you would have declared as a dependency) in your @PostConstruct method.

Note: @PostConstruct and InitializingBean are concepts provided by the Spring framework. For more information on them, see their documentation at @PostConstruct and InitializingBean.

Even though your component's dependencies have been injected, this does not necessarily mean that those dependencies are in a "ready-to-use" state. For example, your component might depend on a JIRA component which caches information about plugins. During your component's initialisation, there may still be other plugins which have not yet been initialised, and thus enabled in the plugins system. Therefore, accessing that JIRA component's methods will cause it to act without knowledge of other as-yet uninitialised plugins. To avoid this scenario, it is best to defer accessing of dependencies which require knowledge of the plugin system until after all plugins have been initialised and enabled. See the section on plugin system events.

If initialisation fails for any reason, the plugin will effectively be disabled. Initialisation may fail if the component takes too long to complete (the default timeout is 30 seconds). Therefore, it is not a good idea to attempt any potentially long-running tasks during component initialisation, such as connecting to an external resource, or loading/processing large amounts of data. If such tasks are required but can be asynchronous, you could instead create a scheduled task to perform them at a more convenient time.

Attempting to access a component's dependencies during the initialisation stage can also lead to deadlocks. For example, if there happens to be a cyclic dependency between one component and another, and one accesses the other during initialisation, then the Spring container will get into a deadlock attempting to wait for both components to become initialised. After the timeout expires (as above), the container will realise that both components could not be initialised, and the loading of the plugin will fail.

Component destruction

Along with component initialisation, component destruction is a key part of the lifecycle of a plugin's components. Destruction of components is required in order to make sure that the plugin can clean up after itself. For example, releasing resources, deregistering listeners. Component destruction occurs when:

  • The plugin system is being shut down:
    • A data export is being imported into JIRA
    • JIRA is shutting down
  • A plugin is being uninstalled
  • A newer version of the plugin is being installed over an existing one
  • A plugin, or the specific plugin module, is being disabled

There are two possible phases to component destruction: the execution of methods annotated with @PreDestroy, followed by the execution of the destroy method from the DisposableBean interface, in that order. Naturally, the second phase is only executed if your component implements the DisposableBean interface. These two phases are essentially equivalent, and the particular phase you wish to use depends on your Spring configuration.

Note: @PreDestroy and DisposableBean are concepts provided by the Spring framework. For more information on them, see their documentation at @PreDestroy and DisposableBean.

It is important that components properly destroy themselves so that JIRA is in a clean state afterwards. This is particularly important when a component is potentially referred to by another plugin or JIRA itself. For example, if a component registers itself as an event listener with the EventPublisher, then it is crucial that it deregisters itself during destruction. If this is not done, then the EventPublisher will hold onto the object past the time when the plugin's classes might be unloaded from the class loader. This can lead to memory leaks and other undesirable consequences.

Similar to component initialisation, there is also a timeout on component destruction (the default is 10 seconds). If destruction fails to complete in that time, the plugins system will log an error and continue.

In the scenario of the entire plugin system being shut down, the order in which plugins are destroyed is not guaranteed. Thus if your plugin's component is dependent on components from other plugins, and you need to access those components during destruction, this can cause errors.

Plugin system events

The events system used by JIRA and the Atlassian Plugins Framework, atlassian-events, fires events at different stages in a plugin's lifecycle. Internally, JIRA listens to these events to ensure the proper working of various system components, for example CustomFieldManager. Plugins can also listen to these events to execute tasks when something happens to a plugin or a plugin module (whoever that plugin belongs to).

PluginEnabledEvent and PluginModuleEnabledEvent

When a plugin has been successfully installed into the plugins system – that is, after it has been successfully initialised – the plugin system notifies all listeners that each plugin's modules have been enabled by firing the PluginModuleEnabledEvent. After all notifications have been made for a plugin's modules, the PluginEnabledEvent is then fired. Specifically, these events are fired when:

  • The plugin system is being started:
    • JIRA is started
    • A data export is being imported into JIRA
  • A plugin is being installed
  • A plugin, or the specific plugin module, is being enabled

Once these events have been fired, then from JIRA's (and the plugin system's) perspective your plugin is enabled. That is to say, any time JIRA asks the PluginAccessor for modules that are present and enabled, your plugin's modules will be returned. At this point in time, you can be sure that any internal dependencies that the plugin's components may use (that is, dependencies provided by your plugin to your plugin) will be in a "ready-to-use" state.

Due to the shortcomings of the component initialisation stage, a listener on the PluginEnabledEvent is the earliest time that you should attempt to do any serious wiring into JIRA. For example, if your plugin needs to ensure there are particular custom fields created in JIRA for the plugin to function properly, the plugin should attempt to create them during this stage, and not earlier.

If however your plugin has tasks which can be executed asynchronously, or even lazily, then that should be your preferred approach. The downside to performing those critical tasks in the PluginEnabledEvent phase is that, in some scenarios, your plugin will be notified that it is enabled, signalling the start of these tasks, but at the same time JIRA will allow requests to be made to your plugin. Thus, if the tasks take too long, and they don't correctly block JIRA from processing a request, then you can get into an unknown state.

Say for example that your plugin needs to load a lot of data into memory, and this process can take a while. If this task is performed when the PluginEnabledEvent is fired, then it is possible that while this task is executing, users are accessing JIRA and requesting information from your plugin. This is unfortunately a limitation of JIRA's plugin system at present. As previously stated, if you can defer this task to be executed asynchronously, or lazily when the first request comes in which requires it, then this will avoid problems. An alternative approach is to make the plugin unavailable to requests until the task is completed, but this requires a lot of defensive programming.

Finally, the order of these events being fired in relation to other stages in the lifecycle can change depending on the scenario you are running under. Consult the section on the different scenarios to get a better understanding of this.

PluginDisabledEvent and PluginModuleDisabledEvent

When a plugin is being removed from the plugins system, the PluginModuleDisabledEvent and PluginDisabledEvent events can be triggered to signal that this is happening. This will happen when:

  • The plugins system is being shut down:
    • A data export is being imported into JIRA
    • JIRA is shutting down
  • A plugin is being uninstalled
  • A newer version of the plugin is being installed over an existing one
  • A plugin, or the specific plugin module, is being disabled

Note: When disabling or uninstalling a plugin, the PluginDisabledEvent corresponding to that plugin is not intended to be caught by the plugin itself. It is provided primarily for JIRA itself and other plugins that may depend on the plugin. Typically, the sequence of events is:

  1. For each of the plugin's modules – PluginModuleDisabledEvent fired
  2. For the plugin's component – @PreDestroy, DisposableBean#destroy()
  3. PluginDisabledEvent fired

As you can see, the plugin you are disabling will no longer be around by the time the PluginDisabledEvent is fired. It can however be used for one plugin to be informed when another plugin is being disabled. This might be necessary if the functionality of one plugin depends on another.

As the component destruction stage is not suitable for all tasks relating to tearing down the state of a plugin, the best time to perform those tasks would be when hooking into a PluginModuleDisabledEvent. That will guarantee that all of your plugin's components will still be available (not destroyed). The event is also fired synchronously – component destruction will not begin until the listeners of the event have finished executing.

LifecycleAware#onStart()

The LifecycleAware interface is offered by the Shared Access Layer library (SAL) to assist plugin developers who want to expose public components to the plugin system and JIRA. It can be implemented by plugin components marked as "public" in the atlassian-plugin.xml file, to hook into the plugin lifecycle. Similar to the event listener model, any components that implement the onStart() method of the interface will have that method invoked at a particular stage in the lifecycle, depending on the scenario.

The onStart() method is guaranteed to be invoked on a component after that component has been through initialisation. However, The time at which it is invoked in relation to other stages depends on the scenario:

  • When JIRA is being started, or when a data export is being imported into JIRA (that is, the whole plugin system is being started) onStart() will be invoked last. That is, it will be invoked after:
    1. PluginModuleEnabledEvent is fired
    2. PluginEnabledEvent is fired
    3. JIRA's data is upgraded by JIRA
    4. JiraStartedEvent is fired
    5. Plugin upgrade tasks recognised by SAL are executed
  • When a plugin is being enabled, or when a plugin is being installed, onStart() will be invoked directly after initialisation. That is, it will be invoked before:
    1. PluginModuleEnabledEvent is fired
    2. Plugin upgrade tasks recognised by SAL are executed (in the "install" scenario)
    3. PluginEnabledEvent is fired

The unreliability of timing of this phase makes it a bad candidate for use beyond the most basic tasks. It may be used in order to schedule tasks with SAL's PluginScheduler, but it still might be best to perform those tasks in the same place that you perform other plugin initialising tasks. This issue is being tracked in the JIRA project on JAC: JRA-26358.

Plugin upgrade tasks

Plugins can define upgrade tasks that can be executed in order to bring JIRA's data up to date with a newer version of the plugin. This is achieved simply by defining components in the atlassian-plugin.xml which are public and implement the com.atlassian.sal.api.upgrade.PluginUpgradeTask interface from SAL.

If your plugin is complex and stores a lot of data – perhaps using the ActiveObjects (AO) plugin/library – then upgrade tasks will probably become a necessity as you release newer versions of your plugin. Therefore, it is useful to know when plugin upgrade tasks are executed in the lifecycle. A plugin's upgrade tasks can run when:

  • The plugin system is being started:
    • JIRA is started
    • A data export is being imported into JIRA
  • A plugin is being installed
  • A plugin is being enabled

An upgrade task component will only be executed if it has not been successfully executed before (that is, completing without throwing an Exception). SAL keeps track of successful upgrade tasks by using the value of getBuildNumber() on the PluginUpgradeTask interface.

Note: Depending on the scenario, the timing of the execution of upgrade tasks will change relative to other stages in the lifecycle. Specifically, when a plugin is being installed into a running instance of JIRA (for example, via UPM), the plugin's upgrade tasks will be executed after the PluginModuleEnabledEvent is fired for all modules, but during the PluginEnabledEvent for the plugin (SAL's PluginUpgradeManager begins the execution of upgrade tasks when it receives the PluginEnabledEvent). This means that, if a request comes into JIRA which requires a module from a plugin which is currently being installed, that request might be serviced while upgrade tasks are running. This differs from the scenario when JIRA is being started up or when data is being upgraded – in both of those cases, JIRA will block requests until plugin upgrade tasks have completed.

Due to this shortcoming in JIRA and the plugin system, we recommend that you use defensive blocking behaviour for plugins with potentially long-running upgrade tasks, to ensure that your plugin cannot be accessed while upgrade tasks are running. This becomes less of an issue if your plugin is only ever installed in JIRA when no users are accessing it, or by shutting down JIRA first – but these sorts of policies are tough to sell to customers.

JIRA events

JIRA itself fires several events during its own lifecycle. Most are for internal purposes and not relevant to plugin developers. However, they can be listened to, using the regular mechanism provided by Atlassian Events.

One event of note is the JiraStartedEvent event. It is triggered when:

  • The plugins system is being started:
    • JIRA is started
    • A data export is being imported into JIRA

In both of the above scenarios, the event is triggered as part of SAL's LifecycleManager start phase, where every LifecycleAware object is notified of the starting of the application. This means that, in theory, the JiraStartedEvent happens at the same time as:

  • any plugin upgrade tasks are executed, and
  • LifecycleAware#onStart() is executed on any known components.

Because the event is not triggered when a plugin is installed or enabled, this phase in the lifecycle is essentially a less-capable version of the LifecycleAware#onStart() phase, with the difference that it is event-based. For the same reasons as with the LifecycleAware#onStart() phase, it is probably safest to avoid using this phase, unless there is a specific reason why you need to execute only when JIRA is starting or data is being restored.

How different scenarios trigger different stages in the lifecycle

This section describes several scenarios which involve the plugin lifecycle. In each scenario, we list the sequence of stages and events to give you an idea about when each stage comes into play.

JIRA is starting up

Sequence of stages and events:

  1. For all components of all plugins – Constructor, @PostConstruct, #afterPropertiesSet
  2. For each plugin
    1. For each plugin module, PluginModuleEnabledEvent fired
    2. PluginEnabledEvent fired
  3. JIRA upgrades data
  4. JIRA re-indexes (possibly)
  5. The following happen "at the same time" (in serial, but not in determined order):
    • JiraStartedEvent fired
    • SAL PluginUpgradeTasks executed if necessary
    • LifecycleAware#onStart() executed
  6. JIRA Scheduler started
  7. JIRA responds to web requests

Data export is being restored

Sequence of stages and events:

  1. Plugin system is shut down
    1. For each plugin
      1. For each plugin component – @PreDestroy, DisposableBean#destroy()
      2. For each plugin module – PluginModuleDisabledEvent fired
    2. For each plugin – PluginDisabledEvent fired
  2. Data is imported into the database
  3. Plugin system restarted
    1. For all components of all plugins – Constructor, @PostConstruct, #afterPropertiesSet
    2. For each plugin
      1. For each plugin module, PluginModuleEnabledEvent fired
      2. PluginEnabledEvent fired
  4. JIRA upgrades data (if necessary)
  5. JIRA re-indexes (possibly)
  6. The following happen "at the same time" (in serial, but not in determined order):
    • JiraStartedEvent fired
    • SAL PluginUpgradeTasks executed if necessary
    • LifecycleAware#onStart() executed
  7. UI can be accessed again

Plugin is disabled via UPM

Sequence of stages and events:

  1. For each of the plugin's modules – PluginModuleDisabledEvent fired
  2. For the plugin's component – @PreDestroy, DisposableBean#destroy()
  3. PluginDisabledEvent fired

Plugin is enabled via UPM

Sequence of stages and events:

  1. For each of the plugin's components – Constructor, @PostConstruct, #afterPropertiesSet, LifecycleAware#onStart
  2. For each of the plugin's modules – PluginModuleEnabledEvent fired
  3. The following happen "at the same time" (in serial, but not in determined order):
    • Upgrade tasks are run (only if they have not been run previously)
    • PluginEnabledEvent fired

Plugin is uninstalled via UPM

Sequence of stages and events:

  1. For each of the plugin's modules – PluginModuleDisabledEvent fired
  2. For the plugin's component – @PreDestroy, DisposableBean#destroy()
  3. PluginDisabledEvent fired
  4. Plugin's classes removed from Class Loader

Plugin is installed via UPM

Sequence of stages and events:

  1. For each of the plugin's components – Constructor, @PostConstruct, #afterPropertiesSet, LifecycleAware#onStart
  2. For each of the plugin's modules – PluginModuleEnabledEvent fired
  3. UI-facing plugin modules can be accessed now
  4. The following happen "at the same time" (in serial, but not in determined order):
    • Upgrade tasks are run (only if they have not been run previously)
    • PluginEnabledEvent fired
  5. UI is accessible to the user who triggered the installation

JIRA is being shut down

Sequence of stages and events:

  1. Plugin system is shut down
    1. For each plugin
      1. For each plugin component – @PreDestroy, DisposableBean#destroy()
      2. For each plugin module – PluginModuleDisabledEvent fired
Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport