Skip to content

Commit 65346a4

Browse files
committed
Allow proxying the new server management protocol added in 1.21.9 through the Exaroton API
1 parent 0be5d32 commit 65346a4

File tree

14 files changed

+235
-7
lines changed

14 files changed

+235
-7
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.SONATYPE_OSSRH_PASSWORD }}
3434
EXAROTON_API_TOKEN: ${{ secrets.EXAROTON_API_KEY }}
3535
EXAROTON_TEST_SERVER: "WgvSsfR8ZizUO1RQ"
36+
EXAROTON_TEST_SERVER_NAME: "tests4ET"
3637
EXAROTON_TEST_POOL: "N2t9gWOMpzRL37FI"
3738
run: ./gradlew clean publish jreleaserDeploy -Prelease=${{ github.ref_name }}
3839

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ jobs:
2626
env:
2727
EXAROTON_API_TOKEN: ${{ secrets.EXAROTON_API_KEY }}
2828
EXAROTON_TEST_SERVER: "WgvSsfR8ZizUO1RQ"
29+
EXAROTON_TEST_SERVER_NAME: "tests4ET"
2930
EXAROTON_TEST_POOL: "N2t9gWOMpzRL37FI"

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 2.4.0
2+
## New Features
3+
- Allow proxying the new server management protocol added in 1.21.9 through the Exaroton API
4+
5+
---
6+
17
# 2.3.0
28
## New Features
39
- Add Endpoint to extend automatic server shutdown timer

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ dependencies {
3131
}
3232

