package org.jboss.arquillian.drone.webdriver.binary.downloading.source;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.net.URI;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.http.client.utils.URIBuilder;
import org.jboss.arquillian.drone.webdriver.binary.downloading.ExternalBinary;
import org.jboss.arquillian.drone.webdriver.utils.GitHubLastUpdateCache;
import org.jboss.arquillian.drone.webdriver.utils.HttpClient;
import org.jboss.arquillian.drone.webdriver.utils.Rfc2126DateTimeFormatter;
import static org.apache.http.HttpHeaders.IF_MODIFIED_SINCE;
import static org.apache.http.HttpHeaders.LAST_MODIFIED;
/**
* GitHub source is an abstract class that helps you to retrieve either latest release or a release with some version
* from some specific repository.
*/
public abstract class GitHubSource implements ExternalBinarySource {
private static final String LATEST_URL = "/releases/latest";
private static final String RELEASES_URL = "/releases";
private static final Logger log = Logger.getLogger(GitHubSource.class.toString());
private static final Gson gson = new Gson();
private final HttpClient httpClient;
private final GitHubLastUpdateCache cache;
private final String projectUrl;
private final String uniqueKey;
// JSON keys
private String tagNameKey = "tag_name";
private String assetNameKey = "name";
private String browserDownloadUrlKey = "browser_download_url";
private String assetsKey = "assets";
/**
* @param organization
* GitHub organization/user name the project belongs to
* @param project
* GitHub project name
*/
public GitHubSource(String organization, String project, HttpClient httpClient,
GitHubLastUpdateCache gitHubLastUpdateCache) {
this.httpClient = httpClient;
this.projectUrl = String.format("https://api.github.com/repos/%s/%s", organization, project);
this.uniqueKey = organization + "@" + project;
this.cache = gitHubLastUpdateCache;
}
/**
* It is expected that this abstract method should return a regex that represents an expected file name
* of the release asset. These names are visible on pages of some specific release or accessible
* via api.github request.
*
* @return A regex that represents an expected file name of an asset associated with the required release.
*/
protected abstract String getExpectedFileNameRegex(String version);
@Override
public ExternalBinary getLatestRelease() throws Exception {
final HttpClient.Response response =
sentGetRequestWithPagination(projectUrl + LATEST_URL, 1, lastModificationHeader());
final ExternalBinary binaryRelease;
if (response.hasPayload()) {
final JsonObject latestRelease = gson.fromJson(response.getPayload(), JsonElement.class).getAsJsonObject();
String tagName = latestRelease.get(tagNameKey).getAsString();
binaryRelease = new ExternalBinary(tagName);
binaryRelease.setUrl(findReleaseBinaryUrl(latestRelease, binaryRelease.getVersion()));
cache.store(binaryRelease, uniqueKey, extractModificationDate(response));
} else {
binaryRelease = cache.load(uniqueKey, ExternalBinary.class);
}
return binaryRelease;
}
protected Map<String, String> lastModificationHeader() {
final Map<String, String> headers = new HashMap<>();
headers.put(IF_MODIFIED_SINCE, cache.lastModificationOf(uniqueKey)
.withZoneSameInstant(ZoneId.of("GMT"))
.format(Rfc2126DateTimeFormatter.INSTANCE));
return headers;
}
protected ZonedDateTime extractModificationDate(HttpClient.Response response) {
final String modificationDate = response.getHeader(LAST_MODIFIED);
final DateTimeFormatter dateTimeFormatter = Rfc2126DateTimeFormatter.INSTANCE;
return ZonedDateTime.parse(modificationDate, dateTimeFormatter);
}
@Override
public ExternalBinary getReleaseForVersion(String version) throws Exception {
final JsonArray releases =
sentGetRequest(projectUrl + RELEASES_URL, Collections.emptyMap(), true).getAsJsonArray();
if (releases != null) {
for (JsonElement release : releases) {
JsonObject releaseObject = release.getAsJsonObject();
String releaseTagName = releaseObject.get(tagNameKey).getAsString();
if (version.equals(releaseTagName)) {
final ExternalBinary binaryRelease = new ExternalBinary(releaseTagName);
binaryRelease.setUrl(findReleaseBinaryUrl(releaseObject, binaryRelease.getVersion()));
return binaryRelease;
}
}
log.warning(
"There wasn't found any release for the version: " + version + " in the repository: " + projectUrl);
}
return null;
}
protected String findReleaseBinaryUrl(JsonObject releaseObject, String version) throws Exception {
final JsonArray assets = releaseObject.get(assetsKey).getAsJsonArray();
for (JsonElement asset : assets) {
JsonObject assetJson = asset.getAsJsonObject();
String name = assetJson.get(assetNameKey).getAsString();
if (name.matches(getExpectedFileNameRegex(version))) {
return assetJson.get(browserDownloadUrlKey).getAsString();
}
}
return null;
}
private JsonElement sentGetRequest(String url, Map<String, String> headers, boolean withPagination) throws Exception {
final HttpClient.Response response = sentGetRequestWithPagination(url, 1, headers);
JsonElement result = gson.fromJson(response.getPayload(), JsonElement.class);
if (result != null && result.isJsonArray()) {
JsonArray resultArray = result.getAsJsonArray();
int i = 2;
while (true) {
final HttpClient.Response nextResponse = sentGetRequestWithPagination(url, i, headers);
JsonArray page = gson.fromJson(nextResponse.getPayload(), JsonArray.class);
if (page.size() == 0) {
break;
}
resultArray.addAll(page);
if (!withPagination) {
break;
}
i++;
}
return resultArray;
}
return result;
}
protected HttpClient.Response sentGetRequestWithPagination(String url, int pageNumber, Map<String, String> headers)
throws Exception {
final URI uri = new URIBuilder(url).setParameter("page", String.valueOf(pageNumber)).build();
return httpClient.get(uri.toString(), headers);
}
protected String getProjectUrl() {
return projectUrl;
}
protected Gson getGson() {
return gson;
}
protected String getUniqueKey() {
return uniqueKey;
}
protected GitHubLastUpdateCache getCache() {
return cache;
}
}