diff --git a/ansible/files/pvp-config/plugins/NameLayer/config.yml b/ansible/files/pvp-config/plugins/NameLayer/config.yml index 345eafa57..e72fc2568 100644 --- a/ansible/files/pvp-config/plugins/NameLayer/config.yml +++ b/ansible/files/pvp-config/plugins/NameLayer/config.yml @@ -10,7 +10,6 @@ sql: max_lifetime: 7200000 groups: enable: true - interact: false grouplimit: 35 creationOnFirstJoin: true rabbitmq: diff --git a/libraries/build.gradle.kts b/libraries/build.gradle.kts index 3d3cf7ca9..729121969 100644 --- a/libraries/build.gradle.kts +++ b/libraries/build.gradle.kts @@ -16,6 +16,12 @@ subprojects { options.release = javaVersion } + tasks.withType { + options { + (this as CoreJavadocOptions).addBooleanOption("Xdoclint:none", true) + } + } + tasks.withType { filteringCharset = "UTF-8" } diff --git a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerInvalidationMessage.java b/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerInvalidationMessage.java index 238f4a45e..bef3f1936 100644 --- a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerInvalidationMessage.java +++ b/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerInvalidationMessage.java @@ -2,22 +2,57 @@ package net.civmc.namelayer.sync; import java.util.Objects; import java.util.Set; +import java.util.UUID; -public record NameLayerInvalidationMessage(Set affectedGroupIds, boolean requiresFullResync) { +public record NameLayerInvalidationMessage( + Set affectedGroupIds, + Set affectedDefaultGroupPlayers, + Set affectedAutoAcceptPlayers, + boolean requiresFullResync +) { public NameLayerInvalidationMessage { + affectedGroupIds = affectedGroupIds == null ? Set.of() : affectedGroupIds; + affectedDefaultGroupPlayers = affectedDefaultGroupPlayers == null ? Set.of() : affectedDefaultGroupPlayers; + affectedAutoAcceptPlayers = affectedAutoAcceptPlayers == null ? Set.of() : affectedAutoAcceptPlayers; affectedGroupIds = Set.copyOf(validateGroupIds(affectedGroupIds)); - if (!requiresFullResync && affectedGroupIds.isEmpty()) { - throw new IllegalArgumentException("affectedGroupIds must not be empty unless requiresFullResync is true"); + affectedDefaultGroupPlayers = Set.copyOf(validateUuids(affectedDefaultGroupPlayers, "affectedDefaultGroupPlayers")); + affectedAutoAcceptPlayers = Set.copyOf(validateUuids(affectedAutoAcceptPlayers, "affectedAutoAcceptPlayers")); + if (!requiresFullResync + && affectedGroupIds.isEmpty() + && affectedDefaultGroupPlayers.isEmpty() + && affectedAutoAcceptPlayers.isEmpty()) { + throw new IllegalArgumentException("targeted invalidations must affect at least one cache entry"); } } public static NameLayerInvalidationMessage targeted(final Set affectedGroupIds) { - return new NameLayerInvalidationMessage(affectedGroupIds, false); + return new NameLayerInvalidationMessage(affectedGroupIds, Set.of(), Set.of(), false); + } + + public static NameLayerInvalidationMessage defaultGroups(final Set affectedPlayers) { + return new NameLayerInvalidationMessage(Set.of(), affectedPlayers, Set.of(), false); + } + + public static NameLayerInvalidationMessage autoAccepts(final Set affectedPlayers) { + return new NameLayerInvalidationMessage(Set.of(), Set.of(), affectedPlayers, false); + } + + public static NameLayerInvalidationMessage targeted( + final Set affectedGroupIds, + final Set affectedDefaultGroupPlayers, + final Set affectedAutoAcceptPlayers + ) { + return new NameLayerInvalidationMessage( + affectedGroupIds, + affectedDefaultGroupPlayers, + affectedAutoAcceptPlayers, + false + ); } public static NameLayerInvalidationMessage fullResync() { - return new NameLayerInvalidationMessage(Set.of(), true); + return new NameLayerInvalidationMessage(Set.of(), Set.of(), Set.of(), true); } private static Set validateGroupIds(final Set groupIds) { @@ -29,4 +64,14 @@ public record NameLayerInvalidationMessage(Set affectedGroupIds, boolea } return groupIds; } + + private static Set validateUuids(final Set uuids, final String fieldName) { + Objects.requireNonNull(uuids, fieldName); + for (final UUID uuid : uuids) { + if (uuid == null) { + throw new IllegalArgumentException(fieldName + " must not contain null values"); + } + } + return uuids; + } } diff --git a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerSyncCodec.java b/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerSyncCodec.java deleted file mode 100644 index 1584b98a1..000000000 --- a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerSyncCodec.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.civmc.namelayer.sync; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParseException; - -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -public final class NameLayerSyncCodec { - - private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); - - private NameLayerSyncCodec() { - } - - public static byte[] encodeWriteRequest(final NameLayerWriteRequest request) { - return encode(request); - } - - public static NameLayerWriteRequest decodeWriteRequest(final byte[] body) { - return decode(body, NameLayerWriteRequest.class); - } - - public static byte[] encodeWriteResponse(final NameLayerWriteResponse response) { - return encode(response); - } - - public static NameLayerWriteResponse decodeWriteResponse(final byte[] body) { - return decode(body, NameLayerWriteResponse.class); - } - - public static byte[] encodeInvalidation(final NameLayerInvalidationMessage invalidation) { - return encode(invalidation); - } - - public static NameLayerInvalidationMessage decodeInvalidation(final byte[] body) { - return decode(body, NameLayerInvalidationMessage.class); - } - - private static byte[] encode(final Object message) { - Objects.requireNonNull(message, "message"); - return GSON.toJson(message).getBytes(StandardCharsets.UTF_8); - } - - private static T decode(final byte[] body, final Class messageType) { - Objects.requireNonNull(body, "body"); - Objects.requireNonNull(messageType, "messageType"); - try { - return GSON.fromJson(new String(body, StandardCharsets.UTF_8), messageType); - } catch (final JsonParseException exception) { - throw new IllegalArgumentException("Invalid NameLayer synchronization message", exception); - } - } -} diff --git a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteFailureCode.java b/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteFailureCode.java index b964926e2..eaabe7680 100644 --- a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteFailureCode.java +++ b/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteFailureCode.java @@ -5,6 +5,7 @@ public enum NameLayerWriteFailureCode { DATABASE_UNAVAILABLE, GROUP_NOT_FOUND, INVALID_REQUEST, + MAX_GROUPS_REACHED, MEMBER_NOT_FOUND, NAME_CONFLICT, PLAYER_NOT_FOUND, diff --git a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteRequestPolicy.java b/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteRequestPolicy.java deleted file mode 100644 index ed79770f1..000000000 --- a/libraries/namelayer-sync/src/main/java/net/civmc/namelayer/sync/NameLayerWriteRequestPolicy.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.civmc.namelayer.sync; - -public final class NameLayerWriteRequestPolicy { - - public static final long DEFAULT_TIMEOUT_MILLIS = 5_000L; - public static final int AUTOMATIC_RETRY_ATTEMPTS = 0; - - private NameLayerWriteRequestPolicy() { - } -} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java index 65024228e..0f9ca168a 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java @@ -1,15 +1,14 @@ package vg.civcraft.mc.namelayer; import com.google.common.collect.Maps; -import java.util.Collection; import java.util.ArrayList; -import java.util.LinkedHashSet; +import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.logging.Level; import net.civmc.namelayer.sync.NameLayerWriteOperation; @@ -34,11 +33,6 @@ public class GroupManager { private static NameLayerReadDao nameLayerReadDao; private PermissionHandler permhandle; - private static Map groupsByName = new ConcurrentHashMap<>(); - private static Map groupsById = new ConcurrentHashMap<>(); - - private static boolean mergingInProgress = false; - public GroupManager() { nameLayerReadDao = NameLayerPlugin.getNameLayerReadDao(); permhandle = new PermissionHandler(); @@ -52,10 +46,6 @@ public class GroupManager { NameLayerPlugin.fullResyncGroupCache(); } - public static boolean reloadGroupById(final int groupId) { - return reloadGroupsById(List.of(groupId)); - } - public static boolean reloadGroupsById(final List groupIds) { if (groupIds == null) { NameLayerPlugin.recordTargetedReloadFailure(0); @@ -82,10 +72,20 @@ public class GroupManager { return true; } + public int countGroups(final UUID ownerUuid) { + final NameLayerGroupCache cache = getCache(); + if (cache == null) { + return 0; + } + return cache.countGroups(ownerUuid); + } + public void createGroupAsync( final UUID actorUuid, final String groupName, final String password, + final int maxGroups, + final boolean adminOverride, final Consumer callback ) { final GroupCreateEvent event = new GroupCreateEvent(groupName, actorUuid, password); @@ -101,6 +101,8 @@ public class GroupManager { arguments.put("password", event.getPassword()); } arguments.put("defaultPermissions", encodeDefaultPermissions(PermissionType.getAllPermissions())); + arguments.put("maxGroups", Integer.toString(maxGroups)); + arguments.put("adminOverride", Boolean.toString(adminOverride)); sendGroupWrite(actorUuid, NameLayerWriteOperation.CREATE_GROUP, arguments, callback); } @@ -241,7 +243,7 @@ public class GroupManager { * @param checkBeforeCreate Checks if the group already exists (asynchronously) prior to creating it. Runs the CreateEvent * synchronously, then behaves as normal after that (running async create). */ - public void createGroupAsync(final Group group, final Consumer postCreate, boolean checkBeforeCreate) { + public void createGroupAsync(final Group group, final Consumer postCreate, boolean checkBeforeCreate, final int maxGroups, final boolean adminOverride) { if (group == null) { NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "Group create failed, caller passed in null", new Exception()); Bukkit.getScheduler().runTask(NameLayerPlugin.getInstance(), () -> postCreate.accept(null)); @@ -252,7 +254,7 @@ public class GroupManager { Bukkit.getScheduler().runTask(NameLayerPlugin.getInstance(), () -> postCreate.accept(null)); return; } - createGroupAsync(group.getOwner(), group.getName(), group.getPassword(), result -> { + createGroupAsync(group.getOwner(), group.getName(), group.getPassword(), maxGroups, adminOverride, result -> { Group createdGroup = result.group(); if (!result.success() || createdGroup == null) { createdGroup = null; @@ -270,113 +272,30 @@ public class GroupManager { NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "getGroup failed, caller passed in null", new Exception()); return null; } - String lower = name.toLowerCase(); - if (getCache() != null) { - Group group = getCache().getByName(lower); + NameLayerGroupCache cache = getCache(); + if (cache != null) { + Group group = cache.getByName(lower); if (group != null) { return group; } - } else if (groupsByName.containsKey(lower)) { - return groupsByName.get(lower); } - Group group = nameLayerReadDao.getGroup(name); - if (group != null) { - if (getCache() != null) { - getCache().putGroup(group); - } else { - groupsByName.put(lower, group); - for (int j : group.getGroupIds()) { - groupsById.put(j, group); - } - } - } else { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "getGroup by Name failed, unable to find the group " + name); - } - return group; + NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "getGroup by Name failed, unable to find the group " + name); + return null; } public static Group getGroup(int groupId) { - if (getCache() != null) { - Group group = getCache().getById(groupId); + NameLayerGroupCache cache = getCache(); + if (cache != null) { + Group group = cache.getById(groupId); if (group != null) { return group; } - } else if (groupsById.containsKey(groupId)) { - return groupsById.get(groupId); } - Group group = nameLayerReadDao.getGroup(groupId); - if (group != null) { - if (getCache() != null) { - getCache().putGroup(group); - } else { - groupsByName.put(group.getName().toLowerCase(), group); - for (int j : group.getGroupIds()) { - groupsById.put(j, group); - } - } - } else { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "getGroup by ID failed, unable to find the group " + groupId); - } - return group; - } - - public static boolean hasGroup(String groupName) { - if (groupName == null) { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "HasGroup Name failed, name was null ", new Exception()); - return false; - } - - if (getCache() != null) { - return getCache().containsName(groupName) || getGroup(groupName.toLowerCase()) != null; - } else if (!groupsByName.containsKey(groupName.toLowerCase())) { - return (getGroup(groupName.toLowerCase()) != null); - } else { - return true; - } - } - - /** - * Returns the admin group for groups if the group was found to be null. - * Good for when you have to have a group that can't be null. - * - * @param name - The group name for the group - * @return Either the group or the special admin group. - */ - public static Group getSpecialCircumstanceGroup(String name) { - if (name == null) { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "getSpecialCircumstance failed, caller passed in null", new Exception()); - return null; - } - String lower = name.toLowerCase(); - if (getCache() != null) { - Group group = getCache().getByName(lower); - if (group != null) { - return group; - } - } else if (groupsByName.containsKey(lower)) { - return groupsByName.get(lower); - } - - Group group = nameLayerReadDao.getGroup(name); - if (group != null) { - if (getCache() != null) { - getCache().putGroup(group); - } else { - groupsByName.put(lower, group); - for (int j : group.getGroupIds()) { - groupsById.put(j, group); - } - } - } else { - group = nameLayerReadDao.getGroup(NameLayerPlugin.getSpecialAdminGroup()); - if (group != null && getCache() != null) { - getCache().putGroup(group); - } - } - return group; + NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "getGroup by ID failed, unable to find the group " + groupId); + return null; } /** @@ -449,67 +368,6 @@ public class GroupManager { return NameLayerPlugin.getDefaultGroupHandler().getDefaultGroup(uuid); } - /** - * Invalidates a group from cache. - * - * @param group the group to invalidate cache for - */ - public static void invalidateCache(String group) { - if (group == null) { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "invalidateCache failed, caller passed in null", new Exception()); - return; - } - - Group g = groupsByName.get(group.toLowerCase()); - if (getCache() != null) { - g = getCache().getByName(group); - } - if (g != null) { - if (getCache() != null) { - getCache().removeGroup(g); - } else { - List k = g.getGroupIds(); - groupsByName.remove(group.toLowerCase()); - boolean fail = true; - for (int j : k) { - if (groupsById.remove(j) != null) { - fail = false; - } - } - - // FALLBACK is hardloop - if (fail) { // can't find ID or cache is wrong. - for (Group x : groupsById.values()) { - if (x.getName().equals(g.getName())) { - groupsById.remove(x.getGroupId()); - } - } - } - } - } else { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "Invalidate cache by name failed, unable to find the group " + group); - } - } - - public static void invalidateCache(int groupId) { - if (getCache() != null) { - getCache().removeGroupById(groupId); - return; - } - Group group = groupsById.remove(groupId); - if (group != null) { - groupsByName.remove(group.getName().toLowerCase()); - } - } - - public int countGroups(UUID uuid) { - if (uuid == null) { - NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "countGroups failed, caller passed in null", new Exception()); - return 0; - } - return nameLayerReadDao.countGroups(uuid); - } - /** * In ascending order * Add an enum here if you wish to add more than the four default tiers of @@ -555,23 +413,6 @@ public class GroupManager { //dont yell at player for nllpt } - public static PlayerType getByID(int id) { - switch (id) { - case 0: - return PlayerType.NOT_BLACKLISTED; - case 1: - return PlayerType.MEMBERS; - case 2: - return PlayerType.MODS; - case 3: - return PlayerType.ADMINS; - case 4: - return PlayerType.OWNER; - default: - return null; - } - } - public static int getID(PlayerType type) { if (type == null) { return -1; diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java index a8734f727..04b826ea5 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java @@ -240,23 +240,6 @@ public class NameLayerPlugin extends ACivMod { if (loadGroups) { nameLayerReadDao = new NameLayerReadDao(getLogger(), db); } - - long begin_time = System.currentTimeMillis(); - - try { - getLogger().log(Level.INFO, "Update prepared, starting database update."); - if (!db.updateDatabase()) { - getLogger().log(Level.SEVERE, "Update failed, terminating Bukkit."); - Bukkit.shutdown(); - } - } catch (Exception e) { - getLogger().log(Level.SEVERE, "Update failed, terminating Bukkit. Cause:", e); - Bukkit.shutdown(); - } - - getLogger() - .log(Level.INFO, "Database update took {0} seconds", (System.currentTimeMillis() - begin_time) / 1000); - } private DataSource getNameApiDataSource() { diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/cache/NameLayerGroupCache.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/cache/NameLayerGroupCache.java index 7cbff3fa0..2760360aa 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/cache/NameLayerGroupCache.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/cache/NameLayerGroupCache.java @@ -47,10 +47,6 @@ public final class NameLayerGroupCache { return groupsById.get(groupId); } - public synchronized boolean containsName(final String name) { - return getByName(name) != null; - } - public synchronized void putGroup(final Group group) { if (group == null || group.getName() == null) { return; @@ -98,16 +94,6 @@ public final class NameLayerGroupCache { } } - public synchronized Group removeGroupById(final int groupId) { - final Group group = groupsById.get(groupId); - if (group != null) { - removeGroup(group); - } else { - groupsById.remove(groupId); - } - return group; - } - public synchronized List getGroupNames(final UUID uuid) { final Set groupIds = groupIdsByPlayer.get(uuid); if (groupIds == null || groupIds.isEmpty()) { @@ -130,4 +116,21 @@ public final class NameLayerGroupCache { public synchronized void setAppliedVersion(final long appliedVersion) { this.appliedVersion = Math.max(0L, appliedVersion); } + + public synchronized int countGroups() { + return this.groupsById.size(); + } + + public synchronized int countGroups(final UUID ownerUuid) { + if (ownerUuid == null) { + return 0; + } + int count = 0; + for (final Group group : this.groupsByName.values()) { + if (ownerUuid.equals(group.getOwner())) { + count++; + } + } + return count; + } } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java index fff2fcd09..363c822c2 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java @@ -41,7 +41,6 @@ import vg.civcraft.mc.namelayer.command.commands.SetPassword; import vg.civcraft.mc.namelayer.command.commands.ShowBlacklist; import vg.civcraft.mc.namelayer.command.commands.ToggleAutoAcceptInvites; import vg.civcraft.mc.namelayer.command.commands.TransferGroup; -import vg.civcraft.mc.namelayer.command.commands.UpdateName; import vg.civcraft.mc.namelayer.permission.PermissionType; public class CommandHandler extends CommandManager { @@ -79,7 +78,6 @@ public class CommandHandler extends CommandManager { registerCommand(new RevokeInvite()); registerCommand(new SetDefaultGroup()); registerCommand(new GetDefaultGroup()); - registerCommand(new UpdateName()); registerCommand(new AddBlacklist()); registerCommand(new RemoveBlacklist()); registerCommand(new ShowBlacklist()); diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java index 943c4db46..a9e72f07d 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java @@ -24,12 +24,8 @@ public class CreateGroup extends BaseCommandMiddle { @Description("Create a group (Public or Private). Password is optional.") public void execute(Player sender, String groupName, @Optional String userPassword) { String name = groupName; - int currentGroupCount = gm.countGroups(sender.getUniqueId()); - - if (NameLayerPlugin.getInstance().getGroupLimit() < currentGroupCount + 1 && !(sender.isOp() || sender.hasPermission("namelayer.admin"))) { - sender.sendMessage(ChatColor.RED + "You cannot create any more groups! Please delete an un-needed group before making more."); - return; - } + final boolean adminOverride = sender.isOp() || sender.hasPermission("namelayer.admin"); + final int groupLimit = NameLayerPlugin.getInstance().getGroupLimit(); //enforce regulations on the name if (name.length() > 32) { @@ -72,14 +68,11 @@ public class CreateGroup extends BaseCommandMiddle { return; } if (createdGroup == null) { - player.sendMessage(ChatColor.RED + "That group is already taken or creation failed."); + player.sendMessage(ChatColor.RED + "That group is already taken, you have reached the group limit, or creation failed."); } else { player.sendMessage(ChatColor.GREEN + "The group " + createdGroup.getName() + " was successfully created."); } - }, false); - if (NameLayerPlugin.getInstance().getGroupLimit() == (currentGroupCount + 1)) { - sender.sendMessage(ChatColor.YELLOW + "You have reached the group limit with " + NameLayerPlugin.getInstance().getGroupLimit() + " groups! Please delete un-needed groups if you wish to create more."); - } + }, false, groupLimit, adminOverride); sender.sendMessage(ChatColor.GREEN + "Group creation request is in process."); } } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/GlobalStats.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/GlobalStats.java index d6084ddac..ba1cb58e5 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/GlobalStats.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/GlobalStats.java @@ -15,14 +15,10 @@ public class GlobalStats extends BaseCommandMiddle { @CommandPermission("namelayer.admin") @Description("Get the amount of global groups.") public void execute(final CommandSender sender) { - Bukkit.getScheduler().runTaskAsynchronously(NameLayerPlugin.getInstance(), new Runnable() { - - @Override - public void run() { - int count = NameLayerPlugin.getNameLayerReadDao().countGroups(); - sender.sendMessage(ChatColor.GREEN + "The amount of groups are: " + count); - } + Bukkit.getScheduler().runTaskAsynchronously(NameLayerPlugin.getInstance(), () -> { + int count = NameLayerPlugin.getGroupCache().countGroups(); + sender.sendMessage(ChatColor.GREEN + "The amount of groups are: " + count); }); sender.sendMessage(ChatColor.GREEN + "Stats are being retrieved, please wait."); } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ToggleAutoAcceptInvites.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ToggleAutoAcceptInvites.java index 9471f7afb..2f98fb5c7 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ToggleAutoAcceptInvites.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ToggleAutoAcceptInvites.java @@ -18,11 +18,10 @@ public class ToggleAutoAcceptInvites extends BaseCommandMiddle { @CommandAlias("nltaai|autoaccept") @Description("Toggle the acceptance of invites.") public void execute(CommandSender sender) { - if (!(sender instanceof Player)) { + if (!(sender instanceof Player p)) { sender.sendMessage("how would this even work"); return; } - Player p = (Player) sender; UUID uuid = NameLayerAPI.getUUID(p.getName()); final boolean enable = !handler.getAutoAccept(uuid); handler.setAutoAcceptAsync(uuid, enable, result -> { diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/UpdateName.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/UpdateName.java deleted file mode 100644 index 720ddab3b..000000000 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/UpdateName.java +++ /dev/null @@ -1,109 +0,0 @@ -package vg.civcraft.mc.namelayer.command.commands; - -import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.Description; -import co.aikar.commands.annotation.Syntax; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; -import vg.civcraft.mc.namelayer.NameLayerAPI; -import vg.civcraft.mc.namelayer.NameLayerPlugin; -import vg.civcraft.mc.namelayer.command.BaseCommandMiddle; -import vg.civcraft.mc.namelayer.misc.NameFetcher; - -public class UpdateName extends BaseCommandMiddle { - - private Map newNames = Collections.synchronizedSortedMap(new TreeMap()); - - @CommandAlias("nlun|updatename|") - @Syntax("[confirm]") - @Description("Updates your name on this server to the one your minecraft account currently has") - public void execute(Player sender, String newNameOrConfirm) { - final Player p = (Player) sender; - final UUID uuid = p.getUniqueId(); - final String oldName = NameLayerAPI.getCurrentName(uuid); - - if (NameLayerPlugin.getNameLayerReadDao().hasChangedNameBefore(uuid)) { - p.sendMessage(ChatColor.RED + "You already changed your name"); - return; - } - - if (newNameOrConfirm.isEmpty()) { - Bukkit.getScheduler().runTaskAsynchronously( - NameLayerPlugin.getInstance(), new Runnable() { - @Override - public void run() { - NameFetcher fetcher = new NameFetcher(Collections.singletonList(uuid)); - Map fetchedNames = null; - try { - fetchedNames = fetcher.call(); - } catch (Exception e) { - e.printStackTrace(); - } - if (fetchedNames == null) { - p.sendMessage(ChatColor.RED - + "An error occured. Try again later"); - return; - } - String newName = fetchedNames.get(uuid); - if (newName == null) { - p.sendMessage(ChatColor.RED - + "An error occured. Try again later"); - return; - } - UUID existingNameUUID = NameLayerAPI.getUUID(newName); - if (existingNameUUID != null) { - if (!uuid.equals(existingNameUUID)) { - // different person has the name - p.sendMessage(ChatColor.RED - + "Someone already has the new name of your minecraft account on this server. Because of that you may not update your name"); - return; - } - if (oldName.equals(newName)) { - // name hasnt changed - p.sendMessage(ChatColor.RED - + "The name of your minecraft account is the same one as on this server"); - return; - } - // Player wants to change name to one which is - // the same one as his current name, but with - // different capitalization. We'll allow it - } - - p.sendMessage(ChatColor.GREEN - + "The current name of your minecraft account is \"" - + newName - + "\". Run \"/nlun CONFIRM\" to update your name on the server to this name. Be careful though as this change can not be reverted!"); - newNames.put(uuid, newName); - } - }); - return; - } else { - String newName = newNames.get(uuid); - if (newName == null) { - sender.sendMessage(ChatColor.RED - + "Run \"/nlun\" first to initiate the name changes process"); - return; - } - if (!newNameOrConfirm.equals("CONFIRM")) { - sender.sendMessage(ChatColor.RED - + "Run \"/nlun CONFIRM\" to confirm your name change to \"" - + newName + "\""); - return; - } - NameLayerPlugin.getNameLayerReadDao().logNameChange(uuid, oldName, - newName); - // uncomment following to directly change name - // NameAPI.getAssociationList().changePlayer(newName, uuid); - // NameAPI.resetCache(uuid); - sender.sendMessage(ChatColor.GREEN - + "Your name was changed to \"" - + newName - + "\". This change will be applied together with all other name changes at a previously announced date."); - } - } -} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/NameLayerReadDao.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/NameLayerReadDao.java index 2e9e9bebd..82fa79a85 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/NameLayerReadDao.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/NameLayerReadDao.java @@ -37,27 +37,15 @@ public final class NameLayerReadDao { private static final String GET_MEMBERS = "select fm.member_name from faction_member fm " + "inner join faction_id id on id.group_name = ? " + "where fm.group_id = id.group_id and fm.role = ?"; - private static final String COUNT_GROUPS = "select count(DISTINCT group_name) as count from faction"; - private static final String COUNT_GROUPS_FROM_UUID = "select count(DISTINCT group_name) as count from faction where founder = ?"; private static final String LOAD_ALL_AUTO_ACCEPT = "select uuid from toggleAutoAccept"; private static final String GET_AUTO_ACCEPT = "select uuid from toggleAutoAccept where uuid = ?"; private static final String GET_DEFAULT_GROUP = "select defaultgroup from default_group where uuid = ?"; private static final String GET_ALL_DEFAULT_GROUPS = "select uuid,defaultgroup from default_group"; private static final String LOAD_GROUPS_INVITATIONS = "select uuid, groupName, role from group_invitation"; - private static final String LOAD_GROUP_INVITATION = "select role from group_invitation where uuid = ? and groupName = ?"; private static final String LOAD_GROUP_INVITATIONS_FOR_GROUP = "select uuid,role from group_invitation where groupName=?"; - private static final String GET_GROUP_NAME_FROM_ROLE = "SELECT DISTINCT faction_id.group_name FROM faction_member " - + "inner join faction_id on faction_member.group_id = faction_id.group_id " - + "WHERE member_name = ? " - + "AND role = ?"; private static final String GET_PLAYER_TYPE = "SELECT role FROM faction_member WHERE group_id = ? AND member_name = ?"; - private static final String LOG_NAME_CHANGE = "insert into nameLayerNameChanges (uuid,oldName,newName) values(?,?,?)"; - private static final String CHECK_FOR_NAME_CHANGE = "select * from nameLayerNameChanges where uuid=?"; private static final String GET_PERMISSION = "select pg.role,pg.permission_name from permission_by_group_name pg inner join faction_id fi on fi.group_name=? " + "where pg.group_id = fi.group_id"; - private static final String GET_BLACKLIST_MEMBERS = "select b.member_name from blacklist b inner join faction_id fi on fi.group_name=? where b.group_id=fi.group_id"; - private static final String GET_GROUP_IDS = "SELECT f.group_id, count(DISTINCT fm.member_name) AS sz FROM faction_id f " - + "LEFT JOIN faction_member fm ON f.group_id = fm.group_id WHERE f.group_name = ? GROUP BY f.group_id ORDER BY sz DESC"; private static final String LOAD_ALL_GROUP_IDS = "SELECT f.group_name, f.group_id, count(DISTINCT fm.member_name) AS sz " + "FROM faction_id f LEFT JOIN faction_member fm ON f.group_id = fm.group_id " + "GROUP BY f.group_name, f.group_id ORDER BY f.group_name, sz DESC"; @@ -403,23 +391,6 @@ public final class NameLayerReadDao { return groups; } - public List getGroupNames(final UUID uuid, final String role) { - final List groups = new ArrayList<>(); - try (Connection connection = db.getConnection(); - PreparedStatement getGroupNameFromRole = connection.prepareStatement(GET_GROUP_NAME_FROM_ROLE)) { - getGroupNameFromRole.setString(1, uuid.toString()); - getGroupNameFromRole.setString(2, role); - try (ResultSet set = getGroupNameFromRole.executeQuery()) { - while (set.next()) { - groups.add(set.getString(1)); - } - } - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Problem getting player " + uuid + " groups by role " + role, exception); - } - return groups; - } - public PlayerType getPlayerType(final int groupId, final UUID uuid) { try (Connection connection = db.getConnection(); PreparedStatement getPlayerType = connection.prepareStatement(GET_PLAYER_TYPE)) { @@ -478,30 +449,6 @@ public final class NameLayerReadDao { return perms; } - public int countGroups() { - try (Connection connection = db.getConnection(); - Statement countGroups = connection.createStatement(); - ResultSet set = countGroups.executeQuery(COUNT_GROUPS)) { - return set.next() ? set.getInt("count") : 0; - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Problem counting groups", exception); - return 0; - } - } - - public int countGroups(final UUID uuid) { - try (Connection connection = db.getConnection(); - PreparedStatement countGroupsFromUUID = connection.prepareStatement(COUNT_GROUPS_FROM_UUID)) { - countGroupsFromUUID.setString(1, uuid.toString()); - try (ResultSet set = countGroupsFromUUID.executeQuery()) { - return set.next() ? set.getInt("count") : 0; - } - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Problem counting groups for " + uuid, exception); - return 0; - } - } - public Set loadAllAutoAccept() { final Set accepts = new HashSet<>(); try (Connection connection = db.getConnection(); @@ -516,8 +463,7 @@ public final class NameLayerReadDao { return accepts; } - @Deprecated - public boolean shouldAutoAcceptGroups(final UUID uuid) { + public boolean isAutoAcceptEnabled(final UUID uuid) { try (Connection connection = db.getConnection(); PreparedStatement statement = connection.prepareStatement(GET_AUTO_ACCEPT)) { statement.setString(1, uuid.toString()); @@ -557,26 +503,6 @@ public final class NameLayerReadDao { return groups; } - public void loadGroupInvitation(final UUID playerUUID, final Group group) { - if (group == null) { - return; - } - try (Connection connection = db.getConnection(); - PreparedStatement loadGroupInvitation = connection.prepareStatement(LOAD_GROUP_INVITATION)) { - loadGroupInvitation.setString(1, playerUUID.toString()); - loadGroupInvitation.setString(2, group.getName()); - try (ResultSet set = loadGroupInvitation.executeQuery()) { - while (set.next()) { - final String role = set.getString("role"); - final PlayerType type = role == null ? null : PlayerType.getPlayerType(role); - GroupManager.reloadGroupById(group.getGroupId()); - } - } - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Problem loading group " + group.getName() + " invites for " + playerUUID, exception); - } - } - public Map getInvitesForGroup(final String groupName) { final Map invitations = new TreeMap<>(); if (groupName == null) { @@ -608,9 +534,7 @@ public final class NameLayerReadDao { while (set.next()) { final String uuid = set.getString("uuid"); final String group = set.getString("groupName"); - final String role = set.getString("role"); final Group cachedGroup = group == null ? null : GroupManager.getGroup(group); - final PlayerType type = role == null ? null : PlayerType.getPlayerType(role); if (uuid != null && cachedGroup != null) { final UUID playerUUID = UUID.fromString(uuid); PlayerListener.addNotification(playerUUID, cachedGroup); @@ -621,67 +545,6 @@ public final class NameLayerReadDao { } } - public void logNameChange(final UUID uuid, final String oldName, final String newName) { - try (Connection connection = db.getConnection(); - PreparedStatement logNameChange = connection.prepareStatement(LOG_NAME_CHANGE)) { - logNameChange.setString(1, uuid.toString()); - logNameChange.setString(2, oldName); - logNameChange.setString(3, newName); - logNameChange.executeUpdate(); - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Failed to log a name change for " + uuid + " from " + oldName + " -> " + newName, exception); - } - } - - public boolean hasChangedNameBefore(final UUID uuid) { - try (Connection connection = db.getConnection(); - PreparedStatement checkForNameChange = connection.prepareStatement(CHECK_FOR_NAME_CHANGE)) { - checkForNameChange.setString(1, uuid.toString()); - try (ResultSet set = checkForNameChange.executeQuery()) { - return set.next(); - } - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Failed to check if " + uuid + " has previously changed names", exception); - } - return false; - } - - public Set getBlackListMembers(final String groupName) { - final Set uuids = new HashSet<>(); - try (Connection connection = db.getConnection(); - PreparedStatement getBlackListMembers = connection.prepareStatement(GET_BLACKLIST_MEMBERS)) { - getBlackListMembers.setString(1, groupName); - try (ResultSet set = getBlackListMembers.executeQuery()) { - while (set.next()) { - uuids.add(UUID.fromString(set.getString(1))); - } - } - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Unable to retrieve black list members for group " + groupName, exception); - } - return uuids; - } - - public List getAllIDs(final String groupName) { - if (groupName == null) { - return null; - } - try (Connection connection = db.getConnection(); - PreparedStatement getGroupIDs = connection.prepareStatement(GET_GROUP_IDS)) { - getGroupIDs.setString(1, groupName); - try (ResultSet set = getGroupIDs.executeQuery()) { - final List ids = new ArrayList<>(); - while (set.next()) { - ids.add(set.getInt(1)); - } - return ids; - } - } catch (final SQLException exception) { - logger.log(Level.WARNING, "Unable to fully load group ID set", exception); - } - return null; - } - public GroupLoadSnapshot loadAllGroupsSnapshot() { final GroupSnapshotData data = newSnapshotData(); final long cacheVersion; diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/AutoAcceptHandler.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/AutoAcceptHandler.java index ca84423a5..101a5de3a 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/AutoAcceptHandler.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/AutoAcceptHandler.java @@ -24,9 +24,7 @@ public class AutoAcceptHandler { if (accept && !autoAccepts.contains(player)) { autoAccepts.add(player); } else { - if (autoAccepts.contains(player)) { - autoAccepts.remove(player); - } + autoAccepts.remove(player); } } @@ -49,10 +47,6 @@ public class AutoAcceptHandler { }); } - public void toggleAutoAcceptAsync(final UUID player, final Consumer callback) { - setAutoAcceptAsync(player, !getAutoAccept(player), callback); - } - public boolean getAutoAccept(UUID uuid) { return autoAccepts.contains(uuid); } @@ -61,6 +55,10 @@ public class AutoAcceptHandler { this.autoAccepts = autoAccepts; } + public void reload(final UUID player, final boolean accept) { + cacheAutoAccept(player, accept); + } + private void handleWriteResponse( final UUID player, final boolean accept, diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/DefaultGroupHandler.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/DefaultGroupHandler.java index 5e1ad9365..96f0748d2 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/DefaultGroupHandler.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/DefaultGroupHandler.java @@ -15,7 +15,7 @@ import vg.civcraft.mc.namelayer.rabbitmq.NameLayerWriteClient; public class DefaultGroupHandler { - private NameLayerReadDao dao; + private final NameLayerReadDao dao; private Map defaultGroups; @@ -36,6 +36,15 @@ public class DefaultGroupHandler { defaultGroups = dao.getAllDefaultGroups(); } + public void reload(final UUID uuid) { + final String groupName = dao.getDefaultGroup(uuid); + if (groupName == null) { + defaultGroups.remove(uuid); + return; + } + defaultGroups.put(uuid, groupName); + } + public void cacheDefaultGroup(UUID uuid, Group g) { defaultGroups.put(uuid, g.getName()); } @@ -101,9 +110,4 @@ public class DefaultGroupHandler { } } - public String recacheDefaultGroup(UUID uuid) { - String gName = dao.getDefaultGroup(uuid); - defaultGroups.put(uuid, gName); - return gName; - } } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java index d20b7821b..21265fc60 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java @@ -276,15 +276,6 @@ public class GUIGroupOverview { showScreen(); return; } - if (NameLayerPlugin.getInstance().getGroupLimit() < gm - .countGroups(p.getUniqueId()) + 1 - && !(p.isOp() || p - .hasPermission("namelayer.admin"))) { - p.sendMessage(ChatColor.RED - + "You cannot create any more groups! Please delete an un-needed group before making more."); - showScreen(); - return; - } // enforce regulations on the name if (groupName.length() > 32) { p.sendMessage(ChatColor.RED @@ -319,6 +310,8 @@ public class GUIGroupOverview { } final UUID uuid = p.getUniqueId(); + final boolean adminOverride = p.isOp() || p.hasPermission("namelayer.admin"); + final int groupLimit = NameLayerPlugin.getInstance().getGroupLimit(); Group g = new Group(groupName, uuid, false, null, -1, System.currentTimeMillis(), "GRAY"); gm.createGroupAsync(g, createdGroup -> { if (createdGroup != null) { @@ -332,17 +325,12 @@ public class GUIGroupOverview { } if (createdGroup == null) { // failure - player.sendMessage(ChatColor.RED + "That group is already taken or creation failed."); + player.sendMessage(ChatColor.RED + "That group is already taken, you have reached the group limit, or creation failed."); } else { player.sendMessage(ChatColor.GREEN + "The group " + createdGroup.getName() + " was successfully created."); - if (NameLayerPlugin.getInstance().getGroupLimit() == gm.countGroups(player.getUniqueId())) { - player.sendMessage(ChatColor.YELLOW + "You have reached the group limit with " - + NameLayerPlugin.getInstance().getGroupLimit() - + " groups! Please delete un-needed groups if you wish to create more."); - } } showScreen(); - }, false); + }, false, groupLimit, adminOverride); } }; diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerInvalidationConsumer.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerInvalidationConsumer.java index ac5b08abb..4bf75b484 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerInvalidationConsumer.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerInvalidationConsumer.java @@ -1,24 +1,32 @@ package vg.civcraft.mc.namelayer.rabbitmq; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.DeliverCallback; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; -import org.bukkit.plugin.java.JavaPlugin; import net.civmc.namelayer.sync.NameLayerInvalidationMessage; import net.civmc.namelayer.sync.NameLayerRabbitMqTopology; -import net.civmc.namelayer.sync.NameLayerSyncCodec; +import org.bukkit.plugin.java.JavaPlugin; import vg.civcraft.mc.namelayer.GroupManager; import vg.civcraft.mc.namelayer.NameLayerPlugin; +import vg.civcraft.mc.namelayer.group.AutoAcceptHandler; +import vg.civcraft.mc.namelayer.group.DefaultGroupHandler; public final class NameLayerInvalidationConsumer implements AutoCloseable { + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + private final ConnectionFactory connectionFactory; private final String serverId; private final Logger logger; @@ -101,13 +109,16 @@ public final class NameLayerInvalidationConsumer implements AutoCloseable { private void handleDelivery(final byte[] body, final long deliveryTag) throws IOException { try { - final NameLayerInvalidationMessage invalidation = NameLayerSyncCodec.decodeInvalidation(body); + final NameLayerInvalidationMessage invalidation = GSON.fromJson( + new String(body, StandardCharsets.UTF_8), + NameLayerInvalidationMessage.class + ); if (applyInvalidation(invalidation)) { channel.basicAck(deliveryTag, false); } else { channel.basicNack(deliveryTag, false, true); } - } catch (final IllegalArgumentException exception) { + } catch (final JsonParseException exception) { logger.log(Level.WARNING, "Rejecting malformed NameLayer invalidation", exception); channel.basicReject(deliveryTag, false); } catch (final RuntimeException exception) { @@ -122,8 +133,30 @@ public final class NameLayerInvalidationConsumer implements AutoCloseable { GroupManager.fullResyncCache(); return true; } - logger.log(Level.INFO, "Applying NameLayer targeted invalidation for " + invalidation.affectedGroupIds().size() + " groups"); - return GroupManager.reloadGroupsById(new ArrayList<>(invalidation.affectedGroupIds())); + logger.log(Level.INFO, "Applying NameLayer targeted invalidation for " + + invalidation.affectedGroupIds().size() + " groups, " + + invalidation.affectedDefaultGroupPlayers().size() + " default groups, " + + invalidation.affectedAutoAcceptPlayers().size() + " auto-accept entries"); + if (!invalidation.affectedGroupIds().isEmpty() + && !GroupManager.reloadGroupsById(new ArrayList<>(invalidation.affectedGroupIds()))) { + return false; + } + final DefaultGroupHandler defaultGroupHandler = NameLayerPlugin.getDefaultGroupHandler(); + if (defaultGroupHandler != null) { + for (final UUID playerUuid : invalidation.affectedDefaultGroupPlayers()) { + defaultGroupHandler.reload(playerUuid); + } + } + final AutoAcceptHandler autoAcceptHandler = NameLayerPlugin.getAutoAcceptHandler(); + if (autoAcceptHandler != null) { + for (final UUID playerUuid : invalidation.affectedAutoAcceptPlayers()) { + autoAcceptHandler.reload( + playerUuid, + NameLayerPlugin.getNameLayerReadDao().isAutoAcceptEnabled(playerUuid) + ); + } + } + return true; } @Override diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerWriteClient.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerWriteClient.java index 52d99a2f9..eaee63ac4 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerWriteClient.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/rabbitmq/NameLayerWriteClient.java @@ -1,11 +1,15 @@ package vg.civcraft.mc.namelayer.rabbitmq; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.DeliverCallback; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -15,13 +19,13 @@ import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import net.civmc.namelayer.sync.NameLayerRabbitMqTopology; -import net.civmc.namelayer.sync.NameLayerSyncCodec; import net.civmc.namelayer.sync.NameLayerWriteRequest; import net.civmc.namelayer.sync.NameLayerWriteResponse; public final class NameLayerWriteClient implements AutoCloseable { private static final long RESPONSE_TIMEOUT_SECONDS = 10L; + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); private final ConnectionFactory connectionFactory; private final String responseQueue; @@ -45,7 +49,7 @@ public final class NameLayerWriteClient implements AutoCloseable { connection = connectionFactory.newConnection("namelayer-paper-writes"); channel = connection.createChannel(); channel.queueDeclare(responseQueue, false, true, true, null); - final DeliverCallback deliverCallback = (consumerTag, delivery) -> handleResponse(delivery.getBody(), delivery.getProperties()); + final DeliverCallback deliverCallback = (consumerTag, delivery) -> handleResponse(delivery.getBody()); channel.basicConsume(responseQueue, true, deliverCallback, consumerTag -> { }); return true; @@ -73,7 +77,12 @@ public final class NameLayerWriteClient implements AutoCloseable { .deliveryMode(2) .build(); synchronized (this) { - channel.basicPublish("", NameLayerRabbitMqTopology.WRITE_REQUEST_QUEUE, properties, NameLayerSyncCodec.encodeWriteRequest(request)); + channel.basicPublish( + "", + NameLayerRabbitMqTopology.WRITE_REQUEST_QUEUE, + properties, + GSON.toJson(request).getBytes(StandardCharsets.UTF_8) + ); } } catch (final IOException exception) { pendingResponses.remove(request.requestId()); @@ -82,11 +91,11 @@ public final class NameLayerWriteClient implements AutoCloseable { return responseFuture; } - private void handleResponse(final byte[] body, final AMQP.BasicProperties properties) { + private void handleResponse(final byte[] body) { final NameLayerWriteResponse response; try { - response = NameLayerSyncCodec.decodeWriteResponse(body); - } catch (final IllegalArgumentException exception) { + response = GSON.fromJson(new String(body, StandardCharsets.UTF_8), NameLayerWriteResponse.class); + } catch (final JsonParseException exception) { logger.log(Level.WARNING, "Dropping malformed NameLayer write response", exception); return; } diff --git a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/NameLayerVelocityPlugin.java b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/NameLayerVelocityPlugin.java index 628baa00e..f76b78e36 100644 --- a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/NameLayerVelocityPlugin.java +++ b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/NameLayerVelocityPlugin.java @@ -43,6 +43,11 @@ public final class NameLayerVelocityPlugin { return; } + try { + Class.forName("org.mariadb.jdbc.Driver"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } dataSource = config.database().createDataSource(); if (!NameLayerDatabaseMigrator.migrate(dataSource, logger)) { return; diff --git a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerInvalidationPublisher.java b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerInvalidationPublisher.java index c95581218..45e211e17 100644 --- a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerInvalidationPublisher.java +++ b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerInvalidationPublisher.java @@ -1,19 +1,23 @@ package net.civmc.namelayer.velocity.rabbitmq; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; import java.util.concurrent.TimeoutException; import net.civmc.namelayer.sync.NameLayerInvalidationMessage; import net.civmc.namelayer.sync.NameLayerRabbitMqTopology; -import net.civmc.namelayer.sync.NameLayerSyncCodec; import org.slf4j.Logger; public final class NameLayerInvalidationPublisher implements AutoCloseable { private static final long PUBLISH_CONFIRM_TIMEOUT_MILLIS = 5_000L; + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); private final ConnectionFactory connectionFactory; private final Logger logger; @@ -44,6 +48,7 @@ public final class NameLayerInvalidationPublisher implements AutoCloseable { } public boolean publish(final NameLayerInvalidationMessage invalidation) { + Objects.requireNonNull(invalidation, "invalidation"); if (channel == null || !channel.isOpen()) { logger.error("NameLayer invalidation publisher is not connected"); return false; @@ -57,7 +62,7 @@ public final class NameLayerInvalidationPublisher implements AutoCloseable { NameLayerRabbitMqTopology.INVALIDATION_EXCHANGE, "", properties, - NameLayerSyncCodec.encodeInvalidation(invalidation) + GSON.toJson(invalidation).getBytes(StandardCharsets.UTF_8) ); return channel.waitForConfirms(PUBLISH_CONFIRM_TIMEOUT_MILLIS); } catch (final IOException | InterruptedException | TimeoutException exception) { diff --git a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerWriteRequestConsumer.java b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerWriteRequestConsumer.java index ae6113617..58444a9ff 100644 --- a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerWriteRequestConsumer.java +++ b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/rabbitmq/NameLayerWriteRequestConsumer.java @@ -1,15 +1,18 @@ package net.civmc.namelayer.velocity.rabbitmq; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.DeliverCallback; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.TimeoutException; import net.civmc.namelayer.sync.NameLayerRabbitMqTopology; -import net.civmc.namelayer.sync.NameLayerSyncCodec; import net.civmc.namelayer.sync.NameLayerWriteFailureCode; import net.civmc.namelayer.sync.NameLayerWriteRequest; import net.civmc.namelayer.sync.NameLayerWriteResponse; @@ -18,6 +21,8 @@ import org.slf4j.Logger; public final class NameLayerWriteRequestConsumer implements AutoCloseable { + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + private final ConnectionFactory connectionFactory; private final NameLayerWriteCoordinator coordinator; private final Logger logger; @@ -61,9 +66,9 @@ public final class NameLayerWriteRequestConsumer implements AutoCloseable { NameLayerWriteRequest request = null; NameLayerWriteResponse response; try { - request = NameLayerSyncCodec.decodeWriteRequest(body); + request = GSON.fromJson(new String(body, StandardCharsets.UTF_8), NameLayerWriteRequest.class); response = coordinator.handle(request); - } catch (final IllegalArgumentException exception) { + } catch (final JsonParseException exception) { logger.warn("Rejecting malformed NameLayer write request", exception); response = malformedRequestResponse(properties); } catch (final RuntimeException exception) { @@ -113,7 +118,7 @@ public final class NameLayerWriteRequestConsumer implements AutoCloseable { .correlationId(response.requestId().toString()) .deliveryMode(1) .build(); - channel.basicPublish("", replyQueue, responseProperties, NameLayerSyncCodec.encodeWriteResponse(response)); + channel.basicPublish("", replyQueue, responseProperties, GSON.toJson(response).getBytes(StandardCharsets.UTF_8)); } private String responseQueue(final AMQP.BasicProperties properties, final NameLayerWriteRequest request) { diff --git a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/write/NameLayerWriteCoordinator.java b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/write/NameLayerWriteCoordinator.java index 1cbb1eb4a..ee1111f30 100644 --- a/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/write/NameLayerWriteCoordinator.java +++ b/plugins/namelayer-velocity/src/main/java/net/civmc/namelayer/velocity/write/NameLayerWriteCoordinator.java @@ -32,6 +32,7 @@ public final class NameLayerWriteCoordinator { private static final String SET_GROUP_OWNER = "UPDATE faction f JOIN faction_id fi ON fi.group_name = f.group_name SET f.founder = ? WHERE fi.group_id = ?"; private static final String SET_OWNER_MEMBER_ROLE = "UPDATE faction_member SET role = 'OWNER' WHERE group_id = ? AND member_name = ?"; private static final String CREATE_GROUP = "CALL createGroup(?, ?, ?, ?)"; + private static final String COUNT_OWNED_GROUPS = "SELECT COUNT(*) FROM faction_id WHERE founder = ?"; private static final String ADD_PERMISSION_BY_NAME = "INSERT IGNORE INTO permission_by_group_name(group_id, role, permission_name) VALUES (?, ?, ?)"; private static final String GET_DEFAULT_GROUP_ID = "SELECT dg.defaultgroup, fi.group_id FROM default_group dg " + "INNER JOIN faction_id fi ON fi.group_name = dg.defaultgroup WHERE dg.uuid = ? LIMIT 1 FOR UPDATE"; @@ -108,6 +109,17 @@ public final class NameLayerWriteCoordinator { try (Connection connection = dataSource.getConnection()) { connection.setAutoCommit(false); try { + if (!createWrite.adminOverride() && createWrite.maxGroups() > 0) { + final int ownedGroups = countOwnedGroups(connection, request.actorUuid()); + if (ownedGroups >= createWrite.maxGroups()) { + connection.rollback(); + return NameLayerWriteResponse.failure( + request.requestId(), + NameLayerWriteFailureCode.MAX_GROUPS_REACHED, + "You have reached the group limit of " + createWrite.maxGroups() + ); + } + } final int groupId; try (PreparedStatement statement = connection.prepareStatement(CREATE_GROUP)) { statement.setString(1, createWrite.groupName()); @@ -166,8 +178,12 @@ public final class NameLayerWriteCoordinator { setDefaultGroup(connection, request.actorUuid(), createdGroup.groupName()); incrementCacheVersion(connection); connection.commit(); - publishFullInvalidation(); - return successFullResync(request, Set.of(createdGroup.groupId())); + publishInvalidation(NameLayerInvalidationMessage.targeted( + Set.of(createdGroup.groupId()), + Set.of(request.actorUuid()), + Set.of() + )); + return NameLayerWriteResponse.success(request.requestId(), Set.of(createdGroup.groupId())); } catch (final SQLException exception) { connection.rollback(); throw exception; @@ -504,7 +520,7 @@ public final class NameLayerWriteCoordinator { setDefaultGroup(connection, request.actorUuid(), groupName); incrementCacheVersion(connection); connection.commit(); - publishFullInvalidation(); + publishInvalidation(NameLayerInvalidationMessage.defaultGroups(Set.of(request.actorUuid()))); return NameLayerWriteResponse.success(request.requestId(), Set.of()); } catch (final SQLException exception) { connection.rollback(); @@ -534,7 +550,7 @@ public final class NameLayerWriteCoordinator { statement.executeUpdate(); incrementCacheVersion(connection); connection.commit(); - publishFullInvalidation(); + publishInvalidation(NameLayerInvalidationMessage.autoAccepts(Set.of(request.actorUuid()))); return NameLayerWriteResponse.success(request.requestId(), Set.of()); } catch (final SQLException exception) { connection.rollback(); @@ -632,6 +648,18 @@ public final class NameLayerWriteCoordinator { } } + private int countOwnedGroups(final Connection connection, final UUID ownerUuid) throws SQLException { + try (PreparedStatement statement = connection.prepareStatement(COUNT_OWNED_GROUPS)) { + statement.setString(1, ownerUuid.toString()); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + return 0; + } + return resultSet.getInt(1); + } + } + } + private NameLayerWriteResponse handleMetadataWrite( final NameLayerWriteRequest request, final String sql, @@ -884,31 +912,16 @@ public final class NameLayerWriteCoordinator { } private void publishInvalidation(final int groupId) { - final boolean published = invalidationPublisher.publish(NameLayerInvalidationMessage.targeted(Set.of(groupId))); - if (!published) { - logger.error("NameLayer write committed, but failed to publish invalidation for group {}", groupId); - } + publishInvalidation(NameLayerInvalidationMessage.targeted(Set.of(groupId))); } - private void publishFullInvalidation() { - final boolean published = invalidationPublisher.publish(NameLayerInvalidationMessage.fullResync()); + private void publishInvalidation(final NameLayerInvalidationMessage invalidation) { + final boolean published = invalidationPublisher.publish(invalidation); if (!published) { - logger.error("NameLayer write committed, but failed to publish full resync invalidation"); + logger.error("NameLayer write committed, but failed to publish invalidation {}", invalidation); } } - private NameLayerWriteResponse successFullResync(final NameLayerWriteRequest request, final Set affectedGroupIds) { - return new NameLayerWriteResponse( - request.requestId(), - true, - null, - "", - affectedGroupIds, - true, - System.currentTimeMillis() - ); - } - private boolean acquireLock(final Connection connection, final String lockName) throws SQLException { try (PreparedStatement statement = connection.prepareStatement(GET_PLAYER_LOCK)) { statement.setString(1, lockName); @@ -1263,14 +1276,33 @@ public final class NameLayerWriteCoordinator { } } - private record CreateGroupWrite(String groupName, String password, Set defaultPermissions) { + private record CreateGroupWrite( + String groupName, + String password, + Set defaultPermissions, + int maxGroups, + boolean adminOverride + ) { private static CreateGroupWrite from(final Map arguments) { final String groupName = PermissionWrite.requireNonBlank(arguments, "groupName"); final String password = Boolean.parseBoolean(arguments.getOrDefault("hasPassword", "false")) ? arguments.getOrDefault("password", "") : null; - return new CreateGroupWrite(groupName, password, DefaultPermission.parse(arguments.getOrDefault("defaultPermissions", ""))); + final int maxGroups; + try { + maxGroups = Integer.parseInt(arguments.getOrDefault("maxGroups", "0")); + } catch (final NumberFormatException exception) { + throw new IllegalArgumentException("maxGroups must be an integer"); + } + final boolean adminOverride = Boolean.parseBoolean(arguments.getOrDefault("adminOverride", "false")); + return new CreateGroupWrite( + groupName, + password, + DefaultPermission.parse(arguments.getOrDefault("defaultPermissions", "")), + maxGroups, + adminOverride + ); } }