Using the JIRA REST java client: Introduction
Bear Giles | August 7, 2013This is the first of a series of articles on using the JIRA REST java client.
Use Cases
Why would we want to use the JIRA java client? Here are three simple examples.
Webapp Exception Catchall
How many of use have seen an exception catch page that contained a stack trace? A nicer page saying that an exception occurred? An even nicer page that claimed that the failure had already been reported as a bug?
How many of us believed the last item?
It’s not hard to capture useful information in the webapp’s default exception handler, e.g. the stack trace, the identity of the current user, a sanitized version of the GET, PUT or POST data, and a sanitized version of the session attributes. What do you do with this information afterwards? Squirrel it away somewhere that nobody remembers to check? Email it to the developers and hope they don’t immediately trash it? The team isn’t deliberately ignoring the reported bugs, the bugs are just falling through the cracks since they aren’t a part of the normal workflow.
What if the information could be entered into JIRA as both a ‘bug’ and as a one-point user story in the agile backlog? (The single point is to perform the initial investigation – the dev and testing teams should add their own tasks and story points for actually fixing the bug and testing it.) It’s a lot harder to overlook an item in your backlog than a bug in bug tracking system.
Implementation note: the logger should be smart enough to avoid flooding the system with duplicate reports. A good way to handle this is to create some type of signature, e.g., an MD5 hash of the stack trace, and storing it as a custom field in the JIRA issue. The exception handler can then check for an existing issue before creating a new one. Incident-specific information is added as an attachment for the first, oh, ten instances.
Vulnerability Assessment Tracking
Everyone regularly performs vulnerability assessments. (Sticks fingers in ears and sings la-la-la-la-I-can’t-hear-you). What do you do with the results?
JIRA is flexible and allows users to define their own issue types and fields. Integration with vulnerability assessments is easy if you create three new issues, call them application (what’s being tested), build (an individual run) and defect (an individual defect). A single defect can span multiple builds, and of course a single build can contain multiple defects.
Again we can add single-point backlog stories to evaluate the results of the imported vulnerability assessment.
General Workflow Engine
JIRA is not just a bug tracker or an agile tool or whatever – it is a general workflow engine that just happens to be configured as these items by default or with standard extensions. You can easily add new types with their own workflows. Therefore there is no reason why you can’t use JIRA as a workflow engine in your application – just hide it behind a service and nobody will care.
There are two benefits to this approach. First it’s usually a lot cheaper to reuse an existing library than creating your own. Second you can use the existing JIRA UI to peek into the back-end instead of writing your own UI.
Getting JIRA
We have to start by installing JIRA. We’ll want to do this for development and testing even if we have an existing instance.
Fortunately this is a straightforward process:
- Download JIRA from https://www.atlassian.com/software/jira/try/. The windows installer is on the main page, the OS X and Linux installers are available at https://www.atlassian.com/software/jira/download.
- Install it.
- Configure the admin user.
It is also possible to stand up a JIRA instance for maven integration tests. For details see the JRJC ‘test’ package.
JIRA requires a license after the trial period but the cost of an annual 10-user license is trivial. That license should be more than enough for client development.
Getting the JIRA REST Java Client (JRJC)
The easiest way to get the JIRA REST Java Client (JRJC) is to add the Atlassian repository to your settings.xml file. I add the repositories in a profile since this is easiest way to selectively enable it.
- <settings>
- <profiles>
- <profile>
- <id>atlassian</id>
- <repositories>
- <repository>
- <id>atlassian-public</id>
- <url>https://m2proxy.atlassian.com/repository/public</url>
- <snapshots>
- <enabled>true</enabled>
- <updatePolicy>daily</updatePolicy>
- <checksumPolicy>warn</checksumPolicy>
- </snapshots>
- <releases>
- <enabled>true</enabled>
- <checksumPolicy>warn</checksumPolicy>
- </releases>
- </repository>
- </repositories>
- <pluginRepositories>
- <pluginRepository>
- <id>atlassian-public</id>
- <url>https://m2proxy.atlassian.com/repository/public</url>
- <releases>
- <enabled>true</enabled>
- <checksumPolicy>warn</checksumPolicy>
- </releases>
- <snapshots>
- <checksumPolicy>warn</checksumPolicy>
- </snapshots>
- </pluginRepository>
- </pluginRepositories>
- </profile>
- </profiles>
- <activeProfiles>
- <!-- <activeProfile>atlassian</activeProfile> -->
- </activeProfiles>
- </settings>
<settings> <profiles> <profile> <id>atlassian</id> <repositories> <repository> <id>atlassian-public</id> <url>https://m2proxy.atlassian.com/repository/public</url> <snapshots> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> <checksumPolicy>warn</checksumPolicy> </snapshots> <releases> <enabled>true</enabled> <checksumPolicy>warn</checksumPolicy> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>atlassian-public</id> <url>https://m2proxy.atlassian.com/repository/public</url> <releases> <enabled>true</enabled> <checksumPolicy>warn</checksumPolicy> </releases> <snapshots> <checksumPolicy>warn</checksumPolicy> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <activeProfiles> <!-- <activeProfile>atlassian</activeProfile> --> </activeProfiles> </settings>
Since 2.0.0 is still under active development some developers may wish to download the source and build the packages locally so they can add missing functionality. The source can be obtained at https://bitbucket.org/atlassian/jira-rest-java-client/downloads and select the “tags” tab.
If you decide to build the packages locally you’ll need to use Maven 2, not Maven 3.
For more information visit the JIRA REST Java Client Library project site.
Connecting to the JIRA server
We can now connect to the JIRA server. Future entries will discuss how to do more useful things but for now it’s enough to just ping the server.
- package com.invariantproperties.sandbox.jira.example;
- import java.io.IOException;
- import java.net.URI;
- import java.util.ResourceBundle;
- import org.junit.Test;
- import com.atlassian.jira.rest.client.api.JiraRestClient;
- import com.atlassian.jira.rest.client.api.JiraRestClientFactory;
- import com.atlassian.jira.rest.client.api.domain.ServerInfo;
- import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
- import com.atlassian.util.concurrent.Promise;
- import com.sun.jersey.core.spi.scanning.uri.BundleSchemeScanner;
- import static org.junit.Assert.assertEquals;
- /**
- * Simple test that does nothing but connect to a JIRA server and verifies the
- * connection.
- *
- * @author Bear Giles <bgiles@coyotesong.com>
- */
- public class HelloWorldTest {
- private static final ResourceBundle bundle = ResourceBundle
- .getBundle(HelloWorldTest.class.getName());
- private static final URI serverUri = URI
- .create(bundle.getString("jiraUrl"));
- private static final String username = bundle.getString("username");
- private static final String password = bundle.getString("password");
- @Test
- public void connectToServer() throws IOException {
- final JiraRestClient restClient = new AsynchronousJiraRestClientFactory()
- .createWithBasicHttpAuthentication(serverUri, username, password);
- try {
- final ServerInfo info = restClient.getMetadataClient().getServerInfo().claim();
- assertEquals(URI.create(bundle.getString("jiraUrl")), info.getBaseUri());
- } finally {
- if (restClient != null) {
- restClient.close();
- }
- }
- }
- }
package com.invariantproperties.sandbox.jira.example; import java.io.IOException; import java.net.URI; import java.util.ResourceBundle; import org.junit.Test; import com.atlassian.jira.rest.client.api.JiraRestClient; import com.atlassian.jira.rest.client.api.JiraRestClientFactory; import com.atlassian.jira.rest.client.api.domain.ServerInfo; import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory; import com.atlassian.util.concurrent.Promise; import com.sun.jersey.core.spi.scanning.uri.BundleSchemeScanner; import static org.junit.Assert.assertEquals; /** * Simple test that does nothing but connect to a JIRA server and verifies the * connection. * * @author Bear Giles <bgiles@coyotesong.com> */ public class HelloWorldTest { private static final ResourceBundle bundle = ResourceBundle .getBundle(HelloWorldTest.class.getName()); private static final URI serverUri = URI .create(bundle.getString("jiraUrl")); private static final String username = bundle.getString("username"); private static final String password = bundle.getString("password"); @Test public void connectToServer() throws IOException { final JiraRestClient restClient = new AsynchronousJiraRestClientFactory() .createWithBasicHttpAuthentication(serverUri, username, password); try { final ServerInfo info = restClient.getMetadataClient().getServerInfo().claim(); assertEquals(URI.create(bundle.getString("jiraUrl")), info.getBaseUri()); } finally { if (restClient != null) { restClient.close(); } } } }
with a resource bundle containing suitable replacements for these values
- # in the real world we should use OWASP ESAPI, for instance,
- # to encrypt the password if not all of the properties.
- jiraUrl = http://localhost:8080
- username = bgiles
- password = bgiles
# in the real world we should use OWASP ESAPI, for instance, # to encrypt the password if not all of the properties. jiraUrl = http://localhost:8080 username = bgiles password = bgiles
and a pom.xml file containing
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.invariantproperties.sandbox</groupId>
- <artifactId>jira</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <dependencies>
- <!-- compilation dependency -->
- <dependency>
- <groupId>com.atlassian.jira</groupId>
- <artifactId>jira-rest-java-client-api</artifactId>
- <version>2.0.0-m25</version>
- </dependency>
- <!-- runtime dependency -->
- <dependency>
- <groupId>com.atlassian.jira</groupId>
- <artifactId>jira-rest-java-client-core</artifactId>
- <version>2.0.0-m25</version>
- </dependency>
- <!-- test dependencies -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.8.1</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.invariantproperties.sandbox</groupId> <artifactId>jira</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- compilation dependency --> <dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-rest-java-client-api</artifactId> <version>2.0.0-m25</version> </dependency> <!-- runtime dependency --> <dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-rest-java-client-core</artifactId> <version>2.0.0-m25</version> </dependency> <!-- test dependencies --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
Security
The current client library has several major security concerns.
Authentication
User authentication information is passed via plaintext username and passwords. I do not know if this is a limitation of the REST server or client. If it’s the former it will be difficult to work around, if it’s the latter then a custom JiraRestClientFactory class may be necessary but it can be done.
Encryption
Issues may contain sensitive information and some sites may require the use of HTTPS connections. The current client factory does not support HTTPS connections “out of the box”. One of the constructors takes an Atlassian-enhanced HttpClient object so encryption can be added to it via the standard HttpComponents methods. I do not know if additional initializations must be performed if this approach is taken.
Proxy
Again the current client factory does not support proxy connections “out of the box” but it can be added to an HttpClient object passed to the factory.
Limitations
The final thing we need to discuss are the limitations of the library. There are two major sources:
JIRA REST API limitations. The JIRA REST API does not include all of the functionality available via the UI. For instance you cannot create, edit, or delete either projects or users. You can review the current REST API at https://docs.atlassian.com/jira/REST/latest/.
JIRA REST java client (JRJC) limitations. The JRJC library does not support all of the functionality in the JIRA REST API. Fortunately the client is fairly straightforward so it is easy to download the source and add the required functionality.
Workarounds
You have two final options if the REST API lacks required functionality.
Write a JIRA plugin. A custom JIRA plugin does not have unlimited access to the system but it will certainly have more than the REST API. It must be installed on the JIRA server though – something many sites will be reluctant to do.
Directly access the database. Eek! Sometimes this is the only possibility but it should only be done as a last resort, only to the minimum extent required, and will almost certainly require an explicit reindex afterwards. (I used this approach when migrating data from our old bug-tracking software but I only used it to reset the usernames and timestamps of issues that I created using the standard REST API.)
[…] but don’t no how to solve it please help. want to fetch JIRA data. I am using similar code http://invariantproperties.com/2013/08/07/using-the-jira-rest-java-client-introduction/ getting exception :: org.springframework.web.util.NestedServletException: Handler processing […]