package de.refactoringbot.api.github; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import com.fasterxml.jackson.databind.ObjectMapper; import de.refactoringbot.configuration.BotConfiguration; import de.refactoringbot.model.configuration.GitConfiguration; import de.refactoringbot.model.exceptions.BotRefactoringException; import de.refactoringbot.model.exceptions.GitHubAPIException; import de.refactoringbot.model.exceptions.ValidationException; import de.refactoringbot.model.github.pullrequest.GithubCreateRequest; import de.refactoringbot.model.github.pullrequest.GithubPullRequest; import de.refactoringbot.model.github.pullrequest.GithubPullRequests; import de.refactoringbot.model.github.pullrequest.GithubUpdateRequest; import de.refactoringbot.model.github.pullrequestcomment.GitHubPullRequestComments; import de.refactoringbot.model.github.pullrequestcomment.PullRequestComment; import de.refactoringbot.model.github.pullrequestcomment.ReplyComment; import de.refactoringbot.model.github.repository.GithubRepository; import de.refactoringbot.model.github.user.GithubUser; /** * This class communicates with the Github-API. * * @author Stefan Basaric * */ @Component public class GithubDataGrabber { @Autowired ObjectMapper mapper; @Autowired BotConfiguration botConfig; private static final Logger logger = LoggerFactory.getLogger(GithubDataGrabber.class); private static final String USER_AGENT = "Mozilla/5.0"; private static final String GITHUB_DEFAULT_APILINK = "https://api.github.com"; /** * This method tries to get a repository from github. * * @param repoName * @param repoOwner * @param botToken * @param apiLink * @return {Repository-File} * @throws GitHubAPIException * @throws URISyntaxException */ public GithubRepository checkRepository(String repoName, String repoOwner, String botToken, String apiLink) throws GitHubAPIException, URISyntaxException { // Create URI from user input URI configUri = null; if (apiLink == null || apiLink.isEmpty()) { configUri = createURIFromApiLink(GITHUB_DEFAULT_APILINK + "/repos/" + repoOwner + "/" + repoName); } else { configUri = createURIFromApiLink(apiLink + "/repos/" + repoOwner + "/" + repoName); } // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath()); apiUriBuilder.queryParam("access_token", botToken); URI githubURI = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send request to the GitHub-API return rest.exchange(githubURI, HttpMethod.GET, entity, GithubRepository.class).getBody(); } catch (RestClientException e) { logger.error(e.getMessage(), e); throw new GitHubAPIException("Repository does not exist on GitHub or invalid Bot-Token!", e); } } /** * This method tries to get a user with the given token. * * @param botUsername * @param botToken * @param botEmail * @param apiLink * @return * @throws Exception */ public void checkGithubUser(String botUsername, String botToken, String botEmail, String apiLink) throws Exception { // Create URI from user input URI configUri = null; if (apiLink == null || apiLink.isEmpty()) { configUri = createURIFromApiLink(GITHUB_DEFAULT_APILINK + "/user"); } else { configUri = createURIFromApiLink(apiLink + "/user"); } // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()).host(configUri.getHost()) .path(configUri.getPath()); apiUriBuilder.queryParam("access_token", botToken); URI githubURI = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); GithubUser githubUser = null; try { // Send request to the GitHub-API githubUser = rest.exchange(githubURI, HttpMethod.GET, entity, GithubUser.class).getBody(); } catch (RestClientException e) { logger.error(e.getMessage(), e); throw new GitHubAPIException("Invalid Bot-Token!"); } // Check if user exists and has a public email if (!botUsername.equals(githubUser.getLogin())) { throw new ValidationException("Bot-User does not exist on Github!"); } if (githubUser.getEmail() == null || githubUser.getEmail().isEmpty()) { throw new ValidationException("Bot-User does not have a public email on Github!"); } if (!githubUser.getEmail().equals(botEmail)) { throw new ValidationException("Invalid Bot-Email!"); } } /** * This method checks if a branch with a specific name exists on the fork. If * such a branch exists, the method throws an exception. * * @param gitConfig * @param branchName * @throws URISyntaxException * @throws BotRefactoringException * @throws GitHubAPIException */ public void checkBranch(GitConfiguration gitConfig, String branchName) throws URISyntaxException, BotRefactoringException, GitHubAPIException { URI configUri = createURIFromApiLink(gitConfig.getForkApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath() + "/branches/" + branchName); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI pullsUri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send Request to the GitHub-API rest.exchange(pullsUri, HttpMethod.GET, entity, String.class).getBody(); throw new BotRefactoringException( "Issue was already refactored in the past! The bot database might have been resetted but not the fork itself."); } catch (RestClientException e) { // If branch does not exist -> return if (e.getMessage().equals("404 Not Found")) { return; } logger.error(e.getMessage(), e); throw new GitHubAPIException("Could not get Branch from Github!", e); } } /** * This method returns all PullRequest from Github. * * @return allRequests * @throws URISyntaxException * @throws GitHubAPIException * @throws IOException */ public GithubPullRequests getAllPullRequests(GitConfiguration gitConfig) throws URISyntaxException, GitHubAPIException, IOException { URI configUri = createURIFromApiLink(gitConfig.getRepoApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath() + "/pulls"); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI pullsUri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); String json = null; try { // Send Request to the GitHub-API json = rest.exchange(pullsUri, HttpMethod.GET, entity, String.class).getBody(); } catch (RestClientException e) { logger.error(e.getMessage(), e); throw new GitHubAPIException("Could not get Pull-Requests from Github!", e); } GithubPullRequests allRequests = new GithubPullRequests(); try { List<GithubPullRequest> requestList = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, GithubPullRequest.class)); allRequests.setAllPullRequests(requestList); return allRequests; } catch (IOException e) { logger.error(e.getMessage(), e); throw new IOException("Could not create object from Github-Request json!", e); } } /** * This method returns all comments of a pull request from Github. * * @return allRequests * @throws GitHubAPIException * @throws IOException */ public GitHubPullRequestComments getAllPullRequestComments(URI commentsUri, GitConfiguration gitConfig) throws GitHubAPIException, IOException { // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(commentsUri.getScheme()) .host(commentsUri.getHost()).path(commentsUri.getPath()); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI githubURI = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); String json = null; try { // Send request to the GitHub-API json = rest.exchange(githubURI, HttpMethod.GET, entity, String.class).getBody(); } catch (RestClientException r) { throw new GitHubAPIException("Could not get pull request comments from Github!", r); } GitHubPullRequestComments allComments = new GitHubPullRequestComments(); try { // Try to map json to object List<PullRequestComment> commentList = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, PullRequestComment.class)); allComments.setComments(commentList); return allComments; } catch (IOException e) { logger.error(e.getMessage(), e); throw new IOException("Could not create object from Github-Comment json!", e); } } /** * This method updates a pull request on Github. * * @param send * @param gitConfig * @throws GitHubAPIException * @throws URISyntaxException */ public void updatePullRequest(GithubUpdateRequest send, GitConfiguration gitConfig, Integer requestNumber) throws GitHubAPIException, URISyntaxException { URI configUri = createURIFromApiLink(gitConfig.getRepoApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath() + "/pulls/" + requestNumber); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI pullsUri = apiUriBuilder.build().encode().toUri(); // For PATCH-Requests HttpHeaders headers = new HttpHeaders(); MediaType mediaType = new MediaType("application", "merge-patch+json"); headers.setContentType(mediaType); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); RestTemplate rest = new RestTemplate(requestFactory); try { // Send request to the GitHub-API rest.exchange(pullsUri, HttpMethod.PATCH, new HttpEntity<>(send), String.class); } catch (RestClientException e) { throw new GitHubAPIException("Could not update pull request!", e); } } /** * This method replies to a comment on Github. * * @param comment * @param gitConfig * @param requestNumber * @throws URISyntaxException * @throws GitHubAPIException */ public void responseToBotComment(ReplyComment comment, GitConfiguration gitConfig, Integer requestNumber) throws URISyntaxException, GitHubAPIException { URI configUri = createURIFromApiLink(gitConfig.getRepoApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath() + "/pulls/" + requestNumber + "/comments"); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI pullsUri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); try { // Send request to Github-API rest.exchange(pullsUri, HttpMethod.POST, new HttpEntity<>(comment), String.class); } catch (RestClientException e) { throw new GitHubAPIException("Could not reply to Github comment!", e); } } /** * This method creates a pull request on Github. * * @param request * @param gitConfig * @throws URISyntaxException * @throws GitHubAPIException */ public GithubPullRequest createRequest(GithubCreateRequest request, GitConfiguration gitConfig) throws URISyntaxException, GitHubAPIException { URI configUri = createURIFromApiLink(gitConfig.getRepoApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath() + "/pulls"); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI pullsUri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); try { // Send request to the GitHub-API return rest.exchange(pullsUri, HttpMethod.POST, new HttpEntity<>(request), GithubPullRequest.class) .getBody(); } catch (RestClientException r) { throw new GitHubAPIException("Could not create pull request on Github!", r); } } /** * This method creates a fork from a repository. * * @param gitConfig * @return * @throws URISyntaxException * @throws GitHubAPIException */ public GithubRepository createFork(GitConfiguration gitConfig) throws URISyntaxException, GitHubAPIException { URI configUri = createURIFromApiLink(gitConfig.getRepoApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath() + "/forks"); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI forksUri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); try { // Send request to the Github-API return rest.exchange(forksUri, HttpMethod.POST, null, GithubRepository.class).getBody(); } catch (RestClientException r) { throw new GitHubAPIException("Could not create fork on Github!", r); } } /** * This method deletes a repository from Github. * * @param gitConfig * @throws URISyntaxException * @throws GitHubAPIException */ public void deleteRepository(GitConfiguration gitConfig) throws URISyntaxException, GitHubAPIException { String originalRepo = gitConfig.getRepoApiLink(); String forkRepo = gitConfig.getForkApiLink(); // Never delete the original repository if (originalRepo.equals(forkRepo)) { return; } // Read URI from configuration URI configUri = createURIFromApiLink(gitConfig.getForkApiLink()); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(configUri.getScheme()) .host(configUri.getHost()).path(configUri.getPath()); apiUriBuilder.queryParam("access_token", gitConfig.getBotToken()); URI repoUri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); try { // Send request to the Github-API rest.exchange(repoUri, HttpMethod.DELETE, null, String.class); } catch (RestClientException r) { throw new GitHubAPIException("Could not delete repository from Github!", r); } } /** * Attempts to instantiate a URI object using the specified API link * * @param link * @return uri * @throws URISyntaxException */ private URI createURIFromApiLink(String link) throws URISyntaxException { URI result = null; try { result = new URI(link); } catch (URISyntaxException u) { logger.error(u.getMessage(), u); throw new URISyntaxException("Could not create URI from given API link!", u.getMessage()); } return result; } }