package com.capitalone.dashboard.collector;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
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.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import com.capitalone.dashboard.model.Commit;
import com.capitalone.dashboard.model.GitHubRepo;
import com.capitalone.dashboard.util.Encryption;
import com.capitalone.dashboard.util.EncryptionException;
import com.capitalone.dashboard.util.Supplier;
/**
* GitHubClient implementation that uses SVNKit to fetch information about
* Subversion repositories.
*/
@Component
public class DefaultGitHubClient implements GitHubClient {
private static final Log LOG = LogFactory.getLog(DefaultGitHubClient.class);
private final GitHubSettings settings;
private final RestOperations restOperations;
private final String SEGMENT_API = "/api/v3/repos/";
private final String PUBLIC_GITHUB_REPO_HOST = "api.github.com/repos/";
private final String PUBLIC_GITHUB_HOST_NAME = "github.com";
private final int FIRST_RUN_HISTORY_DEFAULT = 14;
@Autowired
public DefaultGitHubClient(GitHubSettings settings,
Supplier<RestOperations> restOperationsSupplier) {
this.settings = settings;
this.restOperations = restOperationsSupplier.get();
}
@Override
public List<Commit> getCommits(GitHubRepo repo, boolean firstRun) {
List<Commit> commits = new ArrayList<>();
// format URL
String repoUrl = (String) repo.getOptions().get("url");
if (repoUrl.endsWith(".git")) {
repoUrl = repoUrl.substring(0, repoUrl.lastIndexOf(".git"));
}
URL url = null;
String hostName = "";
String protocol = "";
try {
url = new URL(repoUrl);
hostName = url.getHost();
protocol = url.getProtocol();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
LOG.error(e.getMessage());
}
String hostUrl = protocol + "://" + hostName + "/";
String repoName = repoUrl.substring(hostUrl.length(), repoUrl.length());
String apiUrl = "";
if (hostName.startsWith(PUBLIC_GITHUB_HOST_NAME)) {
apiUrl = protocol + "://" + PUBLIC_GITHUB_REPO_HOST + repoName;
} else {
apiUrl = protocol + "://" + hostName + SEGMENT_API + repoName;
}
Date dt;
if (firstRun) {
int firstRunDaysHistory = settings.getFirstRunHistoryDays();
if (firstRunDaysHistory > 0) {
dt = getDate(new Date(), -firstRunDaysHistory, 0);
} else {
dt = getDate(new Date(), -FIRST_RUN_HISTORY_DEFAULT, 0);
}
} else {
dt = getDate(repo.getLastUpdateTime(), 0, -10);
}
Calendar calendar = new GregorianCalendar();
TimeZone timeZone = calendar.getTimeZone();
Calendar cal = Calendar.getInstance(timeZone);
cal.setTime(dt);
String thisMoment = String.format("%tFT%<tRZ", cal);
String queryUrl = apiUrl.concat("/commits?branch=" + repo.getBranch()
+ "&since=" + thisMoment);
/*
* Calendar cal = Calendar.getInstance(); cal.setTime(dateInstance);
* cal.add(Calendar.DATE, -30); Date dateBefore30Days = cal.getTime();
*/
// decrypt password
String decryptedPassword = "";
if ((repo.getPassword() != null) && !"".equals(repo.getPassword())) {
try {
decryptedPassword = Encryption.decryptString(
repo.getPassword(), settings.getKey());
} catch (EncryptionException e) {
LOG.error(e.getMessage());
}
}
boolean lastPage = false;
int pageNumber = 1;
String queryUrlPage = queryUrl;
while (!lastPage) {
try {
ResponseEntity<String> response = (makeRestCall(queryUrlPage,
repo.getUserId(), decryptedPassword));
JSONArray jsonArray = paresAsArray(response);
for (Object item : jsonArray) {
JSONObject jsonObject = (JSONObject) item;
String sha = str(jsonObject, "sha");
JSONObject commitObject = (JSONObject) jsonObject
.get("commit");
JSONObject authorObject = (JSONObject) commitObject
.get("author");
String message = str(commitObject, "message");
String author = str(authorObject, "name");
long timestamp = new DateTime(str(authorObject, "date"))
.getMillis();
Commit commit = new Commit();
commit.setTimestamp(System.currentTimeMillis());
commit.setScmUrl(repo.getRepoUrl());
commit.setScmRevisionNumber(sha);
commit.setScmAuthor(author);
commit.setScmCommitLog(message);
commit.setScmCommitTimestamp(timestamp);
commit.setNumberOfChanges(1);
commits.add(commit);
}
if ((jsonArray == null) || jsonArray.isEmpty()) {
lastPage = true;
} else {
lastPage = isThisLastPage(response);
pageNumber++;
queryUrlPage = queryUrl + "&page=" + pageNumber;
}
} catch (RestClientException re) {
LOG.error(re.getMessage() + ":" + queryUrl);
lastPage = true;
}
}
return commits;
}
private Date getDate(Date dateInstance, int offsetDays, int offsetMinutes) {
Calendar cal = Calendar.getInstance();
cal.setTime(dateInstance);
cal.add(Calendar.DATE, offsetDays);
cal.add(Calendar.MINUTE, offsetMinutes);
return cal.getTime();
}
private boolean isThisLastPage(ResponseEntity<String> response) {
HttpHeaders header = response.getHeaders();
List<String> link = header.get("Link");
if ((link == null) || (link.isEmpty())) {
return true;
} else {
for (String l : link) {
if (l.contains("rel=\"next\"")) {
return false;
}
}
}
return true;
}
private ResponseEntity<String> makeRestCall(String url, String userId,
String password) {
// Basic Auth only.
if (!"".equals(userId) && !"".equals(password)) {
return restOperations.exchange(url, HttpMethod.GET,
new HttpEntity<>(createHeaders(userId, password)),
String.class);
} else {
return restOperations.exchange(url, HttpMethod.GET, null,
String.class);
}
}
private HttpHeaders createHeaders(final String userId, final String password) {
return new HttpHeaders() {
{
String auth = userId + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset
.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
set("Authorization", authHeader);
}
};
}
private JSONArray paresAsArray(ResponseEntity<String> response) {
try {
return (JSONArray) new JSONParser().parse(response.getBody());
} catch (ParseException pe) {
LOG.error(pe.getMessage());
}
return new JSONArray();
}
private String str(JSONObject json, String key) {
Object value = json.get(key);
return value == null ? null : value.toString();
}
}