Moved code again
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2026 WinniePatGG
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*/
|
||||
|
||||
package licenselib;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.*;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A client for checking plugin licenses against a license server.
|
||||
*/
|
||||
public class LicenseClient {
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private LicenseClient() {
|
||||
throw new UnsupportedOperationException("Utility class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of a license key for a given plugin and server ID against the license server API.
|
||||
* @param apiUrl Server API url
|
||||
* @param plugin Plugin id matching the one on the backend api
|
||||
* @param licenseKey License key issued by the api
|
||||
* @param serverId Server id matching the one on the backend api
|
||||
* @return LicenseResult with the data from the backend
|
||||
*/
|
||||
public static LicenseResult check(String apiUrl, String plugin, String licenseKey, String serverId) {
|
||||
try {
|
||||
HttpRequest request = createRequest(apiUrl, plugin, licenseKey, serverId);
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
return fromHttpResponse(response);
|
||||
} catch (Exception e) {
|
||||
return errorResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously checks the validity of a license key for a given plugin and server ID against the license server API.
|
||||
* @param apiUrl Server API url
|
||||
* @param plugin Plugin id matching the one on the backend api
|
||||
* @param licenseKey License key issued by the api
|
||||
* @param serverId Server id matching the one on the backend api
|
||||
* @return CompletableFuture with the LicenseResult from the backend
|
||||
*/
|
||||
public static CompletableFuture<LicenseResult> checkAsync(String apiUrl, String plugin, String licenseKey, String serverId) {
|
||||
try {
|
||||
HttpRequest request = createRequest(apiUrl, plugin, licenseKey, serverId);
|
||||
|
||||
return HTTP_CLIENT
|
||||
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(LicenseClient::fromHttpResponse)
|
||||
.exceptionally(LicenseClient::errorResult);
|
||||
} catch (Exception e) {
|
||||
return CompletableFuture.completedFuture(errorResult(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the result of a license check, which can be either a success with license details or an error with a message.
|
||||
*/
|
||||
public sealed interface LicenseResult permits LicenseSuccess, LicenseError {}
|
||||
|
||||
private static HttpRequest createRequest(
|
||||
String apiUrl,
|
||||
String plugin,
|
||||
String licenseKey,
|
||||
String serverId
|
||||
) {
|
||||
URI uri = validateUrl(apiUrl);
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("plugin", plugin);
|
||||
payload.addProperty("licenseKey", licenseKey);
|
||||
|
||||
if (serverId != null) {
|
||||
payload.addProperty("serverId", serverId);
|
||||
}
|
||||
|
||||
return HttpRequest.newBuilder()
|
||||
.uri(uri)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(payload)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static LicenseResult errorResult(Throwable throwable) {
|
||||
String message = buildErrorMessage(throwable);
|
||||
return new LicenseError(message);
|
||||
}
|
||||
|
||||
private static String getString(JsonObject json, String key, String def) {
|
||||
return json.has(key) && !json.get(key).isJsonNull()
|
||||
? json.get(key).getAsString()
|
||||
: def;
|
||||
}
|
||||
|
||||
private static LicenseResult fromHttpResponse(HttpResponse<String> response) {
|
||||
if (response.statusCode() != 200) {
|
||||
return errorResult(new RuntimeException(
|
||||
"HTTP " + response.statusCode() + ": " + response.body()
|
||||
));
|
||||
}
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
boolean valid = json.has("valid") && json.get("valid").getAsBoolean();
|
||||
|
||||
return new LicenseSuccess(
|
||||
valid,
|
||||
getString(json, "status", "unknown"),
|
||||
getString(json, "message", ""),
|
||||
getString(json, "plugin", null),
|
||||
getString(json, "customer", null),
|
||||
getString(json, "expiresAt", null),
|
||||
getString(json, "checkedAt", null)
|
||||
);
|
||||
}
|
||||
|
||||
private static URI validateUrl(String apiUrl) {
|
||||
URI uri = URI.create(apiUrl);
|
||||
|
||||
if (uri.getScheme() == null || (!uri.getScheme().equals("http") && !uri.getScheme().equals("https"))) {
|
||||
throw new IllegalArgumentException("Invalid URL scheme: " + apiUrl);
|
||||
}
|
||||
|
||||
if (uri.getHost() == null) {
|
||||
throw new IllegalArgumentException("Invalid URL (missing host): " + apiUrl);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private static String buildErrorMessage(Throwable t) {
|
||||
Throwable root = t;
|
||||
|
||||
while (root.getCause() != null) {
|
||||
root = root.getCause();
|
||||
}
|
||||
|
||||
String msg = root.getMessage();
|
||||
|
||||
if (msg == null || msg.isBlank()) {
|
||||
msg = root.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
return root.getClass().getSimpleName() + ": " + msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package licenselib;
|
||||
|
||||
/**
|
||||
* Represents an error that occurred during the license check process, such as network issues or invalid responses from the server.
|
||||
* @param message A descriptive error message providing details about the failure.
|
||||
*/
|
||||
public record LicenseError(
|
||||
String message
|
||||
) implements LicenseClient.LicenseResult {
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package licenselib;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Periodically checks a license and exposes the most recent result.
|
||||
*/
|
||||
public final class LicenseScheduler implements AutoCloseable {
|
||||
|
||||
private final ScheduledExecutorService executor;
|
||||
private final ScheduledFuture<?> task;
|
||||
private final AtomicReference<LicenseClient.LicenseResult> lastResult;
|
||||
|
||||
private LicenseScheduler(
|
||||
ScheduledExecutorService executor,
|
||||
ScheduledFuture<?> task,
|
||||
AtomicReference<LicenseClient.LicenseResult> lastResult
|
||||
) {
|
||||
this.executor = executor;
|
||||
this.task = task;
|
||||
this.lastResult = lastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a scheduler that checks the license at the given interval.
|
||||
* @param interval Interval between checks
|
||||
* @param apiUrl Server API url
|
||||
* @param plugin Plugin id matching the one on the backend api
|
||||
* @param licenseKey License key issued by the api
|
||||
* @param serverId Server id matching the one on the backend api
|
||||
* @param onResult Optional callback invoked after every check
|
||||
* @return Scheduler handle for stopping and reading the last result
|
||||
*/
|
||||
public static LicenseScheduler startScheduler(
|
||||
Duration interval,
|
||||
String apiUrl,
|
||||
String plugin,
|
||||
String licenseKey,
|
||||
String serverId,
|
||||
Consumer<LicenseClient.LicenseResult> onResult
|
||||
) {
|
||||
Objects.requireNonNull(interval, "interval");
|
||||
if (interval.isZero() || interval.isNegative()) {
|
||||
throw new IllegalArgumentException("interval must be positive");
|
||||
}
|
||||
|
||||
AtomicReference<LicenseClient.LicenseResult> lastResult = new AtomicReference<>();
|
||||
ThreadFactory threadFactory = runnable -> {
|
||||
Thread thread = new Thread(runnable, "license-scheduler");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
};
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
|
||||
ScheduledFuture<?> task = executor.scheduleAtFixedRate(() -> {
|
||||
LicenseClient.LicenseResult result = LicenseClient.check(apiUrl, plugin, licenseKey, serverId);
|
||||
lastResult.set(result);
|
||||
if (onResult != null) {
|
||||
onResult.accept(result);
|
||||
}
|
||||
}, 0, interval.toMillis(), TimeUnit.MILLISECONDS);
|
||||
|
||||
return new LicenseScheduler(executor, task, lastResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a scheduler that retries on errors before invoking offline handling.
|
||||
* @param interval Interval between checks
|
||||
* @param apiUrl Server API url
|
||||
* @param plugin Plugin id matching the one on the backend api
|
||||
* @param licenseKey License key issued by the api
|
||||
* @param serverId Server id matching the one on the backend api
|
||||
* @param maxRetries Number of retries after a failed check
|
||||
* @param retryDelay Delay between retries
|
||||
* @param onResult Optional callback invoked after every attempt
|
||||
* @param onOffline Invoked only after all retries fail
|
||||
* @return Scheduler handle for stopping and reading the last result
|
||||
*/
|
||||
public static LicenseScheduler startSchedulerWithRetries(
|
||||
Duration interval,
|
||||
String apiUrl,
|
||||
String plugin,
|
||||
String licenseKey,
|
||||
String serverId,
|
||||
int maxRetries,
|
||||
Duration retryDelay,
|
||||
Consumer<LicenseClient.LicenseResult> onResult,
|
||||
Consumer<LicenseClient.LicenseResult> onOffline
|
||||
) {
|
||||
Objects.requireNonNull(interval, "interval");
|
||||
Objects.requireNonNull(retryDelay, "retryDelay");
|
||||
if (interval.isZero() || interval.isNegative()) {
|
||||
throw new IllegalArgumentException("interval must be positive");
|
||||
}
|
||||
if (retryDelay.isNegative()) {
|
||||
throw new IllegalArgumentException("retryDelay must not be negative");
|
||||
}
|
||||
if (maxRetries < 0) {
|
||||
throw new IllegalArgumentException("maxRetries must not be negative");
|
||||
}
|
||||
|
||||
AtomicReference<LicenseClient.LicenseResult> lastResult = new AtomicReference<>();
|
||||
ThreadFactory threadFactory = runnable -> {
|
||||
Thread thread = new Thread(runnable, "license-scheduler");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
};
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
|
||||
ScheduledFuture<?> task = executor.scheduleAtFixedRate(() -> {
|
||||
LicenseClient.LicenseResult result = attemptWithRetries(
|
||||
apiUrl,
|
||||
plugin,
|
||||
licenseKey,
|
||||
serverId,
|
||||
maxRetries,
|
||||
retryDelay,
|
||||
onResult,
|
||||
onOffline
|
||||
);
|
||||
lastResult.set(result);
|
||||
}, 0, interval.toMillis(), TimeUnit.MILLISECONDS);
|
||||
|
||||
return new LicenseScheduler(executor, task, lastResult);
|
||||
}
|
||||
|
||||
private static LicenseClient.LicenseResult attemptWithRetries(
|
||||
String apiUrl,
|
||||
String plugin,
|
||||
String licenseKey,
|
||||
String serverId,
|
||||
int maxRetries,
|
||||
Duration retryDelay,
|
||||
Consumer<LicenseClient.LicenseResult> onResult,
|
||||
Consumer<LicenseClient.LicenseResult> onOffline
|
||||
) {
|
||||
int attempt = 0;
|
||||
LicenseClient.LicenseResult result;
|
||||
|
||||
while (true) {
|
||||
result = LicenseClient.check(apiUrl, plugin, licenseKey, serverId);
|
||||
if (onResult != null) {
|
||||
onResult.accept(result);
|
||||
}
|
||||
if (!(result instanceof LicenseError)) {
|
||||
return result;
|
||||
}
|
||||
if (attempt >= maxRetries) {
|
||||
if (onOffline != null) {
|
||||
onOffline.accept(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
attempt++;
|
||||
if (!retryDelay.isZero()) {
|
||||
try {
|
||||
Thread.sleep(retryDelay.toMillis());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent license check result, or null if none has completed yet.
|
||||
* @return The last license check result, or null if not available
|
||||
*/
|
||||
public LicenseClient.LicenseResult getLastResult() {
|
||||
return lastResult.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops further checks and shuts down the scheduler.
|
||||
*/
|
||||
public void stop() {
|
||||
task.cancel(false);
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package licenselib;
|
||||
|
||||
/**
|
||||
* Represents a successful license check result, containing all relevant information about the license status, plugin, customer, and expiration details.
|
||||
* @param valid Indicates whether the license is valid or not.
|
||||
* @param status A string representing the status of the license, such as "valid", "invalid", "expired", etc.
|
||||
* @param message A message providing additional information about the license status, which can be used for logging or debugging purposes.
|
||||
* @param plugin The name of the plugin associated with the license, which can be used to identify which plugin the license check was performed for.
|
||||
* @param customer The name of the customer or organization that owns the license, which can be useful for tracking and support purposes.
|
||||
* @param expiresAt The expiration date of the license in ISO 8601 format (e.g., "2024-12-31T23:59:59Z"), which indicates when the license will no longer be valid.
|
||||
* @param checkedAt The date and time when the license check was performed, also in ISO 8601 format, which can be used to determine how recent the license information is.
|
||||
*/
|
||||
public record LicenseSuccess(
|
||||
boolean valid,
|
||||
String status,
|
||||
String message,
|
||||
String plugin,
|
||||
String customer,
|
||||
String expiresAt,
|
||||
String checkedAt
|
||||
) implements LicenseClient.LicenseResult {
|
||||
}
|
||||
Reference in New Issue
Block a user