mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-04-22 23:08:53 +02:00
Update AGENTS guidelines (#3556)
## Summary - clarify Codex contribution instructions - remove `test.sh` reference and require `./gradlew build` - add Developer Guide, AI note and translation policy ## Testing - `./gradlew spotlessApply` - `./gradlew build`
This commit is contained in:
@@ -47,19 +47,20 @@ public class KeygenLicenseVerifier {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
|
||||
// Shared HTTP client for connection pooling
|
||||
private static final HttpClient httpClient = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.connectTimeout(java.time.Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
private static final HttpClient httpClient =
|
||||
HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.connectTimeout(java.time.Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
// License metadata context class to avoid shared mutable state
|
||||
private static class LicenseContext {
|
||||
private boolean isFloatingLicense = false;
|
||||
private int maxMachines = 1; // Default to 1 if not specified
|
||||
private boolean isEnterpriseLicense = false;
|
||||
|
||||
|
||||
public LicenseContext() {}
|
||||
}
|
||||
|
||||
@@ -248,7 +249,7 @@ public class KeygenLicenseVerifier {
|
||||
// Check for floating license
|
||||
context.isFloatingLicense = attributesObj.optBoolean("floating", false);
|
||||
context.maxMachines = attributesObj.optInt("maxMachines", 1);
|
||||
|
||||
|
||||
// Extract metadata
|
||||
JSONObject metadataObj = attributesObj.optJSONObject("metadata");
|
||||
if (metadataObj != null) {
|
||||
@@ -411,14 +412,16 @@ public class KeygenLicenseVerifier {
|
||||
// Check for floating license in policy
|
||||
boolean policyFloating = policyObj.optBoolean("floating", false);
|
||||
int policyMaxMachines = policyObj.optInt("maxMachines", 1);
|
||||
|
||||
|
||||
// Policy settings take precedence
|
||||
if (policyFloating) {
|
||||
context.isFloatingLicense = true;
|
||||
context.maxMachines = policyMaxMachines;
|
||||
log.info("Policy defines floating license with max machines: {}", context.maxMachines);
|
||||
log.info(
|
||||
"Policy defines floating license with max machines: {}",
|
||||
context.maxMachines);
|
||||
}
|
||||
|
||||
|
||||
// Extract max users and isEnterprise from policy or metadata
|
||||
int users = policyObj.optInt("users", 1);
|
||||
context.isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false);
|
||||
@@ -474,7 +477,8 @@ public class KeygenLicenseVerifier {
|
||||
activateMachine(licenseKey, licenseId, machineFingerprint, context);
|
||||
if (activated) {
|
||||
// Revalidate after activation
|
||||
validationResponse = validateLicense(licenseKey, machineFingerprint, context);
|
||||
validationResponse =
|
||||
validateLicense(licenseKey, machineFingerprint, context);
|
||||
isValid =
|
||||
validationResponse != null
|
||||
&& validationResponse
|
||||
@@ -494,8 +498,8 @@ public class KeygenLicenseVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode validateLicense(String licenseKey, String machineFingerprint, LicenseContext context)
|
||||
throws Exception {
|
||||
private JsonNode validateLicense(
|
||||
String licenseKey, String machineFingerprint, LicenseContext context) throws Exception {
|
||||
String requestBody =
|
||||
String.format(
|
||||
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
||||
@@ -514,7 +518,8 @@ public class KeygenLicenseVerifier {
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
HttpResponse<String> response =
|
||||
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.info("ValidateLicenseResponse body: {}", response.body());
|
||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||
if (response.statusCode() == 200) {
|
||||
@@ -527,21 +532,23 @@ public class KeygenLicenseVerifier {
|
||||
log.info("License validity: " + isValid);
|
||||
log.info("Validation detail: " + detail);
|
||||
log.info("Validation code: " + code);
|
||||
|
||||
|
||||
// Check if the license itself has floating attribute
|
||||
JsonNode licenseAttrs = jsonResponse.path("data").path("attributes");
|
||||
if (!licenseAttrs.isMissingNode()) {
|
||||
context.isFloatingLicense = licenseAttrs.path("floating").asBoolean(false);
|
||||
context.maxMachines = licenseAttrs.path("maxMachines").asInt(1);
|
||||
|
||||
log.info("License floating (from license): {}, maxMachines: {}",
|
||||
context.isFloatingLicense, context.maxMachines);
|
||||
|
||||
log.info(
|
||||
"License floating (from license): {}, maxMachines: {}",
|
||||
context.isFloatingLicense,
|
||||
context.maxMachines);
|
||||
}
|
||||
|
||||
|
||||
// Also check the policy for floating license support if included
|
||||
JsonNode includedNode = jsonResponse.path("included");
|
||||
JsonNode policyNode = null;
|
||||
|
||||
|
||||
if (includedNode.isArray()) {
|
||||
for (JsonNode node : includedNode) {
|
||||
if ("policies".equals(node.path("type").asText())) {
|
||||
@@ -550,20 +557,23 @@ public class KeygenLicenseVerifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (policyNode != null) {
|
||||
// Check if this is a floating license from policy
|
||||
boolean policyFloating = policyNode.path("attributes").path("floating").asBoolean(false);
|
||||
boolean policyFloating =
|
||||
policyNode.path("attributes").path("floating").asBoolean(false);
|
||||
int policyMaxMachines = policyNode.path("attributes").path("maxMachines").asInt(1);
|
||||
|
||||
|
||||
// Policy takes precedence over license attributes
|
||||
if (policyFloating) {
|
||||
context.isFloatingLicense = true;
|
||||
context.maxMachines = policyMaxMachines;
|
||||
}
|
||||
|
||||
log.info("License floating (from policy): {}, maxMachines: {}",
|
||||
context.isFloatingLicense, context.maxMachines);
|
||||
|
||||
log.info(
|
||||
"License floating (from policy): {}, maxMachines: {}",
|
||||
context.isFloatingLicense,
|
||||
context.maxMachines);
|
||||
}
|
||||
|
||||
// Extract user count, default to 1 if not specified
|
||||
@@ -593,86 +603,104 @@ public class KeygenLicenseVerifier {
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint,
|
||||
LicenseContext context) throws Exception {
|
||||
private boolean activateMachine(
|
||||
String licenseKey, String licenseId, String machineFingerprint, LicenseContext context)
|
||||
throws Exception {
|
||||
// For floating licenses, we first need to check if we need to deregister any machines
|
||||
if (context.isFloatingLicense) {
|
||||
log.info("Processing floating license activation. Max machines allowed: {}", context.maxMachines);
|
||||
|
||||
log.info(
|
||||
"Processing floating license activation. Max machines allowed: {}",
|
||||
context.maxMachines);
|
||||
|
||||
// Get the current machines for this license
|
||||
JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId);
|
||||
if (machinesResponse != null) {
|
||||
JsonNode machines = machinesResponse.path("data");
|
||||
int currentMachines = machines.size();
|
||||
|
||||
log.info("Current machine count: {}, Max allowed: {}", currentMachines, context.maxMachines);
|
||||
|
||||
|
||||
log.info(
|
||||
"Current machine count: {}, Max allowed: {}",
|
||||
currentMachines,
|
||||
context.maxMachines);
|
||||
|
||||
// Check if the current fingerprint is already activated
|
||||
boolean isCurrentMachineActivated = false;
|
||||
String currentMachineId = null;
|
||||
|
||||
|
||||
for (JsonNode machine : machines) {
|
||||
if (machineFingerprint.equals(machine.path("attributes").path("fingerprint").asText())) {
|
||||
if (machineFingerprint.equals(
|
||||
machine.path("attributes").path("fingerprint").asText())) {
|
||||
isCurrentMachineActivated = true;
|
||||
currentMachineId = machine.path("id").asText();
|
||||
log.info("Current machine is already activated with ID: {}", currentMachineId);
|
||||
log.info(
|
||||
"Current machine is already activated with ID: {}",
|
||||
currentMachineId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If the current machine is already activated, there's no need to do anything
|
||||
if (isCurrentMachineActivated) {
|
||||
log.info("Machine already activated. No action needed.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// If we've reached the max machines limit, we need to deregister the oldest machine
|
||||
if (currentMachines >= context.maxMachines) {
|
||||
log.info("Max machines reached. Deregistering oldest machine to make room for the new machine.");
|
||||
|
||||
log.info(
|
||||
"Max machines reached. Deregistering oldest machine to make room for the new machine.");
|
||||
|
||||
// Find the oldest machine based on creation timestamp
|
||||
if (machines.size() > 0) {
|
||||
// Find the machine with the oldest creation date
|
||||
String oldestMachineId = null;
|
||||
java.time.Instant oldestTime = null;
|
||||
|
||||
|
||||
for (JsonNode machine : machines) {
|
||||
String createdStr = machine.path("attributes").path("created").asText(null);
|
||||
String createdStr =
|
||||
machine.path("attributes").path("created").asText(null);
|
||||
if (createdStr != null && !createdStr.isEmpty()) {
|
||||
try {
|
||||
java.time.Instant createdTime = java.time.Instant.parse(createdStr);
|
||||
java.time.Instant createdTime =
|
||||
java.time.Instant.parse(createdStr);
|
||||
if (oldestTime == null || createdTime.isBefore(oldestTime)) {
|
||||
oldestTime = createdTime;
|
||||
oldestMachineId = machine.path("id").asText();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not parse creation time for machine: {}", e.getMessage());
|
||||
log.warn(
|
||||
"Could not parse creation time for machine: {}",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we couldn't determine the oldest by timestamp, use the first one
|
||||
if (oldestMachineId == null) {
|
||||
log.warn("Could not determine oldest machine by timestamp, using first machine in list");
|
||||
log.warn(
|
||||
"Could not determine oldest machine by timestamp, using first machine in list");
|
||||
oldestMachineId = machines.path(0).path("id").asText();
|
||||
}
|
||||
|
||||
|
||||
log.info("Deregistering machine with ID: {}", oldestMachineId);
|
||||
|
||||
|
||||
boolean deregistered = deregisterMachine(licenseKey, oldestMachineId);
|
||||
if (!deregistered) {
|
||||
log.error("Failed to deregister machine. Cannot proceed with activation.");
|
||||
log.error(
|
||||
"Failed to deregister machine. Cannot proceed with activation.");
|
||||
return false;
|
||||
}
|
||||
log.info("Machine deregistered successfully. Proceeding with activation of new machine.");
|
||||
log.info(
|
||||
"Machine deregistered successfully. Proceeding with activation of new machine.");
|
||||
} else {
|
||||
log.error("License has reached machine limit but no machines were found to deregister. This is unexpected.");
|
||||
log.error(
|
||||
"License has reached machine limit but no machines were found to deregister. This is unexpected.");
|
||||
// We'll still try to activate, but it might fail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Proceed with machine activation
|
||||
String hostname;
|
||||
try {
|
||||
@@ -720,7 +748,8 @@ public class KeygenLicenseVerifier {
|
||||
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
HttpResponse<String> response =
|
||||
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.info("activateMachine Response body: " + response.body());
|
||||
if (response.statusCode() == 201) {
|
||||
log.info("Machine activated successfully");
|
||||
@@ -738,61 +767,76 @@ public class KeygenLicenseVerifier {
|
||||
private String generateMachineFingerprint() {
|
||||
return GeneralUtils.generateMachineFingerprint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches all machines associated with a specific license
|
||||
*
|
||||
*
|
||||
* @param licenseKey The license key to check
|
||||
* @param licenseId The license ID
|
||||
* @param licenseId The license ID
|
||||
* @return JsonNode containing the list of machines, or null if an error occurs
|
||||
* @throws Exception if an error occurs during the HTTP request
|
||||
*/
|
||||
private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/licenses/" + licenseId + "/machines"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Authorization", "License " + licenseKey)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(
|
||||
URI.create(
|
||||
BASE_URL
|
||||
+ "/"
|
||||
+ ACCOUNT_ID
|
||||
+ "/licenses/"
|
||||
+ licenseId
|
||||
+ "/machines"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Authorization", "License " + licenseKey)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response =
|
||||
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.info("fetchMachinesForLicense Response body: {}", response.body());
|
||||
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
return objectMapper.readTree(response.body());
|
||||
} else {
|
||||
log.error("Error fetching machines for license. Status code: {}, error: {}",
|
||||
response.statusCode(), response.body());
|
||||
log.error(
|
||||
"Error fetching machines for license. Status code: {}, error: {}",
|
||||
response.statusCode(),
|
||||
response.body());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deregisters a machine from a license
|
||||
*
|
||||
*
|
||||
* @param licenseKey The license key
|
||||
* @param machineId The ID of the machine to deregister
|
||||
* @return true if deregistration was successful, false otherwise
|
||||
*/
|
||||
private boolean deregisterMachine(String licenseKey, String machineId) {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Authorization", "License " + licenseKey)
|
||||
.DELETE()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Authorization", "License " + licenseKey)
|
||||
.DELETE()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response =
|
||||
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 204) {
|
||||
log.info("Machine {} successfully deregistered", machineId);
|
||||
return true;
|
||||
} else {
|
||||
log.error("Error deregistering machine. Status code: {}, error: {}",
|
||||
response.statusCode(), response.body());
|
||||
log.error(
|
||||
"Error deregistering machine. Status code: {}, error: {}",
|
||||
response.statusCode(),
|
||||
response.body());
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user