com.dsh105.nexus.hook.github.GitHub.java Source code

Java tutorial

Introduction

Here is the source code for com.dsh105.nexus.hook.github.GitHub.java

Source

/*
 * This file is part of Nexus.
 *
 * Nexus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Nexus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nexus.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.dsh105.nexus.hook.github;

import com.dsh105.nexus.Nexus;
import com.dsh105.nexus.exception.github.*;
import com.dsh105.nexus.hook.github.gist.Gist;
import com.dsh105.nexus.hook.github.gist.GistFile;
import com.dsh105.nexus.util.JsonUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.async.Callback;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.*;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class GitHub {

    public static String API_URL = "https://api.github.com";
    public static String REPO_API_URL = API_URL + "/repos/{name}";
    public static String USER_API_URL = API_URL + "/users/{name}";
    public static String RATE_LIMIT_API_URL = API_URL + "/rate_limit";
    public static String GISTS_API_URL = API_URL + "/gists";

    public static String ISSUES = "/issues/{id}";
    public static String PULLS = "/pulls/{id}";
    public static String CONTRIBUTORS = "/contributors";
    public static String HOOKS = "/hooks";
    public static String COLLABORATORS = "/collaborators";
    public static String LANGUAGES = "/languages";
    public static String FORKS = "/forks";

    private Cache<String, GitHubRepo> REPO_CACHE = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)
            .refreshAfterWrite(3, TimeUnit.MINUTES).build(new CacheLoader<String, GitHubRepo>() {
                @Override
                public GitHubRepo load(String key) throws Exception {
                    GitHubRepo existing = REPO_CACHE.getIfPresent(key);
                    return getRepo(key, existing.userLoginForAccessToken);
                }
            });

    private Cache<String, GitHubIssue> ISSUE_CACHE = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES).refreshAfterWrite(3, TimeUnit.MINUTES)
            .build(new CacheLoader<String, GitHubIssue>() {
                @Override
                public GitHubIssue load(String key) throws Exception {
                    GitHubIssue existing = ISSUE_CACHE.getIfPresent(key);
                    return getIssue(existing.getRepo(), existing.getNumber(),
                            existing.getRepo().userLoginForAccessToken);
                }
            });

    public GitHub() {
    }

    public static String getRepoApiUrl(String repoName) {
        return REPO_API_URL.replace("{name}", repoName);
    }

    public static String getIssuesUrl(String repoName, int id) {
        return getRepoApiUrl(repoName) + ISSUES.replace("{id}", String.valueOf(id));
    }

    public static String getPullsUrl(String repoName, int id) {
        return getRepoApiUrl(repoName) + PULLS.replace("{id}", String.valueOf(id));
    }

    public static String getContributorsUrl(String repoName) {
        return getRepoApiUrl(repoName) + CONTRIBUTORS;
    }

    public static String getHooksUrl(String repoName) {
        return getRepoApiUrl(repoName) + HOOKS;
    }

    public static String getCollaboratorsUrl(String repoName) {
        return getRepoApiUrl(repoName) + COLLABORATORS;
    }

    public static String getLanguagesUrl(String repoName) {
        return getRepoApiUrl(repoName) + LANGUAGES;
    }

    public static String getForksUrl(String repoName) {
        return getRepoApiUrl(repoName) + FORKS;
    }

    public static String getUserUrl(String userLogin) {
        return USER_API_URL.replace("{name}", userLogin);
    }

    public static GitHub getGitHub() {
        return Nexus.getInstance().getGithub();
    }

    public String createGist(Exception e) {
        Nexus.LOGGER.info("Creating a Gist for " + e.getClass().getName());
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        e.printStackTrace(printWriter);
        Gist gist = new Gist(new GistFile(writer.toString()));
        try {
            writer.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        return gist.create();
    }

    public String getAccessToken(String userLogin, boolean onlyAllowTokenAccess) {
        if (userLogin == null || userLogin.isEmpty()) {
            return Nexus.getInstance().getGitHubConfig().getNexusGitHubApiKey();
        }
        String accessToken = Nexus.getInstance().getGitHubConfig().getGitHubApiKey(userLogin);
        if (accessToken.isEmpty() && onlyAllowTokenAccess) {
            throw new GitHubAPIKeyInvalidException(
                    "Please provide a GitHub API key (via the ghkey command) to access this part of the GitHub API");
        }
        // Make sure that we have a valid API key to use. Provide the default Nexus API key if this user doesn't have one.
        return accessToken.isEmpty() ? Nexus.getInstance().getGitHubConfig().getNexusGitHubApiKey() : accessToken;
    }

    public String getAccessToken(String userLogin) {
        return getAccessToken(userLogin, false);
    }

    private GitHubRateLimit getApiRateLimit(String accessToken) {
        try {
            return JsonUtil.read(Unirest.get(RATE_LIMIT_API_URL).header("Authorization", "token " + accessToken),
                    "rate", GitHubRateLimit.class);
        } catch (UnirestException e) {
            throw new GitHubException("Failed to connect to GitHub API!", e);
        }
    }

    public HttpResponse<JsonNode> makeRequest(String urlPath, String userLogin) throws UnirestException {
        return makeRequest(urlPath, userLogin, false);
    }

    protected HttpResponse<JsonNode> makeRequest(String urlPath, String userLogin, boolean assumeAccess)
            throws UnirestException {
        return makeRequest(urlPath, userLogin, assumeAccess, false);
    }

    protected HttpResponse<JsonNode> makeRequest(String urlPath, String userLogin, boolean assumeAccess,
            boolean onlyAllowTokenAccess) throws UnirestException {
        String accessToken = getAccessToken(userLogin, onlyAllowTokenAccess);
        // check if the api key has expired - overused
        GitHubRateLimit rateLimit = getApiRateLimit(accessToken);
        if (rateLimit != null) {
            if (rateLimit.getRemaining() <= 0) {
                throw new GitHubRateLimitExceededException(
                        "Rate limit for GitHub API exceeded. Further requests cannot be executed.");
            }
        }

        Nexus.LOGGER.fine("Connecting to " + urlPath + " with ACCESS_TOKEN of " + userLogin);
        HttpResponse<JsonNode> response = Unirest.get(urlPath).header("Authorization", "token " + accessToken)
                .asJson();
        if (!assumeAccess) {
            try {
                String checkAccess = response.getBody().getObject().getString("message");
                if (checkAccess != null && checkAccess.equalsIgnoreCase("BAD CREDENTIALS")) {
                    throw new GitHubAPIKeyInvalidException("Invalid GitHub API key!");
                }
                return response;
            } catch (JSONException | NullPointerException ignored) {
            }
        }
        return response;
    }

    public GitHubRepo getRepo(String name, String userLogin) {
        Nexus.LOGGER.info("Requesting GitHub repo (" + name + ") on behalf of " + userLogin);
        if (REPO_CACHE.getIfPresent(name) != null) {
            return REPO_CACHE.getIfPresent(name);
        }
        try {
            HttpResponse<JsonNode> response = makeRequest(getRepoApiUrl(name), userLogin);
            InputStream input = response.getRawBody();
            GitHubRepo repo = JsonUtil.read(input, GitHubRepo.class);
            if (repo == null || repo.getUrl() == null) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + name);
            }
            repo.repoOwner = getUser(response.getBody().getObject().getJSONObject("owner").getString("login"),
                    userLogin);
            repo.collaborators = getCollaborators(repo, userLogin);
            repo.contributors = getContributors(repo, userLogin);
            repo.languages = getLanguages(repo, userLogin);
            repo.userLoginForAccessToken = userLogin;
            cache(repo);
            return repo;
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + name, e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    public void fork(GitHubRepo repo, String userLogin) {
        Nexus.LOGGER.info("Attempting to fork repo (" + repo.getFullName() + ") on behalf of " + userLogin);
        try {
            Unirest.post(getForksUrl(repo.getFullName()))
                    .header("Authorization", "token " + getAccessToken(userLogin, true)).asJson();
        } catch (UnirestException e) {
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    public void forkAsync(GitHubRepo repo, String userLogin, Callback<JsonNode> callback) {
        Nexus.LOGGER.info("Attempting to fork repo (" + repo.getFullName() + ") on behalf of " + userLogin);
        Unirest.post(getForksUrl(repo.getFullName()))
                .header("Authorization", "token " + getAccessToken(userLogin, true)).asJsonAsync(callback);
    }

    public GitHubUser getUser(String ghUserLogin, String userLogin) {
        Nexus.LOGGER.info("Requesting GitHub user (" + ghUserLogin + ") on behalf of " + userLogin);
        try {
            return JsonUtil.read(makeRequest(getUserUrl(ghUserLogin), userLogin).getRawBody(), GitHubUser.class);
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubUserNotFoundException("Failed to locate GitHub User: " + ghUserLogin, e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    protected GitHubUser getReporterOf(GitHubIssue issue, String userLogin) {
        try {
            String issueUrl = issue instanceof GitHubPullRequest
                    ? getPullsUrl(issue.getRepo().getFullName(), issue.getNumber())
                    : getIssuesUrl(issue.getRepo().getFullName(), issue.getNumber());
            HttpResponse<JsonNode> response = makeRequest(issueUrl, userLogin);
            return getUser(response.getBody().getObject().getJSONObject("user").getString("login"), userLogin);
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + issue.getRepo().getFullName(),
                        e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    public GitHubIssue getIssue(GitHubRepo repo, int id, String userLogin) {
        Nexus.LOGGER.info(
                "Requesting GitHub issue (" + repo.getFullName() + " - #" + id + ") on behalf of " + userLogin);
        for (GitHubIssue i : ISSUE_CACHE.asMap().values()) {
            if (repo.getFullName().equals(i.getRepo().getFullName()) && i.getNumber() == id) {
                return i;
            }
        }

        try {
            HttpResponse<JsonNode> response = makeRequest(getIssuesUrl(repo.getFullName(), id), userLogin);
            InputStream input = response.getRawBody();
            GitHubIssue issue = null;
            boolean checkForPullRequest = false;
            try {
                if (response.getBody().getObject().get("pull_request") != null) {
                    checkForPullRequest = true;
                }
            } catch (JSONException ignored) {
            }
            try {
                if (!checkForPullRequest) {
                    issue = JsonUtil.read(input, GitHubIssue.class);
                }
                if (issue == null || issue.getNumber() <= 0) {
                    issue = JsonUtil.read(makeRequest(getPullsUrl(repo.getFullName(), id), userLogin).getRawBody(),
                            GitHubPullRequest.class);
                }
            } catch (JSONException ignored) {
            }
            if (issue.getNumber() <= 0) {
                throw new GitHubNotFoundException("Issue #" + id + " doesn't exist at " + repo.getFullName());
            }
            issue.repo = repo;
            issue.reportedBy = getReporterOf(issue, userLogin);
            if (issue != null) {
                cache(issue);
            }
            return issue;
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + repo.getFullName(), e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    public GitHubIssue getIssue(String repoName, int id, String userLogin) {
        return getIssue(getRepo(repoName, userLogin), id, userLogin);
    }

    public void mergePullRequest(GitHubPullRequest pullRequest, String userLogin) {
        Nexus.LOGGER.info("Attempting to merge pull request (" + pullRequest.getRepo().getFullName() + " #"
                + pullRequest.getNumber() + ") on behalf of " + userLogin);
        try {
            HttpResponse<JsonNode> response = Unirest.put(pullRequest.getApiUrl() + "/merge")
                    .header("Authorization", "token " + getAccessToken(userLogin, true))
                    .body("{\"commit_message\":\"" + pullRequest.getTitle() + "\"}").asJson();
            JSONObject responseObject = response.getBody().getObject();
            String message = responseObject.getString("message");
            boolean mergeStatus = false;
            try {
                mergeStatus = responseObject.getBoolean("merged");
            } catch (JSONException ignored) {
            }
            if (!mergeStatus) {
                if (message.equalsIgnoreCase("NOT FOUND")) {
                    message = "You do not have access to this";
                }
                throw new GitHubPullRequestMergeException(message);
            }
        } catch (UnirestException e) {
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    protected GitHubUser[] getCollaborators(GitHubRepo repo, String userLogin) {
        return getCollaborators(repo.getFullName(), userLogin);
    }

    protected GitHubUser[] getCollaborators(String name, String userLogin) {
        try {
            HttpResponse<JsonNode> response = makeRequest(getCollaboratorsUrl(name), userLogin, true);
            if (response.getBody().getObject() != null
                    && response.getBody().getObject().getString("message") != null) {
                return new GitHubUser[0];
            }
            return JsonUtil.read(response.getRawBody(), GitHubUser[].class);
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + name, e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    protected GitHubUser[] getContributors(GitHubRepo repo, String userLogin) {
        return getContributors(repo.getFullName(), userLogin);
    }

    protected GitHubUser[] getContributors(String name, String userLogin) {
        try {
            // Anything using this method should already have checked API key validity
            return JsonUtil.read(makeRequest(getContributorsUrl(name), userLogin, true).getRawBody(),
                    GitHubUser[].class);
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + name, e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        } catch (NullPointerException ignored) {
            // in the event that there's no collaborators
            return new GitHubUser[0];
        }
    }

    protected GitHubLanguage[] getLanguages(GitHubRepo repo, String userLogin) {
        return getLanguages(repo.getFullName(), userLogin);
    }

    protected GitHubLanguage[] getLanguages(String name, String userLogin) {
        try {
            // Anything using this method should already have checked API key validity
            JSONObject jsonResponse = makeRequest(getLanguagesUrl(name), userLogin).getBody().getObject();
            Set<String> set = jsonResponse.keySet();
            ArrayList<GitHubLanguage> languages = new ArrayList<>();
            for (String language : set) {
                languages.add(new GitHubLanguage(language, jsonResponse.getInt(language)));
            }
            return languages.toArray(new GitHubLanguage[languages.size()]);
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + name, e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    public GitHubHook[] getHooks(GitHubRepo repo, String userLogin) {
        return getHooks(repo.getFullName(), userLogin);
    }

    public GitHubHook[] getHooks(String name, String userLogin) {
        Nexus.LOGGER.info("Requesting GitHub hooks for " + name + " on behalf of " + userLogin);
        try {
            return JsonUtil.read(makeRequest(getHooksUrl(name), userLogin, false, true).getRawBody(),
                    GitHubHook[].class);
        } catch (UnirestException e) {
            if (e.getCause() instanceof FileNotFoundException) {
                throw new GitHubNotFoundException("Failed to locate GitHub Repo: " + name, e);
            }
            throw new GitHubException("Error connecting to GitHub API! ", e);
        }
    }

    public GitHubHook getHook(GitHubRepo repo, int id, String userLogin) {
        return getHook(repo.getFullName(), id, userLogin);
    }

    public GitHubHook getHook(String repo, int id, String userLogin) {
        for (GitHubHook hook : getHooks(repo, userLogin)) {
            if (hook.getId() == id) {
                return hook;
            }
        }
        return null;
    }

    public GitHubHook getHook(GitHubRepo repo, String name, String userLogin) {
        return getHook(repo.getFullName(), name, userLogin);
    }

    public GitHubHook getHook(String repo, String name, String userLogin) {
        for (GitHubHook hook : getHooks(repo, userLogin)) {
            if (hook.getName().equals(name)) {
                return hook;
            }
        }
        return null;
    }

    public void setIrcNotifications(GitHubRepo repo, String userLogin, GitHubEvent... events) {
        setIrcNotifications(repo.getFullName(), userLogin, events);
    }

    public void setIrcNotifications(String repo, String userLogin, GitHubEvent... events) {
        GitHubHook hook = getHook(repo, "irc", userLogin);
        if (hook != null) {
            String s = "";
            for (GitHubEvent e : events) {
                s += s.isEmpty() ? "\"" + e.getJsonName() + "\"" : "," + "\"" + e.getJsonName() + "\"";
            }
            Nexus.LOGGER.info(
                    "Attempting to set IRC notifications for " + repo + " to " + s + " on behalf of " + userLogin);
            try {
                Unirest.patch(getHooksUrl(repo) + "/" + hook.getId())
                        .header("Authorization", "token " + getAccessToken(userLogin, true))
                        .header("accept", "application/json").header("content-type", "application/json")
                        .body("{\"events\":[" + s + "]}").asJson();
            } catch (UnirestException e) {
                throw new GitHubException("Could not connect to GitHub API!", e);
            }
        } else {
            throw new GitHubHookNotFoundException("IRC Hook not found for GitHub Repo (" + repo + ")");
        }
    }

    public GitHubEvent[] getIrcNotifications(GitHubRepo repo, String userLogin) {
        return getIrcNotifications(repo.getFullName(), userLogin);
    }

    public GitHubEvent[] getIrcNotifications(String repo, String userLogin) {
        GitHubHook hook = getHook(repo, "irc", userLogin);
        if (hook != null) {
            try {
                Nexus.LOGGER.info(
                        "Requesting GitHub IRC notification settings for " + repo + " on behalf of " + userLogin);
                ArrayList<GitHubEvent> events = new ArrayList<>();
                JSONArray eventsJsonArray = makeRequest(getHooksUrl(repo) + "/" + hook.getId(), userLogin, false,
                        true).getBody().getObject().getJSONArray("events");
                for (int i = 0; i < eventsJsonArray.length(); i++) {
                    GitHubEvent e = GitHubEvent.getByJsonName(eventsJsonArray.getString(i));
                    if (e != null) {
                        events.add(e);
                    }
                }
                return events.toArray(new GitHubEvent[events.size()]);
            } catch (UnirestException e) {
                throw new GitHubException("Could not connect to GitHub API!", e);
            }
        } else {
            throw new GitHubHookNotFoundException("IRC Hook not found for GitHub Repo (" + repo + ")");
        }
    }

    private void cache(GitHubRepo repo) {
        REPO_CACHE.put(repo.getFullName(), repo);
    }

    private void cache(GitHubIssue issue) {
        ISSUE_CACHE.put(issue.getRepo().getFullName(), issue);
    }
}