Spring in Practice

Willie Wheeler's Spring blog

Spring Social GitHub: Revisiting GitHub Integration

| Comments

In my last post, Calling the GitHub API using Spring’s RestTemplate, I explained how to call a public endpoint that retrieves a repository’s watchers on the GitHub API using Spring’s RestTemplate.

I was happy to receive a comment by Craig Walls pointing out that Spring Social has a GitHub binding, which came with an invitation to contribute code to that binding. Sounded great—I forked the project on GitHub and dug in. I discovered two factoids:

  1. The project is Gradle-based. Excellent, because I’d been wanting to try it out for a couple of years since Craig first recommended it to me in at Spring One 2GX. I just never got around to it.
  2. The Spring Social GitHub binding is nascent. (Understandably, Facebook, Twitter and LinkedIn get the lion’s share of development attention.) Outstanding, because I could implement whatever I wanted.

I ended up writing a handful of public endpoints for Spring Social GitHub. I focused on the public ones instead of the personal ones requiring OAuth (GitHub supports OAuth2 among others) since the Skybase app I’m building probably doesn’t need any of the personal endpoints. I’ll show you some of what I did since it’s fairly straightforward and since some of you might be interested in contributing further code. The Spring Social documentation has a chapter on how to create or elaborate a binding, and there are existing bindings for Facebook, Twitter and LinkedIn that provide good guidance as to how to structure things. The GitHub site has useful information on the mechanics as well. I was a little bit lazy and just shot Craig a few questions. He was good-natured about it, and pointed me to the right examples.

Implementing support for repository watching in Spring Social GitHub

Implementing a user class. In the last post I implemented a User class and then just called the GitHub URL with the RestTemplate, deserializing the JSON into my User. As it turns out, this isn’t that different than what I added to Spring Social GitHub. In fact, the revised version is almost exactly the same:

package org.springframework.social.github.api;

import java.io.Serializable;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;

@SuppressWarnings("serial")
@JsonIgnoreProperties(ignoreUnknown = true)
public class GitHubUser implements Serializable {
    private Long id;
    private String url;
    private String login;
    private String avatarUrl;
    private String gravatarId;

    public Long getId() { return id; }

    public void setId(Long id) { this.id = id; }

    public String getUrl() { return url; }

    public void setUrl(String url) { this.url = url; }

    public String getLogin() { return login; }

    public void setLogin(String login) { this.login = login; }

    @JsonProperty("avatar_url")
    public String getAvatarUrl() { return avatarUrl; }

    public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; }

    @JsonProperty("gravatar_id")
    public String getGravatarId() { return gravatarId; }

    public void setGravatarId(String gravatarId) { this.gravatarId = gravatarId; }
}

I added the @JsonIgnoreProperties annotation to try to future-proof the class a little, and I renamed it to GitHubUser since that was more in line with the Spring Social convention around naming data transfer objects.

Creating the client interfaces. Spring Social basically wraps RestTemplate with provider-specific methods and support for OAuth authorization. So instead of passing https://api.github.com/repos/williewheeler/skybase to RestTemplate.getForObject(), we call a getWatchers() method on the Spring Social GitHub API.

The general pattern is for there to be a top-level Java client interface, which is called Facebook, or Twitter, or LinkedIn, or GitHub, and then for this interface to have smaller sub-APIs to group related operations. This avoids having a monster interface with dozens and dozens of methods. Here, for example, is what the GitHub interface looks like so far:

package org.springframework.social.github.api;

import org.springframework.social.ApiBinding;
import org.springframework.social.github.api.impl.GitHubTemplate;

public interface GitHub extends ApiBinding {

    RepoOperations repoOperations();

    UserOperations userOperations();
}

GitHub has a lot more sets of operations than that (e.g. gists, issues, orgs, pull requests, etc.), but I was just getting my feet wet and so I only created a couple of XxxOperations interfaces here.

Here’s what RepoOperations looks like so far:

package org.springframework.social.github.api;

import java.util.List;

public interface RepoOperations {
    List getCollaborators(String user, String repo);
    List getCommits(String user, String repo);
    List getWatchers(String user, String repo);
}

(As I mentioned, I implemented a few things beyond getWatchers().)

Filling in the client implementations. With the interfaces in place, it was time to write their implementations. This looked pretty close to the code from my previous article, but a little more industrial-strength to handle API modularity and also personal endpoints. Here for instance is the RepoTemplate.

package org.springframework.social.github.api.impl;

import static java.util.Arrays.asList;

import java.util.List;
import org.springframework.social.github.api.GitHubCommit;
import org.springframework.social.github.api.GitHubUser;
import org.springframework.social.github.api.RepoOperations;
import org.springframework.web.client.RestTemplate;

public class RepoTemplate extends AbstractGitHubOperations
    implements RepoOperations {

    private final RestTemplate restTemplate;

    public RepoTemplate(RestTemplate restTemplate, boolean isAuthorizedForUser) {
        super(isAuthorizedForUser);
        this.restTemplate = restTemplate;
    }

    public List getCollaborators(String user, String repo) {
        return asList(restTemplate.getForObject(
            buildRepoUri("/collaborators"), GitHubUser[].class, user, repo));
    }

    public List getCommits(String user, String repo) {
        return asList(restTemplate.getForObject(
            buildRepoUri("/commits"), GitHubCommit[].class, user, repo));
    }

    public List getWatchers(String user, String repo) {
        return asList(restTemplate.getForObject(
            buildRepoUri("/watchers"), GitHubUser[].class, user, repo));
    }

    private String buildRepoUri(String path) {
        return buildUri("repos/{user}/{repo}" + path);
    }
}

Test cases. I wrote some test cases as well, using the test framework that the Spring guys put together. It is basically a mock server that serves up JSON files so you don’t have to hit the real GitHub and put your rate limit in danger.

Using Spring Social GitHub

With all that in place, I was excited to try out my new code. Spring Social GitHub hasn’t had an actual release yet (Spring Social Core has, but not the GitHub binding), so you’ll need to hit a snapshot repository to get it:

<repository>
    <id>spring-snapshot</id>
    <name>Spring Maven Snapshot Repository</name>
    <url>http://maven.springframework.org/snapshot</url>
</repository>

...

<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-core</artifactId>
    <version>1.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-github</artifactId>
    <version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>

Here’s the actual Java code:

import org.springframework.social.github.api.GitHub;
import org.springframework.social.github.api.GitHubUser;

...

GitHub gitHub = ... injected or whatever ...
List<GitHubUser> watchers =
    gitHub.repoOperations().getWatchers("williewheeler", "skybase");

Couldn’t be much easier.

Here are some Skybase screenshots that show what sort of information I’m pulling down from GitHub using Spring Social GitHub:

[gallery columns=“2”]

Comments