This tutorial applies to JIRA 5.0 and later.
Level of experience:
This is an intermediate tutorial. You should have completed at least one beginner tutorial before working through this tutorial. See the list of developer tutorials.
It should take you approximately half an hour to complete this tutorial.
On this page:
Overview of the tutorial
JIRA has supported a simple listener API for a long time, but that API has several problems. Installation and configuration are complex. Also, it's impossible (as of JIRA 4.0) to share code between listeners and plugins.
Fortunately, you can use the atlassian-event library to implement the same functionality and avoid these problems. This tutorial shows you how.
In this tutorial, you will create a plugin that listens for events generated when an issue is created or resolved. When the events occur, the plugin simply logs the event. Your completed plugin will consist of the following components:
- Java classes encapsulating the plugin logic.
- A plugin descriptor (XML file) to enable the plugin module in the Atlassian application.
When you have finished, all these components will be packaged in a single JAR file.
To complete this tutorial, you need to know the following:
- The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
- How to create an Atlassian plugin project using the Atlassian Plugin SDK.
- The basics of using and administering JIRA.
We encourage you to work through this tutorial. If you want to skip ahead or check your work when you have finished, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:
Alternatively, you can download the source as a ZIP archive by choosing download here: https://bitbucket.org/atlassian_tutorial/jira-event-listener.
Step 1. Create the plugin project
In this step, you'll use an
atlas command to generate stub code for your plugin. The
atlas commands are part of the Atlassian Plugin SDK, and automate much of the work of plugin development for you.
- If you have not already set up the Atlassian Plugin SDK, do that now: Set up the Atlassian Plugin SDK and Build a Project.
- Open a terminal and navigate to your project home directory.
Enter the following command to create a plugin skeleton:
- When prompted for the version to create the plugin for, choose 1 for JIRA 5.
As prompted, enter the following information to identify your plugin:
- Confirm your entries when prompted.
Step 2. Modify the POM metadata and add a dependency
The POM (Project Object Model definition file) declares your plugin's dependencies, build settings, and metadata (information about your plugin). Modify the POM as follows:
- Change to the
directory created by the SDK.
- Open the
pom.xmlfile for editing.
Add your company or organisation name and your website to the
Add the following
dependencyas a child of the
org.springframeworkversion should match the version used by the UPM.
We're going to use a custom
log4j.propertiesfile for this plugin. Add the following element as a child of the
pluginelement should look like this:
- Save the file.
Step 3. Register the plugin module in the plugin descriptor
The SDK can generate the code for many types of modules you can use in plugins. However, in the case of the event listener module, we'll need to add it by hand.
Let's start with the plugin descriptor. The plugin descriptor is an XML file that identifies the plugin to JIRA and defines the functionality that the plugin requires. Here's a basic plugin descriptor:
Notice that the SDK adds some ingredients that we won't pursue in this tutorial, such as plugin icons for the add-on management page. It doesn't hurt to leave these in, but this tutorial will leave icon customization as an exercise for you to explore later. Also note that some of the information from the POM is transferred to the plugin descriptor using variable names such as
Update the descriptor for the plugin as follows:
- Open the plugin descriptor file,
atlassian-plugin.xmlfor editing. It is located at
Add the following component declarations (as children of the
The first line imports the
EventPublisher instance used to register for events. The second line instantiates the listener class that we will write. To be eligible for dependency injection, your classes must be registered as
component plugin modules. We'll talk about this more later.
Now you are ready to write some code to make your plugin do something.
Step 4. Write the Java class
The recipe for creating an event listener is very simple:
- Inject the
EventPublisherimplementation into your class.
- Add an
@EventListenerannotation to any method that should receive events.
- Handle events as they come in.
To demonstrate this, we'll create a listener that will log a notification whenever an issue is created, resolved, or closed. It's easy to imagine how this same approach could be used to send notifications, say to email or IRC, but we'll leave those as exercises for you to try later.
In the following steps, you'll create the class file and build on it little by little.
Create a file named
IssueCreatedResolvedListener.javain the directory
src/main/java/com/example/tutorial/plugins/, and give it the following contents:
So far, all we've done is declared a few import statement and instantiated a logger. We'll use the logger to record our events as they happen.
Add a constructor that injects an
EventPublisherinstance into our plugin, which registers our
IssueCreatedResolvedListenerclass with the
You'll recall that we added a
component-importstatement to our plugin descriptor for
EventPublisher, the entry point to the atlassian-event library. The
EventPublisherobject handles publication of events and registration of event listeners. While this works for a demonstration, it isn't what you want to do in your actual plugin. We'll explain why in a moment.
Add a method to handle the event:
Notice that the method is annotated with
EventListener. You can apply the
EventListenerannotation to any public method. The method must take a parameter corresponding to the event it should handle,
IssueEventin this case. JIRA provides several events, as described on JIRA-Specific Atlassian Events.
Now create the custom
log4j.propertiesfile. This is the custom resource we added to the plugin's configuration in the POM. We need a custom log4j properties file because the JIRA log4j implementation doesn't know anything about our plugin's logger. And since our package name is
com.example.tutorial.plugins, we don't inherit any of the
Add the following
log4j.propertiesfile in a new directory under your project home,
The key lines are the last two. Instead of adding this file as an alternative build resource for your plugin, you can simply add the last two lines to the
log4j.propertiesfile that comes with the version of JIRA you are using.
Save any changed files, if you haven't already, since you'll try out your plugin next.
Step 5. Try it out
Now you're ready to try out the plugin:
- Open a terminal and change to the project root directory (where the pom.xml file is located).
Enter the following SDK command:
- When JIRA finishes starting up, open the JIRA home page in a browser.
- Create a new project based on the Software Development project type. This gives us the workflow we need to try out each event type, that is, one that includes transitions for resolving and closing issues.
While keeping an eye on the terminal where you started JIRA, try creating an issue and then resolving it. You should see an event logged to the terminal output, like this:
It works! However, it's not yet ready for prime time. We have a little more to do to make sure that our event listener plays well given the normal plugin lifecycle in a production deployment, as described next. You can keep JIRA running while you continue your work.
Step 6. Mind the plugin lifecycle
The code so far makes two naive assumptions about the plugin lifecycle:
- The constructor will only ever be called once, and
- The plugin will only shut down when the system itself shuts down
Neither is true. Plugins should expect to be enabled or disabled at any time. Since our plugin registers with an external service, this needs to be accounted for.
In the plugin system itself, Atlassian plugins are implemented as Spring dynamic modules, and the
atlassian-plugin.xml is transformed into a Spring XML bean configuration before it is loaded by the product. In most cases, plugin developers don't need to worry about this. In our case, we do. As a
IssueCreatedResolvedListener will become a Spring bean, so we can apply the Spring interfaces
DisposableBean. Spring guarantees that the bean – in this case, our listener class – will have a chance to get its act together before it's put into active service, or retired from it.
To coordinate our sample event listener with the plugin lifecyle, replace the
IssueCreatedResolvedListener.java contents with the following:
Here, we've moved the
EventPublisher reference to a member variable and bound the registration to the
destroy() methods, which come from
DisposableBean respectively. This guarantees the correct behaviour even when our plugin is disabled and later re-enabled, perhaps by an administrator using the Universal Plugin Manager.
Although writing a listener as explained here has the advantage of working inside a fully-fledged plugin, it doesn't support the properties feature of legacy (pre-4.0) JIRA listeners. In those listeners, administrators could set properties to configure a listener. For example, a listener that forwarded issue events to IRC might have properties for the IRC server, username and password to use. Atlassian event listeners don't automatically integrate with the JIRA listener configuration screen.
To make your listener configurable by a JIRA administrator, implement your own administration page that can configure not only your listener but any other information your users might need to input. For guidance on how to do this, see Writing an Admin Configuration Screen.