From 33d70e6c1d06d61fbd7ec438dcf313d977520792 Mon Sep 17 00:00:00 2001 From: Maploop Date: Mon, 1 Dec 2025 14:25:31 +0330 Subject: [PATCH 1/2] Upgrade to Java 21, add DataRequest system and RedisParsableMessage utility class. --- pom.xml | 4 +- .../net/swofty/redisapi/api/RedisAPI.java | 8 ++ .../redisapi/api/requests/DataRequest.java | 62 +++++++++++ .../api/requests/DataRequestResponder.java | 42 +++++++ .../redisapi/api/requests/DataResponse.java | 11 ++ .../api/requests/DataStreamListener.java | 38 +++++++ .../redisapi/util/RedisParsableMessage.java | 104 ++++++++++++++++++ 7 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/swofty/redisapi/api/requests/DataRequest.java create mode 100644 src/main/java/net/swofty/redisapi/api/requests/DataRequestResponder.java create mode 100644 src/main/java/net/swofty/redisapi/api/requests/DataResponse.java create mode 100644 src/main/java/net/swofty/redisapi/api/requests/DataStreamListener.java create mode 100644 src/main/java/net/swofty/redisapi/util/RedisParsableMessage.java diff --git a/pom.xml b/pom.xml index 3fa1dff..9f902c4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,8 +9,8 @@ 1.1.3 - 1.8 - 1.8 + 21 + 21 diff --git a/src/main/java/net/swofty/redisapi/api/RedisAPI.java b/src/main/java/net/swofty/redisapi/api/RedisAPI.java index 8e797eb..9082c3f 100644 --- a/src/main/java/net/swofty/redisapi/api/RedisAPI.java +++ b/src/main/java/net/swofty/redisapi/api/RedisAPI.java @@ -3,6 +3,7 @@ import lombok.AccessLevel; import lombok.NonNull; import lombok.experimental.FieldDefaults; +import net.swofty.redisapi.api.requests.DataStreamListener; import net.swofty.redisapi.events.EventRegistry; import net.swofty.redisapi.events.RedisMessagingReceiveEvent; import net.swofty.redisapi.events.RedisMessagingReceiveInterface; @@ -141,6 +142,13 @@ public static RedisAPI generateInstance(@NonNull String uri, String password) { * Starts listeners for the Redis Pub/Sub channels */ public void startListeners() { + try { + registerChannel("internal-data-request", DataStreamListener.class); + } catch (ChannelAlreadyRegisteredException ignored) { + System.out.println("[WARNING]: The internal data request channel has already been registered. This will cause issues if you are using the DataRequest API along with the Redis API." + + "\n Channel Name: internal-data-request"); + } + new Thread(() -> { try (Jedis jedis = getPool().getResource()) { EventRegistry.pubSub = new JedisPubSub() { diff --git a/src/main/java/net/swofty/redisapi/api/requests/DataRequest.java b/src/main/java/net/swofty/redisapi/api/requests/DataRequest.java new file mode 100644 index 0000000..0225260 --- /dev/null +++ b/src/main/java/net/swofty/redisapi/api/requests/DataRequest.java @@ -0,0 +1,62 @@ +package net.swofty.redisapi.api.requests; + +import net.swofty.redisapi.api.ChannelRegistry; +import net.swofty.redisapi.api.RedisAPI; +import net.swofty.redisapi.util.RedisParsableMessage; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class DataRequest { + public static final Map RECEIVED_DATA = new HashMap<>(); + + private final String id; + private final String filter; + private final String key; + private final JSONObject data; + + /** + * Create a new data request to get a specific object of data from a specific filter ID. + * @param filterID The filter ID where you want to receive data from, can be "all" for all listeners. + * @param key The data identifier key. + */ + public DataRequest(String filterID, String key, JSONObject data) { + this.id = UUID.randomUUID().toString(); + this.filter = filterID; + this.key = key; + this.data = data; + } + + public CompletableFuture await() { + return CompletableFuture.supplyAsync(() -> { + long start = System.currentTimeMillis(); + JSONObject request = new JSONObject(); + request.put("id", id); + request.put("key", key); + request.put("data", data); + request.put("sender", "proxy"); + request.put("stream", StreamType.REQUEST.name()); + + RedisAPI.getInstance().publishMessage(filter, ChannelRegistry.getFromName("internal-data-request"), RedisParsableMessage.from(request).formatForSend()); + + int timeout = 0; + while (!RECEIVED_DATA.containsKey(id)) { + try { Thread.sleep(1); timeout++; } catch (InterruptedException ignored) { } + if (timeout >= 100) break; + } + + JSONObject response = RECEIVED_DATA.get(id); + RECEIVED_DATA.remove(id); + long latency = (System.currentTimeMillis() - start); + return new DataResponse(response, latency); + }); + } + + public enum StreamType { + REQUEST, + RESPONSE + } +} diff --git a/src/main/java/net/swofty/redisapi/api/requests/DataRequestResponder.java b/src/main/java/net/swofty/redisapi/api/requests/DataRequestResponder.java new file mode 100644 index 0000000..936059b --- /dev/null +++ b/src/main/java/net/swofty/redisapi/api/requests/DataRequestResponder.java @@ -0,0 +1,42 @@ +package net.swofty.redisapi.api.requests; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class DataRequestResponder { + public static final Map RESPONDERS = new HashMap<>(); + + private final Function callback; + + protected DataRequestResponder(Function callback) { + this.callback = callback; + } + + public JSONObject respond(JSONObject request) { + return this.callback.apply(request); + } + + /** + * Creates a new Data Request Responder. Must be registered before {@link net.swofty.redisapi.api.RedisAPI#startListeners()} in order to work properly. + * @param key The key to respond to. + * @param callback Callback, has a JSONObject parameter request, and returns a JSONObject response, request and response both can be empty. + * @return The created DataRequestResponder, not entirely useful, but still there. + */ + public static DataRequestResponder create(String key, Function callback) { + DataRequestResponder responder = new DataRequestResponder(callback); + RESPONDERS.put(key, responder); + return responder; + } + + /** + * Get a DataRequestResponder by key. + * @param key The key to get the DataRequestResponder by. + * @return The DataRequestResponder, or null if it doesn't exist. + */ + public static DataRequestResponder get(String key) { + return RESPONDERS.get(key); + } +} diff --git a/src/main/java/net/swofty/redisapi/api/requests/DataResponse.java b/src/main/java/net/swofty/redisapi/api/requests/DataResponse.java new file mode 100644 index 0000000..dc4c88b --- /dev/null +++ b/src/main/java/net/swofty/redisapi/api/requests/DataResponse.java @@ -0,0 +1,11 @@ +package net.swofty.redisapi.api.requests; + +import org.json.JSONObject; + +/** + * The response to a DataRequest. + * @param data The data object, will be null if the request has timed out. + * @param latency The latency of the request, normal range is between 1-10ms, unless the server is under heavy load or the Redis server is running on a different machine. + */ +public record DataResponse(JSONObject data, long latency) { +} diff --git a/src/main/java/net/swofty/redisapi/api/requests/DataStreamListener.java b/src/main/java/net/swofty/redisapi/api/requests/DataStreamListener.java new file mode 100644 index 0000000..dd82a4c --- /dev/null +++ b/src/main/java/net/swofty/redisapi/api/requests/DataStreamListener.java @@ -0,0 +1,38 @@ +package net.swofty.redisapi.api.requests; + +import net.swofty.redisapi.api.ChannelRegistry; +import net.swofty.redisapi.api.RedisAPI; +import net.swofty.redisapi.events.RedisMessagingReceiveInterface; +import net.swofty.redisapi.util.RedisParsableMessage; +import org.json.JSONObject; + +public class DataStreamListener implements RedisMessagingReceiveInterface { + @Override + public void onMessage(String channel, String message) { + RedisParsableMessage msg = RedisParsableMessage.parse(message); + DataRequest.StreamType type = DataRequest.StreamType.valueOf(msg.get("stream", "NONE")); + String key = msg.get("key", "NONE"); + String id = msg.get("id", "NONE"); + String sender = msg.get("sender", "NONE"); + JSONObject data = msg.getJson().getJSONObject("data"); + + switch (type) { + case REQUEST -> { + DataRequestResponder responder = DataRequestResponder.get(key); + if (responder == null) return; + + JSONObject response = responder.respond(data); + JSONObject responseJson = new JSONObject(); + responseJson.put("id", id); + responseJson.put("sender", "internal"); + responseJson.put("stream", DataRequest.StreamType.RESPONSE.name()); + responseJson.put("key", key); + responseJson.put("data", response); + + RedisAPI.getInstance().publishMessage(sender, ChannelRegistry.getFromName("internal-data-request"), + RedisParsableMessage.from(responseJson).formatForSend()); + } + case RESPONSE -> DataRequest.RECEIVED_DATA.put(id, data); + } + } +} diff --git a/src/main/java/net/swofty/redisapi/util/RedisParsableMessage.java b/src/main/java/net/swofty/redisapi/util/RedisParsableMessage.java new file mode 100644 index 0000000..c698f29 --- /dev/null +++ b/src/main/java/net/swofty/redisapi/util/RedisParsableMessage.java @@ -0,0 +1,104 @@ +package net.swofty.redisapi.util; + +import lombok.Getter; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * This is a utility class, used for sending JSONObjects over Redis instead of working with raw Strings. + */ +@Getter +public class RedisParsableMessage { + private final JSONObject json; + + protected RedisParsableMessage(JSONObject json) { + this.json = json; + } + + /** + * Builds a new RedisParsableMessage from a JSONObject. + * @param fields The fields to build the JSONObject from. + * @return The built RedisParsableMessage. + */ + public static RedisParsableMessage from( Map fields) { + return from(new JSONObject(fields)); + } + + /** + * Builds a new RedisParsableMessage from a JSONObject. + * @param obj The JSONObject to build the RedisParsableMessage from. + * @return The built RedisParsableMessage. + */ + public static RedisParsableMessage from(JSONObject obj) { + return new RedisParsableMessage(obj); + } + + /** + * Parse a RedisParsableMessage from a raw String. + * @param raw The raw String to parse. + * @return The parsed RedisParsableMessage. + * @throws IllegalArgumentException if the raw String is not a valid JSONObject. + */ + public static RedisParsableMessage parse(String raw) { + String toParse = raw; + if (raw.contains(";")) { + String[] split = raw.split(";"); + toParse = split[1]; + } + return new RedisParsableMessage(new JSONObject(toParse)); + } + + /** + * Formats the JSONObject into a String to send over Redis, this is the same as {@link #json#toString()}. + * @return The formatted String. + */ + public String formatForSend() { + return json.toString(); + } + + @Override + public String toString() { + return formatForSend(); + } + + /** + * Get an object from the JSONObject. + * @param key The key to get the object from. + * @param defaultValue The default value to return if the key is not found. + * @return The object. + * @param The type of the object. + */ + public T get(String key, T defaultValue) { + return json.has(key) ? (T) json.get(key) : defaultValue; + } + + /* + * Beyond here are some utility methods for getting data from the JSONObject. + */ + + /** + * Get a UUID from the JSONObject. + * @param key The key to get the UUID from. + * @return The UUID. + */ + public UUID getUUID(String key) { + return UUID.fromString(get(key, "")); + } + + public JSONArray getJsonArray(String key) { + return json.has(key) ? json.getJSONArray(key) : new JSONArray(); + } + + public List getStringList(String key) { + return json.has(key) ? json.getJSONArray(key).toList().stream().map(String::valueOf).toList() : new ArrayList<>(); + } + + public boolean getBoolean(String key) { + return json.has(key) && json.getBoolean(key); + } +} From 14992b74f5eda04c758db14e22b5f5219307c75c Mon Sep 17 00:00:00 2001 From: Maploop Date: Mon, 1 Dec 2025 11:00:35 +0000 Subject: [PATCH 2/2] Update maven.yml --- .github/workflows/maven.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 825103e..9ed59b5 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -16,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '21' distribution: 'temurin' cache: maven - name: Build with Maven