3333
test {
34+
def trustStorePath = System.getenv('JAVA_TRUSTSTORE')
35+
if (trustStorePath) {
36+
jvmArgs "-Djavax.net.ssl.trustStore=${trustStorePath}"
37+
}
3438
useJUnitPlatform()
3539
testLogging {
3640
events "passed", "skipped", "failed"

src/main/java/com/exaroton/api/ExarotonClient.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class ExarotonClient {
4040
/**
4141
* API host
4242
*/
43-
private final String host = "api.exaroton.com";
43+
private String host = "api.exaroton.com";
4444

4545
/**
4646
* API base path
@@ -99,6 +99,15 @@ public ExarotonClient setUserAgent(String userAgent) {
9999
return this;
100100
}
101101

102+
@ApiStatus.Internal
103+
public void setHost(String host) {
104+
if (host == null || host.isBlank()) {
105+
throw new IllegalArgumentException("No host specified");
106+
}
107+
108+
this.host = host;
109+
}
110+
102111
/**
103112
* @return API host
104113
*/

src/main/java/com/exaroton/api/server/Server.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.exaroton.api.ws.stream.*;
88
import com.exaroton.api.ws.subscriber.*;
99
import com.google.gson.Gson;
10+
import com.google.gson.JsonElement;
1011
import org.jetbrains.annotations.ApiStatus;
1112
import org.jetbrains.annotations.NotNull;
1213
import org.jetbrains.annotations.Nullable;
@@ -578,6 +579,41 @@ public void removeTickSubscriber(@NotNull TickSubscriber subscriber) {
578579
this.subscribe().removeStreamSubscriber(TickStream.class, subscriber);
579580
}
580581

582+
/**
583+
* Subscribe to server management notifications
584+
* @param subscriber management notification handler
585+
*/
586+
@ApiStatus.AvailableSince("2.4.0")
587+
public void addServerManagementNotificationSubscriber(@NotNull ManagementNotificationSubscriber subscriber) {
588+
this.subscribe().addStreamSubscriber(ServerManagementStream.class, subscriber);
589+
}
590+
591+
/**
592+
* Unsubscribe from server management notifications
593+
* @param subscriber management notification handler
594+
*/
595+
@ApiStatus.AvailableSince("2.4.0")
596+
public void removeServerManagementNotificationSubscriber(@NotNull ManagementNotificationSubscriber subscriber) {
597+
if (this.webSocket == null) {
598+
return;
599+
}
600+
601+
this.subscribe().removeStreamSubscriber(ServerManagementStream.class, subscriber);
602+
}
603+
604+
/**
605+
* Send a server management request. This requires the management server to be online. It also automatically
606+
* subscribes to the management stream if not already done. This means you have to manually unsubscribe using
607+
* {@link WebSocketConnection#unsubscribe(StreamType)}} if you want to close the connection.
608+
* @param method name of the method to call
609+
* @param params arguments for the method (can be null)
610+
* @return future that completes with the result of the request
611+
*/
612+
@ApiStatus.AvailableSince("2.4.0")
613+
public CompletableFuture<JsonElement> sendServerManagementRequest(@NotNull String method, @Nullable JsonElement params) {
614+
return this.subscribe().getOrCreateStream(ServerManagementStream.class).sendRequest(method, params);
615+
}
616+
581617
/**
582618
* Get the current WebSocketConnection. A new connection will be created automatically if a subscriber is added.
583619
* @return web socket connection or null

src/main/java/com/exaroton/api/ws/WebSocketConnection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ public CompletableFuture<Void> waitForReady() {
206206
return readyFuture;
207207
}
208208

209-
private <T extends Stream<?>> @NotNull T getOrCreateStream(Class<T> clazz) {
209+
@ApiStatus.Internal
210+
public <T extends Stream<?>> @NotNull T getOrCreateStream(Class<T> clazz) {
210211
synchronized (streams) {
211212
@SuppressWarnings("unchecked") T stream = (T) this.streams.computeIfAbsent(clazz, this::createAndStartStream);
212213
return stream;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.exaroton.api.ws.stream;
2+
3+
import com.exaroton.api.server.ServerStatus;
4+
import com.exaroton.api.ws.WebSocketConnection;
5+
import com.exaroton.api.ws.subscriber.ManagementNotificationSubscriber;
6+
import com.google.gson.Gson;
7+
import com.google.gson.JsonElement;
8+
import com.google.gson.JsonObject;
9+
import org.jetbrains.annotations.ApiStatus;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
import java.util.*;
14+
import java.util.concurrent.CompletableFuture;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
17+
@ApiStatus.Internal
18+
public final class ServerManagementStream extends Stream<ManagementNotificationSubscriber> {
19+
private final Map<UUID, CompletableFuture<JsonElement>> waitingForResponse = new ConcurrentHashMap<>();
20+
21+
public ServerManagementStream(@NotNull WebSocketConnection ws, @NotNull Gson gson) {
22+
super(ws, gson);
23+
}
24+
25+
@Override
26+
public StreamType getType() {
27+
return StreamType.MANAGEMENT;
28+
}
29+
30+
@Override
31+
protected void onDataMessage(String type, JsonObject message) {
32+
var data = message.getAsJsonObject("data");
33+
switch (type) {
34+
case "notification":
35+
for (ManagementNotificationSubscriber subscriber : getSubscribers()) {
36+
subscriber.handleNotification(data.get("name").getAsString(), data.get("data"));
37+
}
38+
break;
39+
case "response":
40+
var id = UUID.fromString(data.get("id").getAsString());
41+
var future = waitingForResponse.remove(id);
42+
if (future != null) {
43+
future.complete(data.get("data"));
44+
}
45+
break;
46+
}
47+
}
48+
49+
@Override
50+
protected Set<ServerStatus> getStartableStatuses() {
51+
return Set.of(ServerStatus.ONLINE);
52+
}
53+
54+
@Override
55+
public boolean hasNoSubscribers() {
56+
return false;
57+
}
58+
59+
public CompletableFuture<JsonElement> sendRequest(@NotNull String method, @Nullable JsonElement params) {
60+
return this.shouldBeStarted().thenCompose(shouldStart -> {
61+
if (!shouldStart) {
62+
throw new IllegalStateException("The management stream is not active.");
63+
}
64+
65+
var future = new CompletableFuture<JsonElement>();
66+
var id = UUID.randomUUID();
67+
waitingForResponse.put(id, future);
68+
var item = new Request(id, method, params);
69+
this.sendWhenStarted(messageData("request", item));
70+
return future;
71+
});
72+
}
73+
74+
@SuppressWarnings({"FieldCanBeLocal", "unused"})
75+
private static final class Request {
76+
private final UUID id;
77+
private final String method;
78+
private final JsonElement params;
79+
80+
public Request(UUID id, String method, JsonElement params) {
81+
this.id = id;
82+
this.method = method;
83+
this.params = params;
84+
}
85+
}
86+
}

src/main/java/com/exaroton/api/ws/stream/Stream.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.ArrayList;
1212
import java.util.List;
1313
import java.util.Objects;
14+
import java.util.Set;
1415
import java.util.concurrent.CompletableFuture;
1516

1617
@ApiStatus.Internal
@@ -19,7 +20,7 @@ public abstract class Stream<T> {
1920
/**
2021
* Has this stream been started?
2122
*/
22-
private boolean started;
23+
protected boolean started;
2324

2425
/**
2526
* Should this stream be started when the server is ready?
@@ -196,7 +197,11 @@ protected CompletableFuture<Boolean> shouldBeStarted() {
196197
return CompletableFuture.completedFuture(false);
197198
}
198199

199-
return ws.serverHasStatus(
200+
return ws.serverHasStatus(this.getStartableStatuses());
201+
}
202+
203+
protected Set<ServerStatus> getStartableStatuses() {
204+
return Set.of(
200205
ServerStatus.ONLINE,
201206
ServerStatus.STARTING,
202207
ServerStatus.STOPPING,

src/main/java/com/exaroton/api/ws/stream/StreamType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public enum StreamType {
1515
STATUS("status", ServerStatusStream.class, ServerStatusStream::new),
1616
STATS("stats", StatsStream.class, StatsStream::new),
1717
TICK("tick", TickStream.class, TickStream::new),
18-
;
18+
@ApiStatus.AvailableSince("2.4.0")
19+
MANAGEMENT("management", ServerManagementStream.class, ServerManagementStream::new);
1920

2021
private final String name;
2122
private final Class<? extends Stream<?>> streamClass;

0 commit comments

Comments
 (0)