0.1.1 | Scheduler shenanigans
This commit is contained in:
@@ -68,3 +68,72 @@ public class main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Usage Scheduler (without retries)
|
||||||
|
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
LicenseScheduler scheduler = LicenseScheduler.startScheduler(
|
||||||
|
Duration.ofMinutes(5),
|
||||||
|
apiUrl,
|
||||||
|
plugin,
|
||||||
|
licenseKey,
|
||||||
|
serverId,
|
||||||
|
result -> {
|
||||||
|
if (result instanceof LicenseSuccess success) {
|
||||||
|
if (!success.valid()) {
|
||||||
|
// Handle invalid license
|
||||||
|
}
|
||||||
|
} else if (result instanceof LicenseError error) {
|
||||||
|
// Log errors if you want
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Later: Stop the scheduler
|
||||||
|
scheduler.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Usage Scheduler (with retries)
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
LicenseScheduler scheduler = LicenseScheduler.startSchedulerWithRetries(
|
||||||
|
Duration.ofMinutes(5),
|
||||||
|
apiUrl,
|
||||||
|
plugin,
|
||||||
|
licenseKey,
|
||||||
|
serverId,
|
||||||
|
3, //max retries,
|
||||||
|
Duration.ofSeconds(10), //delay between retries
|
||||||
|
result -> {
|
||||||
|
if(result instanceof LicenseSuccess success) {
|
||||||
|
if (!success.valid()) {
|
||||||
|
// Handle invalid license
|
||||||
|
}
|
||||||
|
} else if (result instanceof LicenseError error) {
|
||||||
|
// Log errors if you want
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result -> {
|
||||||
|
// Called only after all retries failed
|
||||||
|
// Put your "offline" handling here
|
||||||
|
// Disable plugin or something like that
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Later: Stop the scheduler
|
||||||
|
scheduler.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
group = 'de.winniepat'
|
|
||||||
version = '0.1.0'
|
|
||||||
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
|
||||||
}
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {url = "https://repo.papermc.io/repository/maven-public/" }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'com.google.code.gson:gson:2.13.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('test') {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
mavenJava(MavenPublication) {
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
url = uri("https://maven.winniepat.de/repository/maven-releases/")
|
|
||||||
|
|
||||||
credentials {
|
|
||||||
username = project.property("publish.username")
|
|
||||||
password = project.property("publish.password")
|
|
||||||
// the credentials from before are not valid anymore (I am not that dumb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+36
-27
@@ -1,41 +1,50 @@
|
|||||||
/*
|
|
||||||
* This file was generated by the Gradle 'init' task.
|
|
||||||
*
|
|
||||||
* This generated file contains a sample Java library project to get you started.
|
|
||||||
* For more details on building Java & JVM projects, please refer to https://docs.gradle.org/9.5.1/userguide/building_java_projects.html in the Gradle documentation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// Apply the java-library plugin for API and implementation separation.
|
id 'java'
|
||||||
id 'java-library'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
group = 'de.winniepat'
|
||||||
// Use Maven Central for resolving dependencies.
|
version = '0.1.1'
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// Use JUnit Jupiter for testing.
|
|
||||||
testImplementation libs.junit.jupiter
|
|
||||||
|
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
||||||
|
|
||||||
// This dependency is exported to consumers, that is to say found on their compile classpath.
|
|
||||||
api libs.commons.math3
|
|
||||||
|
|
||||||
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
|
|
||||||
implementation libs.guava
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply a specific Java toolchain to ease working on different environments.
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
}
|
}
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven {url = "https://repo.papermc.io/repository/maven-public/" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT'
|
||||||
|
implementation 'com.google.code.gson:gson:2.13.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
// Use JUnit Platform for unit tests.
|
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = uri("https://maven.winniepat.de/repository/maven-releases/")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = project.property("publish.username")
|
||||||
|
password = project.property("publish.password")
|
||||||
|
// the credentials from before are not valid anymore (I am not that dumb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* Licensed under the MIT License
|
* Licensed under the MIT License
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.winniepat.licenselib;
|
package licenselib;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package de.winniepat.licenselib;
|
package licenselib;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an error that occurred during the license check process, such as network issues or invalid responses from the server.
|
* Represents an error that occurred during the license check process, such as network issues or invalid responses from the server.
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package de.winniepat.licenselib;
|
package licenselib;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a successful license check result, containing all relevant information about the license status, plugin, customer, and expiration details.
|
* Represents a successful license check result, containing all relevant information about the license status, plugin, customer, and expiration details.
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
/*
|
|
||||||
* This source file was generated by the Gradle 'init' task
|
|
||||||
*/
|
|
||||||
package org.example;
|
|
||||||
|
|
||||||
public class Library {
|
|
||||||
public boolean someLibraryMethod() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* This source file was generated by the Gradle 'init' task
|
|
||||||
*/
|
|
||||||
package org.example;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class LibraryTest {
|
|
||||||
@Test void someLibraryMethodReturnsTrue() {
|
|
||||||
Library classUnderTest = new Library();
|
|
||||||
assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user