Skip to content

Commit 9341ecd

Browse files
Merge pull request #9 from Maploop/master
Upgrade to Java 21, add DataRequest system and RedisParsableMessage u…
2 parents 17177ba + 14992b7 commit 9341ecd

File tree

8 files changed

+269
-4
lines changed

8 files changed

+269
-4
lines changed

.github/workflows/maven.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616

1717
steps:
1818
- uses: actions/checkout@v3
19-
- name: Set up JDK 11
19+
- name: Set up JDK 21
2020
uses: actions/setup-java@v3
2121
with:
22-
java-version: '11'
22+
java-version: '21'
2323
distribution: 'temurin'
2424
cache: maven
2525
- name: Build with Maven

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
<version>1.1.3</version>
1010

1111
<properties>
12-
<maven.compiler.source>1.8</maven.compiler.source>
13-
<maven.compiler.target>1.8</maven.compiler.target>
12+
<maven.compiler.source>21</maven.compiler.source>
13+
<maven.compiler.target>21</maven.compiler.target>
1414
</properties>
1515

1616
<dependencies>

src/main/java/net/swofty/redisapi/api/RedisAPI.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import lombok.AccessLevel;
44
import lombok.NonNull;
55
import lombok.experimental.FieldDefaults;
6+
import net.swofty.redisapi.api.requests.DataStreamListener;
67
import net.swofty.redisapi.events.EventRegistry;
78
import net.swofty.redisapi.events.RedisMessagingReceiveEvent;
89
import net.swofty.redisapi.events.RedisMessagingReceiveInterface;
@@ -141,6 +142,13 @@ public static RedisAPI generateInstance(@NonNull String uri, String password) {
141142
* Starts listeners for the Redis Pub/Sub channels
142143
*/
143144
public void startListeners() {
145+
try {
146+
registerChannel("internal-data-request", DataStreamListener.class);
147+
} catch (ChannelAlreadyRegisteredException ignored) {
148+
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." +
149+
"\n Channel Name: internal-data-request");
150+
}
151+
144152
new Thread(() -> {
145153
try (Jedis jedis = getPool().getResource()) {
146154
EventRegistry.pubSub = new JedisPubSub() {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package net.swofty.redisapi.api.requests;
2+
3+
import net.swofty.redisapi.api.ChannelRegistry;
4+
import net.swofty.redisapi.api.RedisAPI;
5+
import net.swofty.redisapi.util.RedisParsableMessage;
6+
import org.json.JSONObject;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.UUID;
11+
import java.util.concurrent.CompletableFuture;
12+
13+
public class DataRequest {
14+
public static final Map<String, JSONObject> RECEIVED_DATA = new HashMap<>();
15+
16+
private final String id;
17+
private final String filter;
18+
private final String key;
19+
private final JSONObject data;
20+
21+
/**
22+
* Create a new data request to get a specific object of data from a specific filter ID.
23+
* @param filterID The filter ID where you want to receive data from, can be "all" for all listeners.
24+
* @param key The data identifier key.
25+
*/
26+
public DataRequest(String filterID, String key, JSONObject data) {
27+
this.id = UUID.randomUUID().toString();
28+
this.filter = filterID;
29+
this.key = key;
30+
this.data = data;
31+
}
32+
33+
public CompletableFuture<DataResponse> await() {
34+
return CompletableFuture.supplyAsync(() -> {
35+
long start = System.currentTimeMillis();
36+
JSONObject request = new JSONObject();
37+
request.put("id", id);
38+
request.put("key", key);
39+
request.put("data", data);
40+
request.put("sender", "proxy");
41+
request.put("stream", StreamType.REQUEST.name());
42+
43+
RedisAPI.getInstance().publishMessage(filter, ChannelRegistry.getFromName("internal-data-request"), RedisParsableMessage.from(request).formatForSend());
44+
45+
int timeout = 0;
46+
while (!RECEIVED_DATA.containsKey(id)) {
47+
try { Thread.sleep(1); timeout++; } catch (InterruptedException ignored) { }
48+
if (timeout >= 100) break;
49+
}
50+
51+
JSONObject response = RECEIVED_DATA.get(id);
52+
RECEIVED_DATA.remove(id);
53+
long latency = (System.currentTimeMillis() - start);
54+
return new DataResponse(response, latency);
55+
});
56+
}
57+
58+
public enum StreamType {
59+
REQUEST,
60+
RESPONSE
61+
}
62+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package net.swofty.redisapi.api.requests;
2+
3+
import org.json.JSONObject;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
import java.util.function.Function;
8+
9+
public class DataRequestResponder {
10+
public static final Map<String, DataRequestResponder> RESPONDERS = new HashMap<>();
11+
12+
private final Function<JSONObject, JSONObject> callback;
13+
14+
protected DataRequestResponder(Function<JSONObject, JSONObject> callback) {
15+
this.callback = callback;
16+
}
17+
18+
public JSONObject respond(JSONObject request) {
19+
return this.callback.apply(request);
20+
}
21+
22+
/**
23+
* Creates a new Data Request Responder. Must be registered before {@link net.swofty.redisapi.api.RedisAPI#startListeners()} in order to work properly.
24+
* @param key The key to respond to.
25+
* @param callback Callback, has a JSONObject parameter request, and returns a JSONObject response, request and response both can be empty.
26+
* @return The created DataRequestResponder, not entirely useful, but still there.
27+
*/
28+
public static DataRequestResponder create(String key, Function<JSONObject, JSONObject> callback) {
29+
DataRequestResponder responder = new DataRequestResponder(callback);
30+
RESPONDERS.put(key, responder);
31+
return responder;
32+
}
33+
34+
/**
35+
* Get a DataRequestResponder by key.
36+
* @param key The key to get the DataRequestResponder by.
37+
* @return The DataRequestResponder, or null if it doesn't exist.
38+
*/
39+
public static DataRequestResponder get(String key) {
40+
return RESPONDERS.get(key);
41+
}
42+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package net.swofty.redisapi.api.requests;
2+
3+
import org.json.JSONObject;
4+
5+
/**
6+
* The response to a DataRequest.
7+
* @param data The data object, will be null if the request has timed out.
8+
* @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.
9+
*/
10+
public record DataResponse(JSONObject data, long latency) {
11+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package net.swofty.redisapi.api.requests;
2+
3+
import net.swofty.redisapi.api.ChannelRegistry;
4+
import net.swofty.redisapi.api.RedisAPI;
5+
import net.swofty.redisapi.events.RedisMessagingReceiveInterface;
6+
import net.swofty.redisapi.util.RedisParsableMessage;
7+
import org.json.JSONObject;
8+
9+
public class DataStreamListener implements RedisMessagingReceiveInterface {
10+
@Override
11+
public void onMessage(String channel, String message) {
12+
RedisParsableMessage msg = RedisParsableMessage.parse(message);
13+
DataRequest.StreamType type = DataRequest.StreamType.valueOf(msg.get("stream", "NONE"));
14+
String key = msg.get("key", "NONE");
15+
String id = msg.get("id", "NONE");
16+
String sender = msg.get("sender", "NONE");
17+
JSONObject data = msg.getJson().getJSONObject("data");
18+
19+
switch (type) {
20+
case REQUEST -> {
21+
DataRequestResponder responder = DataRequestResponder.get(key);
22+
if (responder == null) return;
23+
24+
JSONObject response = responder.respond(data);
25+
JSONObject responseJson = new JSONObject();
26+
responseJson.put("id", id);
27+
responseJson.put("sender", "internal");
28+
responseJson.put("stream", DataRequest.StreamType.RESPONSE.name());
29+
responseJson.put("key", key);
30+
responseJson.put("data", response);
31+
32+
RedisAPI.getInstance().publishMessage(sender, ChannelRegistry.getFromName("internal-data-request"),
33+
RedisParsableMessage.from(responseJson).formatForSend());
34+
}
35+
case RESPONSE -> DataRequest.RECEIVED_DATA.put(id, data);
36+
}
37+
}
38+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package net.swofty.redisapi.util;
2+
3+
import lombok.Getter;
4+
import org.json.JSONArray;
5+
import org.json.JSONObject;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.UUID;
11+
12+
/**
13+
* This is a utility class, used for sending JSONObjects over Redis instead of working with raw Strings.
14+
*/
15+
@Getter
16+
public class RedisParsableMessage {
17+
private final JSONObject json;
18+
19+
protected RedisParsableMessage(JSONObject json) {
20+
this.json = json;
21+
}
22+
23+
/**
24+
* Builds a new RedisParsableMessage from a JSONObject.
25+
* @param fields The fields to build the JSONObject from.
26+
* @return The built RedisParsableMessage.
27+
*/
28+
public static RedisParsableMessage from( Map<String, Object> fields) {
29+
return from(new JSONObject(fields));
30+
}
31+
32+
/**
33+
* Builds a new RedisParsableMessage from a JSONObject.
34+
* @param obj The JSONObject to build the RedisParsableMessage from.
35+
* @return The built RedisParsableMessage.
36+
*/
37+
public static RedisParsableMessage from(JSONObject obj) {
38+
return new RedisParsableMessage(obj);
39+
}
40+
41+
/**
42+
* Parse a RedisParsableMessage from a raw String.
43+
* @param raw The raw String to parse.
44+
* @return The parsed RedisParsableMessage.
45+
* @throws IllegalArgumentException if the raw String is not a valid JSONObject.
46+
*/
47+
public static RedisParsableMessage parse(String raw) {
48+
String toParse = raw;
49+
if (raw.contains(";")) {
50+
String[] split = raw.split(";");
51+
toParse = split[1];
52+
}
53+
return new RedisParsableMessage(new JSONObject(toParse));
54+
}
55+
56+
/**
57+
* Formats the JSONObject into a String to send over Redis, this is the same as {@link #json#toString()}.
58+
* @return The formatted String.
59+
*/
60+
public String formatForSend() {
61+
return json.toString();
62+
}
63+
64+
@Override
65+
public String toString() {
66+
return formatForSend();
67+
}
68+
69+
/**
70+
* Get an object from the JSONObject.
71+
* @param key The key to get the object from.
72+
* @param defaultValue The default value to return if the key is not found.
73+
* @return The object.
74+
* @param <T> The type of the object.
75+
*/
76+
public <T> T get(String key, T defaultValue) {
77+
return json.has(key) ? (T) json.get(key) : defaultValue;
78+
}
79+
80+
/*
81+
* Beyond here are some utility methods for getting data from the JSONObject.
82+
*/
83+
84+
/**
85+
* Get a UUID from the JSONObject.
86+
* @param key The key to get the UUID from.
87+
* @return The UUID.
88+
*/
89+
public UUID getUUID(String key) {
90+
return UUID.fromString(get(key, ""));
91+
}
92+
93+
public JSONArray getJsonArray(String key) {
94+
return json.has(key) ? json.getJSONArray(key) : new JSONArray();
95+
}
96+
97+
public List<String> getStringList(String key) {
98+
return json.has(key) ? json.getJSONArray(key).toList().stream().map(String::valueOf).toList() : new ArrayList<>();
99+
}
100+
101+
public boolean getBoolean(String key) {
102+
return json.has(key) && json.getBoolean(key);
103+
}
104+
}

0 commit comments

Comments
 (0)