/* * Copyright 2009 David Linsin * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.linsin.github.rest.service; import java.util.Arrays; import java.util.Collections; import java.util.List; import de.linsin.github.rest.domain.Comment; import de.linsin.github.rest.domain.Issue; import de.linsin.github.rest.domain.Repository; import de.linsin.github.rest.resource.CommentIssueRequest; import de.linsin.github.rest.resource.IssueRequest; import de.linsin.github.rest.resource.IssueResponse; import de.linsin.github.rest.resource.IssuesResponse; import de.linsin.github.rest.resource.Request; import de.linsin.github.rest.resource.CommentIssueResponse; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; /** * Provides methods to browser through GitHub issues for a particular repository. * You need your GitHub credentials, in order to use certain methods * * @author David Linsin - dlinsin@gmail.com */ public class IssueBrowser extends Browser { public static final String BASE_URL = "http://github.com/api/v2/json/"; public static final String ISSUES_BASE_URL = BASE_URL.concat("issues/list/{username}/{repo}"); public static final String ISSUE_BASE_URL = BASE_URL.concat("issues/show/{username}/{repo}"); public static final String ISSUES_OPEN_URL = ISSUES_BASE_URL.concat("/open"); public static final String ISSUES_CLOSED_URL = ISSUES_BASE_URL.concat("/closed"); public static final String ISSUE_URL = ISSUE_BASE_URL.concat("/{no}"); public static final String OPEN_ISSUE_URL = BASE_URL.concat("issues/open/{username}/{repo}"); public static final String CLOSE_ISSUE_URL = BASE_URL.concat("issues/close/{username}/{repo}/{no}"); public static final String REOPEN_ISSUE_URL = BASE_URL.concat("issues/reopen/{username}/{repo}/{no}"); public static final String EDIT_ISSUE_URL = BASE_URL.concat("issues/edit/{username}/{repo}/{no}"); public static final String COMMENT_ISSUE_URL = BASE_URL.concat("issues/comment/{username}/{repo}/{no}"); private String apiToken; private String username; /** * Initializes invariables of IssueBrowser * * @param argUsername {@link String} username of the callee * @param argApiToken {@link String} api token that you need in order to call GitHub */ public IssueBrowser(String argUsername, String argApiToken) { apiToken = argApiToken; username = argUsername; } /** * Use this constructor in case you don't want to authorize. * Note: various methods need authorization. */ public IssueBrowser() { } /** * Browse open issues of a repository * * @param argRepository {@link Repository} instance which contains name and owner * @return {@link List} containing {@link Issue} instances, empty List if no open issues are found * @throws NullPointerException in case passed repository is null * @throws HttpClientErrorException in case passed user or repository doesn't exist */ public List<Issue> browseOpen(Repository argRepository) { return doBrowse(argRepository, ISSUES_OPEN_URL); } /** * Browse closed issues of a repository * * @param argRepository {@link Repository} instance which contains name and owner * @return {@link List} containing {@link Issue} instances, empty List if no closed issues are found * @throws NullPointerException in case passed repository is null * @throws HttpClientErrorException in case passed user or repository doesn't exist */ public List<Issue> browseClosed(Repository argRepository) { return doBrowse(argRepository, ISSUES_CLOSED_URL); } /** * Browse a specific issue of a repository * * @param argRepository {@link Repository} instance which contains name and owner * @param argIssueNo int which is the number of the issue, you want to browse * @return {@link Issue} instance with passed id * @throws NullPointerException in case passed repository is null * @throws HttpClientErrorException in case passed user, repository or issue doesn't exist */ public Issue browse(Repository argRepository, int argIssueNo) { RestTemplate template = initTemplate(); IssueResponse resp = template.getForObject(ISSUE_URL, IssueResponse.class, argRepository.getOwner(), argRepository.getName(), String.valueOf(argIssueNo)); return resp.getIssue(); } /** * Opens the passed {@link Issue} in the passed {@link Repository} * Note: so far only title and body are used from passed instance * * @param argRepository {@link Repository} instance used to open issue * @param argIssue {@link Issue} instance containing title and body of the issue * @return the {@link Issue} instance which was opened and contains assigned id * @throws IllegalArgumentException in case passed Issue doesn't contain a body or title * @throws NullPointerException in case passed repository or issue is null * @throws HttpClientErrorException in case passed user or repository doesn't exist */ public Issue open(Repository argRepository, Issue argIssue) { RestTemplate template = initTemplate(); Assert.hasText(argIssue.getTitle()); Assert.hasText(argIssue.getBody()); IssueRequest req = new IssueRequest(username, apiToken, argIssue.getTitle(), argIssue.getBody()); IssueResponse resp = template.postForObject(OPEN_ISSUE_URL, req, IssueResponse.class, argRepository.getOwner(), argRepository.getName()); return resp.getIssue(); } /** * Reopens the passed {@link Issue} in the passed {@link Repository} * * @param argRepository {@link Repository} instance used to reopen issue * @param argIssue {@link Issue} instance containing id * @return the {@link Issue} instance which was reopened * @throws IllegalArgumentException in case passed Issue doesn't have an id * @throws NullPointerException in case passed repository or issue is null * @throws HttpClientErrorException in case passed user, repository or issue doesn't exist */ public Issue reopen(Repository argRepository, Issue argIssue) { RestTemplate template = initTemplate(); Assert.isTrue(argIssue.getNumber() > 0); Request req = new Request(username, apiToken); IssueResponse resp = template.postForObject(REOPEN_ISSUE_URL, req, IssueResponse.class, argRepository.getOwner(), argRepository.getName(), String.valueOf(argIssue.getNumber())); return resp.getIssue(); } /** * Closes the passed {@link Issue} in the passed {@link Repository} * * @param argRepository {@link Repository} instance used to close issue * @param argIssue {@link Issue} instance containing id * @throws IllegalArgumentException in case passed Issue doesn't contain an id * @throws NullPointerException in case passed repository or issue is null * @throws HttpClientErrorException in case passed user, repository or issue doesn't exist */ public void close(Repository argRepository, Issue argIssue) { RestTemplate template = initTemplate(); Assert.isTrue(argIssue.getNumber() > 0); Request req = new Request(username, apiToken); template.postForObject(CLOSE_ISSUE_URL, req, IssueResponse.class, argRepository.getOwner(), argRepository.getName(), String.valueOf(argIssue.getNumber())); } /** * Edits the existing {@link Issue} passed, which resides in the provided {@link Repository} * Note: so far only id, title and body are used from passed instance * * @param argRepository {@link Repository} instance used to edit issue * @param argIssue {@link Issue} instance containing id, title and body of the issue * @return the {@link Issue} instance which was edited * @throws IllegalArgumentException in case passed Issue doesn't contain an id, body or title * @throws NullPointerException in case passed repository or issue is null * @throws HttpClientErrorException in case passed user, repository or issue doesn't exist */ public Issue edit(Repository argRepository, Issue argIssue) { RestTemplate template = initTemplate(); Assert.isTrue(argIssue.getNumber() > 0); Assert.hasText(argIssue.getTitle()); Assert.hasText(argIssue.getBody()); IssueRequest req = new IssueRequest(username, apiToken, argIssue.getTitle(), argIssue.getBody()); IssueResponse resp = template.postForObject(EDIT_ISSUE_URL, req, IssueResponse.class, argRepository.getOwner(), argRepository.getName(), String.valueOf(argIssue.getNumber())); return resp.getIssue(); } /** * Adds a comment to an exisiting {@link Issue}, which resides in the provided {@link Repository} * Note: only adding comments is supported by the github API http://support.github.com/discussions/repos/1112-retreiving-comments-for-an-issue * * @param argRepository {@link Repository} instance used to retrieve issue * @param argIssue {@link Issue} instance which exists in passed Repository * @param argComment {@link Comment} which must contain a comment * @return the {@link Comment} which was added * @throws IllegalArgumentException in case passed Issue doesn't contain an id * @throws NullPointerException in case passed repository, issue or comment is null * @throws HttpClientErrorException in case passed user, repository or issue doesn't exist */ public Comment comment(Repository argRepository, Issue argIssue, Comment argComment) { RestTemplate template = initTemplate(); Assert.isTrue(argIssue.getNumber() > 0); Assert.hasText(argComment.getComment()); CommentIssueRequest req = new CommentIssueRequest(username, apiToken, argComment.getComment()); CommentIssueResponse resp = template.postForObject(COMMENT_ISSUE_URL, req, CommentIssueResponse.class, argRepository.getOwner(), argRepository.getName(), String.valueOf(argIssue.getNumber())); return resp.getComment(); } protected List<Issue> doBrowse(Repository argRepository, String argUrl) { RestTemplate template = initTemplate(); IssuesResponse resp = template.getForObject(argUrl, IssuesResponse.class, argRepository.getOwner(), argRepository.getName()); if (resp == null || resp.getIssues() == null || resp.getIssues().length == 0) { return Collections.emptyList(); } else { return Arrays.asList(resp.getIssues()); } } }