REST Service Plugin Module Tutorial for FishEye

This tutorial teaches you how toprovide your own REST API. This is useful when your external application's use cases don't fit well with the FishEye/Crucible REST API, resulting in too many round trips.

Here's a more detailed tutorial – it uses JIRA as the application which will host the plugin, but the tutorial also applies to FishEye/Crucible.

Plugin Source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are done, 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:

$ git clone https://atlassian_tutorial@bitbucket.org/atlassian_tutorial/fecru-rest-service-tutorial.git

Alternatively, you can download the source using the Downloads page here: https://bitbucket.org/atlassian_tutorial/fecru-rest-service-tutorial

Work through the Tutorial

Suppose we want to find the set of users who have not completed all their open reviews. Using the REST API we need to make one request to get the list of all open reviews, then for each review we must as for a list of uncompleted reviewers. This results in many HTTP requests, and much data transfer. If we calculate the set on the server using the ReviewService, we can retrieve it in a single call.

Our REST interface is a Java class with annotations which define which methods service which REST URLs.

Plugins which include REST Service modules need to add a dependency on jsr311 to their pom, to provide the REST annotations:

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>jsr311-api</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
</dependency>

We'll start with a very simple class which just returns the string "Hello World" when we make a GET request to the URL http://localhost:3990/fecru/rest/completion-status/1.0/users.

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class RestCompletionStatusService {
    @GET
    @Path("users")
    public Response getUncompletedUsers() {
        return Response.ok("Hello World").build();
    }
}

Add this module to atlassian-plugin.xml:

<rest key="completion-status-rest" path="/completion-status" version="1.0">
    <description>Review completion information</description>
</rest>

Go to the URL above in your browser, and you should (as long as you are logged in) get the XML:

<pair><first>Hello</first><second>World</second></pair>

Now we'll make the implementation less trivial.

@Path("/")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class RestCompletionStatusService {
    private final ReviewService reviewService;

    public RestCompletionStatusService(ReviewService reviewService) {
        this.reviewService = reviewService;
    }

    // We can't return a naked Collection, it must be wrapped in a container
    @XmlRootElement
    public static class Reviewers {
        public Reviewers(Collection<ReviewerData> reviewer) {
            this.reviewer = reviewer;
        }

        // Needed by JAXB
        private Reviewers() {
        }

        // Must be public. By naming this field 'reviewer' each element in the Collection is
        // placed inside a '<reviewer>' element in the XML
        public Collection<ReviewerData> reviewer;
    }

    @GET
    @Path("users")
    public Response getUncompletedUsers() {
        Set<ReviewerData> users = new HashSet<ReviewerData>();
        for (ReviewData reviewData : reviewService.getFilteredReviews("allOpenReviews",false)) {
            System.out.println(reviewData.getPermaId());
            users.addAll(reviewService.getUncompletedReviewers(reviewData.getPermaId()));
        }
        return Response.ok(new Reviewers(users)).build();
    }
}

This version uses the ReviewService to retrieve all open reviews, and then accumulate their incomplete reviewers in a Set.

The returned XML looks like:

<reviewers>
  <reviewer>
    <displayName>Jeffrey</displayName>
    <userName>jeffrey</userName>
    <completed>false</completed>
  </reviewer>
  <reviewer>
    <displayName>Jim Jones</displayName>
    <userName>jim</userName>
    <completed>false</completed>
  </reviewer>
</reviewers>
Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport