Creating an Admin Configuration Form
This tutorial describes how to create plugins that work across Atlassian products. It does so by creating a plugin that works with RefApp 2.12.0.
Level of experience:
This is an advanced tutorial. You should have completed at least one intermediate tutorial before working through this tutorial. See the list of tutorials in DAC.
It should take you approximately 2 hours to complete this tutorial.
On this page:
Overview of the tutorial
Along the way, this tutorial introduces these concepts:
- Shared Access Layer (SAL) API is a programming interface that exposes many common services to plugins, such as persistence and user authorization. It does so in a way that works with any Atlassian application.
- Atlassian Template Renderer (ATR) is a plugin that enables other plugins to render templates, typically Velocity templates.
- Atlassian User Interface (AUI) gives user interface elements the Atlassian look-and-feel.
- REST module forms the communication mechanism between the browser and the server.
Your completed plugin will consist of:
- Java classes encapsulating the plugin logic.
- Resources for display of the plugin user interface (UI).
- A plugin descriptor (XML file) to enable the plugin module in the Atlassian application.
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.
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 using the get source option here: https://bitbucket.org/atlassian_tutorial/xproduct-admin-ui-plugin.
Step 1. Create the plugin project
In this step, you 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.
- Open a terminal and navigate to the directory where you want to create your tutorial project directory.
Enter the following command:
When prompted, enter the following information to identify your plugin:
- Confirm your entries when prompted.
The command creates a directory named
xproduct-admin-ui-plugin, which contains all your initial project files. This tutorial refers to this directory as your project home. Unless otherwise indicated, all directory paths are relative to the project home.
Step 2. Review and tweak the generated project
It's a good idea to familiarise yourself with the generated project files. In this section, we'll check a version value and tweak the generated files. We'll also start up the RefApp and have a look around.
Add plugin metadata to the POM
The POM (Project Object Model definition file) is located at the root of your project and declares the project dependencies and other information.
Add some metadata about your plugin and your company or organisation.
- Edit the
pom.xmlfile in the root folder of your plugin.
Add your company or organisation name and your website to the
- Save the file.
Verify your host application version
As mentioned in the overview, the host application for the plugin you are creating in this tutorial is the RefApp. When you generated the stub files, a default version specification was included in your
pom.xml file for the RefApp. Take a moment and examine the dependency:
- If it's not already open, open the
- Scroll to the bottom of the file.
- Find the
This section lists the version of the RefApp and also the version of the
atlas-commands you are running.
- Verify that the RefApp version is the one you want. If you are not sure which version you need, check the version table at the top of this tutorial.
- Save and close the
Review the generated plugin descriptor
Your stub code contains a plugin descriptor file
atlassian-plugin.xml. This is an XML file that identifies the plugin to the host application (HOSTAPP) and defines the required plugin functionality. In a text editor or IDE (integrated development environment, such as Eclipse or IDEA) open the descriptor file located in your project under
src/main/resources. It should look like this:
Start and view the RefApp
Although you haven't modified the generated code yet, try starting the RefApp application now. It's always a good idea to check your work periodically by making sure the application compiles and starts up.
At the command line, change directory to your plugin project home (the
xproduct-admin-ui-plugindirectory created earlier).
This triggers the RefApp startup process. When finished, you should see output similar to:
Notice the URL for the RefApp. In this case, it is:
Open the RefApp in a browser and have a look around, although there isn't much to see yet.
Step 3. Add the web form servlet
Let's add a servlet to the RefApp that presents a webform in the RefApp UI. The form won't do anything yet, but it gives us something to add to as we go.
Create the servlet
Let's add a servlet to the project.
- Change directories to
- Make a new directory at that location, named
In the new directory, create a new file named
AdminServlet.javawith the following contents:
- Save and close the file.
Optionally, run the application again (using the
atlas-run command from the project home), just to make sure everything compiles and starts up okay.
Add a credential check
Our web application first checks whether the user is logged in. If not, it redirects the user to the login page. We use the SAL User Manager feature to make sure that the current user is an administrator, so we need to add this dependency to our project file.
pom.xml and add the following
dependency element alongside the other
dependencies elements, immediately after the packaging declaration.
With SAL added as a build dependency, we can import SAL and use it in our servlet.
- Navigate to the directory:
Add the following import statements alongside the existing statements:
Replace the entire
AdminServletclass declaration with the following:
doGet method, we check whether the user is logged in (that is, username is not
null) and that the user is an administrator, thanks to the
UserManager class. If either are false, we redirect the user to the application login page by calling the
redirectToLogin method, which we haven't implemented yet.
Add the following two methods,
getUri methods to the
AdminServlet class immediately after
Notice that the
getUri method returns the current URI to the
LoginUriProvider so that the user is redirected back to our admin UI when they are properly authenticated.
Render the form with the Atlassian Template Renderer
Now let's get to rendering! As mentioned in the Overview, we can use the Atlassian Template Renderer for that. Once again, before we can write the code, we need to tell Maven that we'll be using it.
dependencies section in the
pom.xml file and add the following
AdminServlet.java, add the following import statement to the existing ones:
We need to instantiate the renderer object in the constructor and call it in the
doGet method. Add the
renderer variable declaration to the existing ones and replace the entire
AdminServlet constructor and
doGet method with the following:
We've added a dependency on a
TemplateRenderer to our constructor. This is the renderer we use to display our form. In the
doGet method, after applying our authorization check, we set the response content type to
text/html and then make a call to the
templateRenderer, telling it to render our
admin.vm template. We will create the template shortly.
Modify the Atlassian plugin descriptor
Before we go any further, let's tell the plugin system about our servlet.
atlassian-plugin.xml for editing and add the following component import and servlet declaration statements. You can add it anywhere within the
atlassian-plugin element, such as after the existing servlet declaration.
With that done, let's test the login redirect logic we've added. Start the RefApp by running the
atlas-run command again and go to:
A login prompt should appear. Log in using the credentials admin/admin. Once logged in, you won't see much except a big exception saying that the
admin.vm file couldn't be found. Let's create it.
Step 4. Create the Velocity template
A Velocity template determines the HTML presentation produced by our application.
Create a Velocity template file named
admin.vm in the
src/main/resources directory. To start, we keep it simple. Add this to the file:
In your browser, refresh the admin page,
You should see something like this:
It's a start. But it's also, well, ugly. It's missing application template features, such as the Atlassian logo and menus. Let's fix that.
Add some decoration
To style the page, open
admin.vm for editing again and add this
meta tag. Add it to the
head element, after the existing
This tells the application that it needs to use the admin decorator around the body of this page.
Now the page looks like something the application would actually display.
Now we're getting somewhere. But the form itself still doesn't look so good. AUI to the rescue!
First we need to include AUI in our header. Add the following line after the meta tag in the
requireResource will include all the resources for the
web-resource module along with its dependencies.
But where did the
WebResourceManager come from? It was automatically added to the Velocity context by the
TemplateRenderer. It makes a few things that are commonly useful available, like the
WebResourceManager. We'll see another one that is included automatically below, when we get to internationalizing our template.
Next, we improve the appearance of the form by adding class attributes.
The changes were small. We added
class="aui" to the
class="text" to each of our text input boxes,
class="button" to our submit button, and
class="field-group" to our div tags. And for such little work we get:
A nice looking form in any Atlassian application, and consistent with the application look and feel!
We can also add internationalization support to our user interface pretty easily.
First, add a resource element specifying our i18n properties file to
Next, create the properties file that holds the label text for the form. Under the
src/main/resources/ directory, create the following directory path and file:
In the file, add this text:
These properties define the labels that will appear in our UI. For example, the label for the
time field is 'Time'.
Next, replace the hard-coded text values in the
admin.vm template with lookups for internationalized text.
$i18n is an instance of SAL's
I18nResolver, which comes automatically with
TemplateRenderer, just like the
WebResourceManager. We use it to resolve our message keys to actual text.
Feel free to experiment with the labels in the properties file. To localize an application for an actual deployment, the only thing left to do would be to get the properties file translated.
Now we have our basic UI. It's time to make it do something!
web-resource module in your
atlassian-plugin.xml file. Using this method, you can also specify dependencies on other
web-resource modules, like AUI.
Add this to
Now edit the
admin.vm template. Replace the existing
$webResourceManager.requireResource call with:
Populating our form is pretty easy. AUI supports jQuery, which we use to communicate with our REST resources. Start out by making a GET request to set the values of the input fields.
Create a file named
AJS.contextPath() (which might be something like "/jira", "/stash", or even an empty string, "") with the hard-coded relative URL of our REST resource. Then it queries the server to find the current config. Once done, it populates the name and time elements with our data.
Next we write the REST resource that serves the data.
Serve the configuration from a REST resource
pom.xml file and add these
dependency elements to the
Now we can write our REST resource class.
Retrieve the configuration with
Again, start with a simple class and build it up from there.
Create a new file named
ConfigResource.java in your project home directory:
Add the following:
This contains all the import statements we need, as well as a simple class constructor.
To populate the application web form, we issue a GET request to the resource that returns the data as JSON. JAXB serializes our configuration object for us, making life a bit easier.
First, add an inner class that encapsulates our configuration data.
Here we've created a class that will encapsulate a few properties, a
String and an
@XmlElement annotations are JAXB annotations. The
@XmlRootElement annotation declares that instances of this type can be serialized to XML, and with a little magic in the REST module, to JSON as well. The
@XmlElement annotations declare that the attributes should be treated as XML elements or JSON object properties.
If we were to serialize an instance of this class to XML, it would look like this:
And in JSON, like this:
Now let's implement the GET method, which returns a
Response with an instance of the
Config type we've just created as the entity.
This method first performs our now familiar user check.
We then construct a
Response with an entity that is returned from a call to a funky
transactionTemplate.execute method, providing it with an instance of
TransactionCallback. If you've used Spring's
TransactionTemplate, this should look familiar to you. For everyone spoiled by AOP, the summary is that we are accessing data that is coming from a database—well, it's not in Fisheye or Crucible, but work with me here!
To ensure that reads and writes don't clash and give us inconsistent data, we need to protect ourselves any time we access
PluginSettings data. The
TransactionTemplate frees us from needing to know the application-specific transaction creation and usage semantics. If we didn't use the
TransactionTemplate, we'd need code something like this:
TransactionTemplate takes care of managing the transaction for us, by calling
TransactionCallback to perform the real work. The return value of the
TransactionTemplate.execute method is the return value of calling
TransactionCallback.doInTransaction method, we use the
PluginSettingsFactory to create a reference to the global, application-wide settings data. We need to use the global settings object because we're configuring a plugin. If we were storing data associated with a project (in JIRA, Bamboo, Fisheye or Crucible) or a space (in Confluence), we'd instead use the
PluginSettingsFactory.createSettingsForKey method, where the
key is the project or space the configuration data should be associated with.
Once we have a reference to a
PluginSettings object, things are pretty easy.
PluginSettings can be thought of as a persistent map. Just use
get to retrieve data and
put to add or update values.
Step 6. Updating the configuration
Now that our form can be populated with our current configuration, we want to allow the admin user to update the configuration.
Add the following function to
admin.js, after the
populateForm function definition but before the call to
This is the function we call to update the server configuration. It's very minimal; we haven't included error checking and we really should be escaping quotes and other special characters when creating our data, but that is left as an exercise for the reader.
Now, we just need to hook that function up to our form. After the call to
Now, whenever the form is submitted, instead of the usual, default action of POSTing the form data, our
updateConfig() method is called and our updated configuration is submitted as a
PUT request to the configuration resource.
Update the configuration with
To handle the
PUT requests from the client, add this method to the
ConfigResource.java file you created earlier:
@Consumes annotation declares that we expect a request with a
application/json body. We expect JSON in a form that matches the serialized form of the
Config object we created earlier, so that the REST module can deserialize to a
Config object, which it then passes to the
Next, we do the authentication and authorization check to make sure no one can modify the configuration except admin users. Then, we wrap our modifications in
TransactionCallback, which we pass to the
transactionTemplate, just like we did when we were retrieving the configuration. Since
PluginSettings is just a persistent map, we just use the
put method to put the new configuration values into it and we're done.
Finally, we return
204 No Content, which tells the client that the update succeeded and there's nothing left to be said.
Wire things together
Finally, we need to add a few items to the
atlassian-plugin.xml file. We have used a few new services from SAL, which we need to import. We also need to add the
rest module, so that the plugin system knows about the REST resource and where to find it.
Add the following lines to
Step 7. Add menu items
application attribute will come to our rescue (mostly)!
To add a link in the 'Global Settings' menu in JIRA's administration area, add the following
To add a link in the 'Global Settings' menu in Fisheye's administration area, add the following
Looking carefully, you'll notice that the
key attribute isn't really a key. This is because Fisheye doesn't do any internationalization yet, so the value of the
key attribute will be the text that is displayed.
To add a link in the 'Plugins' menu in Bamboo's administration area, add the following
Bamboo has the same limitation as Fisheye, so again the
key attribute is the textual value we want to display.
To add a link in the 'Configuration' menu in Confluence's administration area, add the following
Astute readers will notice the suspicious lack of the
application tag on that
web-item element. Well, in Confluence, if you add the
application attribute, your
web-item won't show up at all. We'll leave it off here, which is safe because none of the other Atlassian applications have a
system.admin/configuration section—at least, at the time of this writing they don't.
To add a link in the 'General menu' in the RefApp's administration area.
That's it! We're done! Let's run it!
Step 8. Build, install and run the plugin
Start your RefApp application again by entering the
atlas-run command, or use a command below to try the application in another product. After the application starts, navigate to the plugin page by appending the plugin path to the base application URL. For example, for JIRA the path would be:
The startup commands are: