GgceVersionCheck.java
/*
* Copyright 2026 Global Crop Diversity Trust
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root folder or http://www.apache.org/licenses/LICENSE-2.0
*/
package org.gringlobal.worker;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.Strings;
import org.springframework.scheduling.annotation.Scheduled;
import org.gringlobal.service.TransientMessageService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
/**
* Check for latest GGCE release version
*/
@Component
@Slf4j
public class GgceVersionCheck {
@Value("${frontend.url}")
private String frontendUrl;
@Value("${build.version}")
private String buildVersion;
@Value("${disable.version.check:false}")
private boolean disableVersionCheck;
private final TransientMessageService transientMessageService;
private final ObjectMapper objectMapper;
public GgceVersionCheck(TransientMessageService transientMessageService, ObjectMapper objectMapper) {
this.transientMessageService = transientMessageService;
this.objectMapper = objectMapper;
}
/**
* Periodically checks for a newer GGCE version.
* If a newer version is found, an administrator alert is created.
*/
@Scheduled(initialDelayString = "PT10S", fixedDelayString = "P7D")
public void check() {
if (disableVersionCheck) {
return;
}
var latest = getLatestVersion();
if (latest != null) {
String latestVersion = latest.get("version");
log.info("Checking GGCE version: current={} latest={}", buildVersion, latestVersion);
if (isNewerVersion(latestVersion, buildVersion)) {
String releaseNotes = latest.get("releaseNotes");
transientMessageService.addAdminAlert(
"ggceVersionAlert",
String.format("GGCE %s is available! You are using %s. See https://ggce.genesys-pgr.org%s for more information",
latestVersion, buildVersion, releaseNotes)
);
}
}
}
public static boolean isNewerVersion(String latest, String current) {
if (Strings.CI.equals(latest, current)) {
return false;
}
String[] latestParts = latest.split("\\.");
String[] currentParts = current.split("\\.");
int length = Math.max(latestParts.length, currentParts.length);
for (int i = 0; i < length; i++) {
int l = i < latestParts.length ? getNumericPrefix(latestParts[i]) : 0;
int c = i < currentParts.length ? getNumericPrefix(currentParts[i]) : 0;
if (l > c) return true;
if (l < c) return false;
}
return false;
}
private static int getNumericPrefix(String part) {
if (part == null) return 0;
Matcher matcher = Pattern.compile("^(\\d+)").matcher(part);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
return 0;
}
/**
* Fetches the latest release information from the GGCE releases JSON endpoint.
*
* @return a map containing "version" and "releaseNotes", or null if fetching failed.
*/
public Map<String, String> getLatestVersion() {
try {
String userAgent = String.format("ggce-server/%s (%s; %s; %s)", buildVersion, System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
var client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://ggce.genesys-pgr.org/releases.json"))
.timeout(Duration.ofSeconds(10))
.header("User-Agent", userAgent)
.header("Referer", frontendUrl)
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
log.debug("GGCE releases.json: {}", response.body());
if (response.statusCode() == 200) {
var releases = objectMapper.readTree(response.body());
if (releases.isArray() && releases.size() > 0) {
var latestRelease = releases.get(0);
return Map.of(
"version", latestRelease.get("version").asText(),
"date", latestRelease.get("date").asText(),
"releaseNotes", latestRelease.get("releaseNotes").asText()
);
}
} else {
log.warn("Could not fetch GGCE releases, received status code: {}", response.statusCode());
}
} catch (Exception e) {
log.error("Error fetching GGCE version: {}", e.getMessage());
}
return null;
}
}