From 023082e7498e3c37df2db5f3143e12f9ffc75007 Mon Sep 17 00:00:00 2001 From: William Date: Fri, 7 Jan 2022 00:27:26 +0000 Subject: [PATCH] Overhaul API, add JitPack integration for developer API provision --- .jitpack/ensure-java-16 | 19 ++++++ api/build.gradle | 40 ++++++++++++- .../husksync/bukkit/api/HuskSyncAPI.java | 60 +++++++++++++++++++ .../api/events/SyncCompleteEvent.java | 5 +- .../{ => bukkit}/api/events/SyncEvent.java | 15 ++--- .../husksync/bukkit/data/DataSerializer.java | 48 +++++++++++++++ bukkit/build.gradle | 1 + .../william278/husksync/HuskSyncBukkit.java | 1 + .../bukkit/listener/BukkitRedisListener.java | 24 +++++++- .../husksync/bukkit/util/PlayerSetter.java | 6 +- .../listener/BungeeRedisListener.java | 24 ++++++++ .../husksync/redis/RedisMessage.java | 15 +++++ jitpack.yml | 12 ++++ settings.gradle | 2 +- .../listener/VelocityRedisListener.java | 24 ++++++++ 15 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 .jitpack/ensure-java-16 create mode 100644 api/src/main/java/me/william278/husksync/bukkit/api/HuskSyncAPI.java rename api/src/main/java/me/william278/husksync/{ => bukkit}/api/events/SyncCompleteEvent.java (88%) rename api/src/main/java/me/william278/husksync/{ => bukkit}/api/events/SyncEvent.java (82%) rename {bukkit => api}/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java (87%) create mode 100644 jitpack.yml diff --git a/.jitpack/ensure-java-16 b/.jitpack/ensure-java-16 new file mode 100644 index 00000000..9bb37ccc --- /dev/null +++ b/.jitpack/ensure-java-16 @@ -0,0 +1,19 @@ +#!/bin/bash + +JV=$(java -version 2>&1 >/dev/null | head -1) +echo "$JV" | sed -E 's/^.*version "([^".]*)\.[^"]*".*$/\1/' + +if [ "$JV" != 16 ]; then + case "$1" in + install) + echo "installing sdkman..." + curl -s "https://get.sdkman.io" | bash + source ~/.sdkman/bin/sdkman-init.sh + sdk install java 16.0.1-open + ;; + use) + echo "must source ~/.sdkman/bin/sdkman-init.sh" + exit 1 + ;; + esac +fi \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index 24a39ccb..28055395 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -2,5 +2,43 @@ dependencies { implementation project(':common') compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' - compileOnly 'org.jetbrains:annotations:20.1.0' + compileOnly 'org.jetbrains:annotations:22.0.0' +} + +publishing { + publications { + mavenJava(MavenPublication) { + shadow.component(it) + afterEvaluate { + artifact javadocsJar + } + } + } + repositories { + mavenLocal() + } +} + +shadowJar { + classifier = null + relocate ':common', 'me.william278.husksync' +} + +repositories { + mavenCentral() + maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } +} + +task javadocs(type: Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + source = project(':common').sourceSets.main.allJava + source += project(':api').sourceSets.main.allJava + classpath = files(project(':common').sourceSets.main.compileClasspath) + classpath += files(project(':api').sourceSets.main.compileClasspath) + destinationDir = file("${buildDir}/docs/javadoc") +} + +task javadocsJar(type: Jar, dependsOn: javadocs) { + classifier = 'javadoc' + from javadocs.destinationDir } \ No newline at end of file diff --git a/api/src/main/java/me/william278/husksync/bukkit/api/HuskSyncAPI.java b/api/src/main/java/me/william278/husksync/bukkit/api/HuskSyncAPI.java new file mode 100644 index 00000000..3272e7df --- /dev/null +++ b/api/src/main/java/me/william278/husksync/bukkit/api/HuskSyncAPI.java @@ -0,0 +1,60 @@ +package me.william278.husksync.bukkit.api; + +import me.william278.husksync.PlayerData; +import me.william278.husksync.Settings; +import me.william278.husksync.redis.RedisMessage; + +import java.io.IOException; +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * API method class for HuskSync. To access methods, use the {@link #getInstance()} entrypoint. + */ +public class HuskSyncAPI { + + private HuskSyncAPI() { + } + + private static HuskSyncAPI instance; + + /** + * API entry point. Returns an instance of the {@link HuskSyncAPI} + * + * @return instance of the {@link HuskSyncAPI} + */ + public static HuskSyncAPI getInstance() { + if (instance == null) { + instance = new HuskSyncAPI(); + } + return instance; + } + + /** + * (INTERNAL) Map of API requests that are processed by the bukkit plugin that implements the API. + */ + public static HashMap> apiRequests = new HashMap<>(); + + /** + * Returns a {@link CompletableFuture} that will fetch the {@link PlayerData} for a user given their {@link UUID}, which contains synchronised data that can then be deserialized into ItemStacks and other usable values using the {@link me.william278.husksync.bukkit.data.DataSerializer} class. If no data could be returned, such as if an invalid UUID is specified, the CompletableFuture will be cancelled. Note that this only returns the last cached data of the user; not necessarily the current state of their inventory if they are online. + * + * @param playerUUID The {@link UUID} of the player to get data for + * @return a {@link CompletableFuture} with the user's {@link PlayerData} accessible on completion + * @throws IOException If an exception occurs with serializing during processing of the request + */ + public CompletableFuture getPlayerData(UUID playerUUID) throws IOException { + final UUID requestUUID = UUID.randomUUID(); + CompletableFuture playerDataCompletableFuture = new CompletableFuture<>(); + playerDataCompletableFuture.whenComplete((playerData, throwable) -> apiRequests.remove(requestUUID)); + + // Request the data via the proxy + new RedisMessage(RedisMessage.MessageType.API_DATA_REQUEST, + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), + playerUUID.toString(), requestUUID.toString()).send(); + + apiRequests.put(requestUUID, playerDataCompletableFuture); + return playerDataCompletableFuture; + } + +} \ No newline at end of file diff --git a/api/src/main/java/me/william278/husksync/api/events/SyncCompleteEvent.java b/api/src/main/java/me/william278/husksync/bukkit/api/events/SyncCompleteEvent.java similarity index 88% rename from api/src/main/java/me/william278/husksync/api/events/SyncCompleteEvent.java rename to api/src/main/java/me/william278/husksync/bukkit/api/events/SyncCompleteEvent.java index ab1d6cd9..236ba2ea 100644 --- a/api/src/main/java/me/william278/husksync/api/events/SyncCompleteEvent.java +++ b/api/src/main/java/me/william278/husksync/bukkit/api/events/SyncCompleteEvent.java @@ -1,4 +1,4 @@ -package me.william278.husksync.api.events; +package me.william278.husksync.bukkit.api.events; import me.william278.husksync.PlayerData; import org.bukkit.entity.Player; @@ -7,8 +7,7 @@ import org.bukkit.event.player.PlayerEvent; import org.jetbrains.annotations.NotNull; /** - * Represents an event that will be fired when a {@link Player} has finished - * being synchronised with the correct {@link PlayerData}. + * Represents an event that will be fired when a {@link Player} has finished being synchronised with the correct {@link PlayerData}. */ public class SyncCompleteEvent extends PlayerEvent { diff --git a/api/src/main/java/me/william278/husksync/api/events/SyncEvent.java b/api/src/main/java/me/william278/husksync/bukkit/api/events/SyncEvent.java similarity index 82% rename from api/src/main/java/me/william278/husksync/api/events/SyncEvent.java rename to api/src/main/java/me/william278/husksync/bukkit/api/events/SyncEvent.java index f5c4a395..e53294f3 100644 --- a/api/src/main/java/me/william278/husksync/api/events/SyncEvent.java +++ b/api/src/main/java/me/william278/husksync/bukkit/api/events/SyncEvent.java @@ -1,17 +1,14 @@ -package me.william278.husksync.api.events; +package me.william278.husksync.bukkit.api.events; import me.william278.husksync.PlayerData; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; /** - * Represents an event that will be fired before a {@link Player} is about - * to be synchronised with their {@link PlayerData}. + * Represents an event that will be fired before a {@link Player} is about to be synchronised with their {@link PlayerData}. */ public class SyncEvent extends PlayerEvent implements Cancellable { @@ -26,6 +23,7 @@ public class SyncEvent extends PlayerEvent implements Cancellable { /** * Returns the {@link PlayerData} which has just been set on the {@link Player} + * * @return The {@link PlayerData} that has been set */ public PlayerData getData() { @@ -34,6 +32,7 @@ public class SyncEvent extends PlayerEvent implements Cancellable { /** * Sets the {@link PlayerData} to be synchronised to this player + * * @param data The {@link PlayerData} to set to the player */ public void setData(PlayerData data) { @@ -50,8 +49,7 @@ public class SyncEvent extends PlayerEvent implements Cancellable { } /** - * Gets the cancellation state of this event. A cancelled event will not - * be executed in the server, but will still pass to other plugins + * Gets the cancellation state of this event. A cancelled event will not be executed in the server, but will still pass to other plugins * * @return true if this event is cancelled */ @@ -61,8 +59,7 @@ public class SyncEvent extends PlayerEvent implements Cancellable { } /** - * Sets the cancellation state of this event. A cancelled event will not - * be executed in the server, but will still pass to other plugins. + * Sets the cancellation state of this event. A cancelled event will not be executed in the server, but will still pass to other plugins. * * @param cancel true if you wish to cancel this event */ diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java b/api/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java similarity index 87% rename from bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java rename to api/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java index 2235d252..b3880b48 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java +++ b/api/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java @@ -19,6 +19,9 @@ import java.io.Serializable; import java.time.Instant; import java.util.*; +/** + * Class that contains static methods for serializing and deserializing data from {@link me.william278.husksync.PlayerData} + */ public class DataSerializer { /** @@ -208,6 +211,13 @@ public class DataSerializer { playerLocation.getYaw(), playerLocation.getPitch(), player.getWorld().getName(), player.getWorld().getEnvironment())); } + /** + * Deserializes a player's advancement data as serialized with {@link #getSerializedAdvancements(Player)} into {@link AdvancementRecordDate} data. + * + * @param serializedAdvancementData The serialized advancement data {@link String} + * @return The deserialized {@link AdvancementRecordDate} for the player + * @throws IOException If the deserialization fails + */ @SuppressWarnings("unchecked") // Ignore the unchecked cast here public static List deserializeAdvancementData(String serializedAdvancementData) throws IOException { if (serializedAdvancementData.isEmpty()) { @@ -216,6 +226,7 @@ public class DataSerializer { try { List deserialize = (List) RedisMessage.deserialize(serializedAdvancementData); + // Migrate old AdvancementRecord into date format if (!deserialize.isEmpty() && deserialize.get(0) instanceof AdvancementRecord) { deserialize = ((List) deserialize).stream() .map(o -> new AdvancementRecordDate( @@ -230,6 +241,13 @@ public class DataSerializer { } } + /** + * Returns a serialized {@link String} of a player's advancements that can be deserialized with {@link #deserializeStatisticData(String)} + * + * @param player {@link Player} to serialize advancement data of + * @return The serialized advancement data as a {@link String} + * @throws IOException If the serialization fails + */ public static String getSerializedAdvancements(Player player) throws IOException { Iterator serverAdvancements = Bukkit.getServer().advancementIterator(); ArrayList advancementData = new ArrayList<>(); @@ -247,6 +265,13 @@ public class DataSerializer { return RedisMessage.serialize(advancementData); } + /** + * Deserializes a player's statistic data as serialized with {@link #getSerializedStatisticData(Player)} into {@link StatisticData}. + * + * @param serializedStatisticData The serialized statistic data {@link String} + * @return The deserialized {@link StatisticData} for the player + * @throws IOException If the deserialization fails + */ public static DataSerializer.StatisticData deserializeStatisticData(String serializedStatisticData) throws IOException { if (serializedStatisticData.isEmpty()) { return new DataSerializer.StatisticData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()); @@ -258,6 +283,13 @@ public class DataSerializer { } } + /** + * Returns a serialized {@link String} of a player's statistic data that can be deserialized with {@link #deserializeStatisticData(String)} + * + * @param player {@link Player} to serialize statistic data of + * @return The serialized statistic data as a {@link String} + * @throws IOException If the serialization fails + */ public static String getSerializedStatisticData(Player player) throws IOException { HashMap untypedStatisticValues = new HashMap<>(); HashMap> blockStatisticValues = new HashMap<>(); @@ -294,14 +326,27 @@ public class DataSerializer { return RedisMessage.serialize(statisticData); } + /** + * A record used to store data for a player's location + */ public record PlayerLocation(double x, double y, double z, float yaw, float pitch, String worldName, World.Environment environment) implements Serializable { } + /** + * A record used to store data for advancement synchronisation + * + * @deprecated Old format - Use {@link AdvancementRecordDate} instead + */ + @Deprecated + @SuppressWarnings("DeprecatedIsStillUsed") // Suppress deprecation warnings here (still used for backwards compatibility) public record AdvancementRecord(String advancementKey, ArrayList awardedAdvancementCriteria) implements Serializable { } + /** + * A record used to store data for native advancement synchronisation, tracking advancement date progress + */ public record AdvancementRecordDate(String key, Map criteriaMap) implements Serializable { AdvancementRecordDate(String key, List criteriaList) { this(key, new HashMap<>() {{ @@ -310,6 +355,9 @@ public class DataSerializer { } } + /** + * A record used to store data for a player's statistics + */ public record StatisticData(HashMap untypedStatisticValues, HashMap> blockStatisticValues, HashMap> itemStatisticValues, diff --git a/bukkit/build.gradle b/bukkit/build.gradle index baa25313..4bf908b2 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -9,6 +9,7 @@ dependencies { compileOnly 'net.craftersland.data:bridge:4.0.1' compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' + compileOnly 'org.jetbrains:annotations:22.0.0' } shadowJar { diff --git a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java index a654e082..d987b4d2 100644 --- a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java +++ b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java @@ -17,6 +17,7 @@ import org.bukkit.scheduler.BukkitTask; import java.io.IOException; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.logging.Level; public final class HuskSyncBukkit extends JavaPlugin { diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java index eac248af..2726c8b3 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java @@ -2,16 +2,17 @@ package me.william278.husksync.bukkit.listener; import de.themoep.minedown.MineDown; import me.william278.husksync.HuskSyncBukkit; -import me.william278.husksync.util.MessageManager; import me.william278.husksync.PlayerData; import me.william278.husksync.Settings; +import me.william278.husksync.bukkit.api.HuskSyncAPI; import me.william278.husksync.bukkit.config.ConfigLoader; import me.william278.husksync.bukkit.data.DataViewer; -import me.william278.husksync.bukkit.util.PlayerSetter; import me.william278.husksync.bukkit.migrator.MPDBDeserializer; +import me.william278.husksync.bukkit.util.PlayerSetter; import me.william278.husksync.migrator.MPDBPlayerData; import me.william278.husksync.redis.RedisListener; import me.william278.husksync.redis.RedisMessage; +import me.william278.husksync.util.MessageManager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -107,6 +108,25 @@ public class BukkitRedisListener extends RedisListener { } }); } + case API_DATA_RETURN -> { + final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]); + if (HuskSyncAPI.apiRequests.containsKey(requestUUID)) { + try { + final PlayerData data = (PlayerData) RedisMessage.deserialize(message.getMessageDataElements()[1]); + HuskSyncAPI.apiRequests.get(requestUUID).complete(data); + } catch (IOException | ClassNotFoundException e) { + log(Level.SEVERE, "Failed to serialize returned API-requested player data"); + } + } + + } + case API_DATA_CANCEL -> { + final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]); + // Cancel requests if no data could be found on the proxy + if (HuskSyncAPI.apiRequests.containsKey(requestUUID)) { + HuskSyncAPI.apiRequests.get(requestUUID).cancel(true); + } + } case RELOAD_CONFIG -> { plugin.reloadConfig(); ConfigLoader.loadSettings(plugin.getConfig()); diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java index 704bad2c..9be2ffba 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java @@ -3,8 +3,8 @@ package me.william278.husksync.bukkit.util; import me.william278.husksync.HuskSyncBukkit; import me.william278.husksync.PlayerData; import me.william278.husksync.Settings; -import me.william278.husksync.api.events.SyncCompleteEvent; -import me.william278.husksync.api.events.SyncEvent; +import me.william278.husksync.bukkit.api.events.SyncCompleteEvent; +import me.william278.husksync.bukkit.api.events.SyncEvent; import me.william278.husksync.bukkit.data.DataSerializer; import me.william278.husksync.bukkit.util.nms.AdvancementUtils; import me.william278.husksync.redis.RedisMessage; @@ -315,7 +315,7 @@ public class PlayerSetter { * Update a player's advancements and progress to match the advancementData * * @param player The player to set the advancements of - * @param advancementData The ArrayList of {@link DataSerializer.AdvancementRecord}s to set + * @param advancementData The ArrayList of {@link DataSerializer.AdvancementRecordDate}s to set */ private static void setPlayerAdvancements(Player player, List advancementData, PlayerData data) { // Temporarily disable advancement announcing if needed diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java index 8a79c465..95cb7992 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java @@ -197,6 +197,30 @@ public class BungeeRedisListener extends RedisListener { HuskSyncBungeeCord.dataManager)); } } + case API_DATA_REQUEST -> { + final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]); + final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]); + + try { + final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId()); + + if (data == null) { + new RedisMessage(RedisMessage.MessageType.API_DATA_CANCEL, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + requestUUID.toString()) + .send(); + } else { + // Send the reply alongside the request UUID, serializing the requested message data + new RedisMessage(RedisMessage.MessageType.API_DATA_RETURN, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + requestUUID.toString(), + RedisMessage.serialize(data)) + .send(); + } + } catch (IOException e) { + plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API"); + } + } } } diff --git a/common/src/main/java/me/william278/husksync/redis/RedisMessage.java b/common/src/main/java/me/william278/husksync/redis/RedisMessage.java index 1200aae3..8f7d7df5 100644 --- a/common/src/main/java/me/william278/husksync/redis/RedisMessage.java +++ b/common/src/main/java/me/william278/husksync/redis/RedisMessage.java @@ -104,6 +104,21 @@ public class RedisMessage { */ PLAYER_DATA_SET, + /** + * Sent by Bukkit servers to proxy to request {@link PlayerData} from the proxy via the API + */ + API_DATA_REQUEST, + + /** + * Sent by the Proxy to fulfill an {@code MessageType.API_DATA_REQUEST}, containing the latest {@link PlayerData} for the requested UUID + */ + API_DATA_RETURN, + + /** + * Sent by the Proxy to cancel an {@code MessageType.API_DATA_REQUEST} if no data can be returned. + */ + API_DATA_CANCEL, + /** * Sent by the proxy to a Bukkit server to have them request data on join; contains no data otherwise */ diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..79a8c66b --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,12 @@ +# This file ensures jitpack builds HuskSync correctly by setting the JDK to 16 +jdk: + - 'openjdk16' +before_install: + - 'git clone https://github.com/WiIIiam278/HuskSync.git --recurse-submodules' + - 'chmod +x gradlew' + - 'chmod +x ./.jitpack/ensure-java-16' + - 'bash ./.jitpack/ensure-java-16 install' +install: + - 'if ! ./.jitpack/ensure-java-16 use; then source ~/.sdkman/bin/sdkman-init.sh; fi' + - 'java -version' + - './gradlew api:clean api:publishToMavenLocal' \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 26d34c30..80bab06e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,8 +7,8 @@ pluginManagement { rootProject.name = 'HuskSync' include 'common' -include 'api' include 'bukkit' include 'bungeecord' include 'velocity' +include 'api' include 'plugin' \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java index 1a1b9fa4..4fa139ff 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java @@ -190,6 +190,30 @@ public class VelocityRedisListener extends RedisListener { migrator.loadIncomingData(migrator.incomingPlayerData, HuskSyncVelocity.dataManager); } } + case API_DATA_REQUEST -> { + final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]); + final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]); + + try { + final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId()); + + if (data == null) { + new RedisMessage(RedisMessage.MessageType.API_DATA_CANCEL, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + requestUUID.toString()) + .send(); + } else { + // Send the reply alongside the request UUID, serializing the requested message data + new RedisMessage(RedisMessage.MessageType.API_DATA_RETURN, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + requestUUID.toString(), + RedisMessage.serialize(data)) + .send(); + } + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API"); + } + } } }