From 6f96c0a618144c95f01f8e814b23e40e4b981649 Mon Sep 17 00:00:00 2001 From: William Date: Tue, 5 Nov 2024 23:22:48 +0000 Subject: [PATCH] fix: make `World` Gson-safe --- .../william278/huskhomes/BukkitHuskHomes.java | 6 +- .../william278/huskhomes/user/BukkitUser.java | 48 +++++++------- common/build.gradle | 1 + .../net/william278/huskhomes/HuskHomes.java | 10 +-- .../william278/huskhomes/network/Payload.java | 3 +- .../network/PluginMessageBroker.java | 17 ++--- .../huskhomes/network/RedisBroker.java | 2 +- .../william278/huskhomes/position/World.java | 65 +++++-------------- .../huskhomes/util/GsonProvider.java | 52 +++++++++++++++ .../network/MessageSerializationTests.java | 5 +- 10 files changed, 115 insertions(+), 94 deletions(-) create mode 100644 common/src/main/java/net/william278/huskhomes/util/GsonProvider.java diff --git a/bukkit/src/main/java/net/william278/huskhomes/BukkitHuskHomes.java b/bukkit/src/main/java/net/william278/huskhomes/BukkitHuskHomes.java index 80fdc313..7ba091dd 100644 --- a/bukkit/src/main/java/net/william278/huskhomes/BukkitHuskHomes.java +++ b/bukkit/src/main/java/net/william278/huskhomes/BukkitHuskHomes.java @@ -364,7 +364,11 @@ public static org.bukkit.World adapt(@NotNull World world) { @NotNull public static World adapt(@NotNull org.bukkit.World world) { - return World.from(world.getName(), world.getUID(), World.Environment.match(world.getEnvironment().name())); + return World.from( + world.getName(), + world.getUID(), + World.Environment.match(world.getEnvironment().name()) + ); } } diff --git a/bukkit/src/main/java/net/william278/huskhomes/user/BukkitUser.java b/bukkit/src/main/java/net/william278/huskhomes/user/BukkitUser.java index d8709118..5de78ffd 100644 --- a/bukkit/src/main/java/net/william278/huskhomes/user/BukkitUser.java +++ b/bukkit/src/main/java/net/william278/huskhomes/user/BukkitUser.java @@ -45,11 +45,11 @@ public class BukkitUser extends OnlineUser { private final NamespacedKey INVULNERABLE_KEY = new NamespacedKey((BukkitHuskHomes) plugin, "invulnerable"); - private final Player player; + private final Player bukkitPlayer; - private BukkitUser(@NotNull Player player, @NotNull BukkitHuskHomes plugin) { - super(player.getUniqueId(), player.getName(), plugin); - this.player = player; + private BukkitUser(@NotNull Player bukkitPlayer, @NotNull BukkitHuskHomes plugin) { + super(bukkitPlayer.getUniqueId(), bukkitPlayer.getName(), plugin); + this.bukkitPlayer = bukkitPlayer; } @NotNull @@ -60,35 +60,35 @@ public static BukkitUser adapt(@NotNull Player player, @NotNull BukkitHuskHomes @NotNull public Player getPlayer() { - return player; + return bukkitPlayer; } @Override public Position getPosition() { - return BukkitHuskHomes.Adapter.adapt(player.getLocation(), plugin.getServerName()); + return BukkitHuskHomes.Adapter.adapt(bukkitPlayer.getLocation(), plugin.getServerName()); } @Override public Optional getBedSpawnPosition() { - return Optional.ofNullable(player.getBedSpawnLocation()) + return Optional.ofNullable(bukkitPlayer.getBedSpawnLocation()) .map(loc -> BukkitHuskHomes.Adapter.adapt(loc, plugin.getServerName())); } @Override public double getHealth() { - return player.getHealth(); + return bukkitPlayer.getHealth(); } @Override public boolean hasPermission(@NotNull String node) { - return player.hasPermission(node); + return bukkitPlayer.hasPermission(node); } @Override @NotNull public Map getPermissions() { - return player.getEffectivePermissions().stream() + return bukkitPlayer.getEffectivePermissions().stream() .collect(Collectors.toMap( PermissionAttachmentInfo::getPermission, PermissionAttachmentInfo::getValue, (a, b) -> b @@ -99,8 +99,8 @@ public Map getPermissions() { public CompletableFuture dismount() { final CompletableFuture future = new CompletableFuture<>(); plugin.runSync(() -> { - player.leaveVehicle(); - player.eject(); + bukkitPlayer.leaveVehicle(); + bukkitPlayer.eject(); future.complete(null); }, this); return future; @@ -119,29 +119,29 @@ public void teleportLocally(@NotNull Location target, boolean async) throws Tele // Run on the appropriate thread scheduler for this platform plugin.runSync(() -> { - player.leaveVehicle(); - player.eject(); + bukkitPlayer.leaveVehicle(); + bukkitPlayer.eject(); if (async || ((BukkitHuskHomes) plugin).getScheduler().isUsingFolia()) { - PaperLib.teleportAsync(player, location, PlayerTeleportEvent.TeleportCause.PLUGIN); + PaperLib.teleportAsync(bukkitPlayer, location, PlayerTeleportEvent.TeleportCause.PLUGIN); return; } - player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN); + bukkitPlayer.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN); }, this); } @Override public boolean isMoving() { - return player.getVelocity().length() >= 0.1; + return bukkitPlayer.getVelocity().length() >= 0.1; } @Override public void sendPluginMessage(byte[] message) { - player.sendPluginMessage((BukkitHuskHomes) plugin, PluginMessageBroker.BUNGEE_CHANNEL_ID, message); + bukkitPlayer.sendPluginMessage((BukkitHuskHomes) plugin, PluginMessageBroker.BUNGEE_CHANNEL_ID, message); } @Override public boolean isVanished() { - return player.getMetadata("vanished") + return bukkitPlayer.getMetadata("vanished") .stream() .map(MetadataValue::asBoolean) .findFirst() @@ -150,7 +150,7 @@ public boolean isVanished() { @Override public boolean hasInvulnerability() { - return player.getPersistentDataContainer().has(INVULNERABLE_KEY, PersistentDataType.INTEGER); + return bukkitPlayer.getPersistentDataContainer().has(INVULNERABLE_KEY, PersistentDataType.INTEGER); } @Override @@ -159,17 +159,17 @@ public void handleInvulnerability() { if (invulnerableTicks <= 0) { return; } - player.getPersistentDataContainer().set(INVULNERABLE_KEY, PersistentDataType.INTEGER, 1); - player.setInvulnerable(true); + bukkitPlayer.getPersistentDataContainer().set(INVULNERABLE_KEY, PersistentDataType.INTEGER, 1); + bukkitPlayer.setInvulnerable(true); plugin.runSyncDelayed(this::removeInvulnerabilityIfPermitted, this, invulnerableTicks); } @Override public void removeInvulnerabilityIfPermitted() { if (this.hasInvulnerability()) { - player.setInvulnerable(false); + bukkitPlayer.setInvulnerable(false); } - player.getPersistentDataContainer().remove(INVULNERABLE_KEY); + bukkitPlayer.getPersistentDataContainer().remove(INVULNERABLE_KEY); } } diff --git a/common/build.gradle b/common/build.gradle index 8de39630..b57ffb97 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -9,6 +9,7 @@ dependencies { api 'commons-io:commons-io:2.17.0' api 'org.apache.commons:commons-text:1.12.0' api 'com.google.code.gson:gson:2.11.0' + api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2' api 'com.github.Exlll.ConfigLib:configlib-yaml:v4.5.0' api('com.zaxxer:HikariCP:6.0.0') { exclude module: 'slf4j-api' diff --git a/common/src/main/java/net/william278/huskhomes/HuskHomes.java b/common/src/main/java/net/william278/huskhomes/HuskHomes.java index 1069a1be..44f331fd 100644 --- a/common/src/main/java/net/william278/huskhomes/HuskHomes.java +++ b/common/src/main/java/net/william278/huskhomes/HuskHomes.java @@ -19,8 +19,6 @@ package net.william278.huskhomes; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import net.kyori.adventure.key.Key; import net.william278.huskhomes.api.BaseHuskHomesAPI; import net.william278.huskhomes.command.Command; @@ -57,7 +55,8 @@ */ public interface HuskHomes extends Task.Supplier, EventDispatcher, SavePositionProvider, TransactionResolver, ConfigProvider, DatabaseProvider, BrokerProvider, MetaProvider, HookProvider, RandomTeleportProvider, - AudiencesProvider, UserProvider, TextValidator, ManagerProvider, ListenerProvider, CommandProvider { + AudiencesProvider, UserProvider, TextValidator, ManagerProvider, ListenerProvider, CommandProvider, + GsonProvider { int BSTATS_BUKKIT_PLUGIN_ID = 8430; @@ -252,9 +251,4 @@ default Key getKey(@NotNull String... data) { return Key.key("huskhomes", joined); } - @NotNull - default Gson getGson() { - return new GsonBuilder().create(); - } - } diff --git a/common/src/main/java/net/william278/huskhomes/network/Payload.java b/common/src/main/java/net/william278/huskhomes/network/Payload.java index 5d9b22c8..4b24a265 100644 --- a/common/src/main/java/net/william278/huskhomes/network/Payload.java +++ b/common/src/main/java/net/william278/huskhomes/network/Payload.java @@ -21,6 +21,7 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; +import lombok.AccessLevel; import lombok.NoArgsConstructor; import net.william278.huskhomes.position.Position; import net.william278.huskhomes.position.World; @@ -35,7 +36,7 @@ /** * Represents a payload sent in a cross-server {@link Message}. */ -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class Payload { @Nullable diff --git a/common/src/main/java/net/william278/huskhomes/network/PluginMessageBroker.java b/common/src/main/java/net/william278/huskhomes/network/PluginMessageBroker.java index 75bee9dc..9b657926 100644 --- a/common/src/main/java/net/william278/huskhomes/network/PluginMessageBroker.java +++ b/common/src/main/java/net/william278/huskhomes/network/PluginMessageBroker.java @@ -40,17 +40,20 @@ public class PluginMessageBroker extends Broker { /** * The name of BungeeCord's provided plugin channel. * - *

Internally, this is {@code bungeecord:main}, - * but Spigot remaps {@code BungeeCord} automatically to the new one (hence BungeeCord is kept for back-compat). + * @implNote Technically, the effective identifier of this channel is {@code bungeecord:main}, but Spigot remaps + * {@code BungeeCord} automatically to the new one (source). + * Spigot's official documentation + * still instructs usage of {@code BungeeCord} as the name to use, however. It's all a bit inconsistent, so just in case + * it's best to leave it how it is for to maintain backwards compatibility. */ public static final String BUNGEE_CHANNEL_ID = "BungeeCord"; - public PluginMessageBroker(@NotNull HuskHomes plugin) { + protected PluginMessageBroker(@NotNull HuskHomes plugin) { super(plugin); } @Override - public void initialize() throws IllegalStateException { + public void initialize() throws RuntimeException { plugin.setupPluginMessagingChannels(); } @@ -70,16 +73,14 @@ public final void onReceive(@NotNull String channel, @NotNull OnlineUser user, b inputStream.readFully(messageBody); try (final DataInputStream messageReader = new DataInputStream(new ByteArrayInputStream(messageBody))) { - super.handle(user, plugin.getGson().fromJson(messageReader.readUTF(), Message.class)); + super.handle(user, plugin.getMessageFromJson(messageReader.readUTF())); } catch (IOException e) { plugin.log(Level.SEVERE, "Failed to fully read plugin message", e); } } @Override - protected void send(@NotNull Message message, @Nullable OnlineUser sender) { - Preconditions.checkNotNull(sender, "Sender cannot be null with a Plugin Message broker"); - + protected void send(@NotNull Message message, @NotNull OnlineUser sender) { final ByteArrayDataOutput messageWriter = ByteStreams.newDataOutput(); messageWriter.writeUTF(message.getTargetType().getPluginMessageChannel()); messageWriter.writeUTF(message.getTarget()); diff --git a/common/src/main/java/net/william278/huskhomes/network/RedisBroker.java b/common/src/main/java/net/william278/huskhomes/network/RedisBroker.java index dc652ae7..311be536 100644 --- a/common/src/main/java/net/william278/huskhomes/network/RedisBroker.java +++ b/common/src/main/java/net/william278/huskhomes/network/RedisBroker.java @@ -196,7 +196,7 @@ private void onThreadUnlock(@NotNull Throwable t) { public void onMessage(@NotNull String channel, @NotNull String encoded) { final Message message; try { - message = broker.plugin.getGson().fromJson(encoded, Message.class); + message = broker.plugin.getMessageFromJson(encoded); } catch (Exception e) { broker.plugin.log(Level.WARNING, "Failed to decode message from Redis: " + e.getMessage()); return; diff --git a/common/src/main/java/net/william278/huskhomes/position/World.java b/common/src/main/java/net/william278/huskhomes/position/World.java index 23502433..3d9ddd12 100644 --- a/common/src/main/java/net/william278/huskhomes/position/World.java +++ b/common/src/main/java/net/william278/huskhomes/position/World.java @@ -19,78 +19,41 @@ package net.william278.huskhomes.position; -import lombok.NoArgsConstructor; +import com.google.gson.annotations.Expose; +import lombok.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Locale; -import java.util.Optional; import java.util.UUID; /** * Represents a world on a server. */ +@Getter +@Setter +@AllArgsConstructor(staticName = "from") @NoArgsConstructor public class World { + @Expose private String name; + @Expose private UUID uuid; + @Expose @Nullable - private Environment environment; - - private World(@NotNull String name, @NotNull UUID uuid, @Nullable Environment environment) { - this.setName(name); - this.setUuid(uuid); - this.setEnvironment(environment); - } - - @NotNull - public static World from(@NotNull String name, @NotNull UUID uuid, @NotNull Environment environment) { - return new World(name, uuid, environment); - } + @Getter(AccessLevel.NONE) + private Environment environment = null; @NotNull public static World from(@NotNull String name, @NotNull UUID uuid) { return new World(name, uuid, null); } - /** - * The name of this world, as defined by the world directory name. - */ - @NotNull - public String getName() { - return name; - } - - public void setName(@NotNull String name) { - this.name = name; - } - - /** - * UUID of this world, as defined by the {@code uid.dat} file in the world directory. - */ - @NotNull - public UUID getUuid() { - return uuid; - } - - public void setUuid(@NotNull UUID uuid) { - this.uuid = uuid; - } - - /** - * Environment of the world ({@link Environment#OVERWORLD}, {@link Environment#NETHER}, {@link Environment#THE_END}, - * or {@link Environment#CUSTOM}). - * - *

Will return {@link Environment#OVERWORLD} if the environment is null - */ @NotNull + @SuppressWarnings("unused") public Environment getEnvironment() { - return Optional.ofNullable(environment).orElse(Environment.OVERWORLD); - } - - public void setEnvironment(@Nullable Environment environment) { - this.environment = environment; + return environment == null ? Environment.OVERWORLD : environment; } /** @@ -102,6 +65,7 @@ public enum Environment { THE_END, CUSTOM; + @NotNull public static Environment match(@NotNull String name) { return switch (name.toLowerCase(Locale.ENGLISH)) { case "overworld" -> OVERWORLD; @@ -115,8 +79,9 @@ public static Environment match(@NotNull String name) { @Override public boolean equals(@NotNull Object obj) { if (obj instanceof World world) { - return world.getUuid().equals(getUuid()); + return this.uuid.equals(world.uuid); } return super.equals(obj); } + } diff --git a/common/src/main/java/net/william278/huskhomes/util/GsonProvider.java b/common/src/main/java/net/william278/huskhomes/util/GsonProvider.java new file mode 100644 index 00000000..14e5a4d5 --- /dev/null +++ b/common/src/main/java/net/william278/huskhomes/util/GsonProvider.java @@ -0,0 +1,52 @@ +/* + * This file is part of HuskHomes, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.william278.huskhomes.util; + +import com.fatboyindustrial.gsonjavatime.Converters; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import net.william278.huskhomes.HuskHomes; +import net.william278.huskhomes.network.Message; +import org.jetbrains.annotations.NotNull; + +public interface GsonProvider { + + @NotNull + default GsonBuilder getGsonBuilder() { + return Converters.registerOffsetDateTime(new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + ); + } + + @NotNull + default Gson getGson() { + return getGsonBuilder().create(); + } + + @NotNull + default Message getMessageFromJson(@NotNull String json) throws JsonSyntaxException { + return getGson().fromJson(json, Message.class); + } + + @NotNull + HuskHomes getPlugin(); + +} diff --git a/common/src/test/java/net/william278/huskhomes/network/MessageSerializationTests.java b/common/src/test/java/net/william278/huskhomes/network/MessageSerializationTests.java index 06355ba0..d7506c9a 100644 --- a/common/src/test/java/net/william278/huskhomes/network/MessageSerializationTests.java +++ b/common/src/test/java/net/william278/huskhomes/network/MessageSerializationTests.java @@ -19,6 +19,7 @@ package net.william278.huskhomes.network; +import com.fatboyindustrial.gsonjavatime.Converters; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import net.william278.huskhomes.position.Position; @@ -89,7 +90,9 @@ public void testMessageSerialization(@NotNull Message message, @SuppressWarnings @NotNull private static Gson createGson() { - return new GsonBuilder().create(); + return Converters.registerOffsetDateTime(new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + ).create(); } private static Stream provideMessages() {