From 3d74c4a42716aca3240cee3bb2ae98f0059512b2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 17 Jun 2017 18:46:16 -0700 Subject: [PATCH] Added object deletion to the database. This is required when islands are reset or players deleted, for example. The deletion is done based on the uniqueId of the object. Tested and working for flat file and MySQL. To make an island do /is create. To reset do /is reset. --- .../us/tastybento/bskyblock/BSkyBlock.java | 3 +- .../bskyblock/commands/AdminCommand.java | 37 +++++- .../bskyblock/commands/IslandCommand.java | 6 +- .../flatfile/FlatFileDatabaseHandler.java | 30 ++++- .../managers/AbstractDatabaseHandler.java | 19 ++- .../database/managers/IslandsManager.java | 108 ++++++++++++++++-- .../database/managers/PlayersManager.java | 4 +- .../database/mysql/MySQLDatabaseHandler.java | 64 ++++++++++- .../bskyblock/util/DeleteIslandBlocks.java | 12 +- 9 files changed, 258 insertions(+), 25 deletions(-) diff --git a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java index 51a85c5d5..a79b5d1f1 100755 --- a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java +++ b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java @@ -18,6 +18,7 @@ import us.tastybento.bskyblock.config.BSBLocale; import us.tastybento.bskyblock.config.PluginConfig; import us.tastybento.bskyblock.config.Settings; import us.tastybento.bskyblock.database.BSBDatabase; +import us.tastybento.bskyblock.database.BSBDatabase.DatabaseType; import us.tastybento.bskyblock.database.managers.IslandsManager; import us.tastybento.bskyblock.database.managers.OfflineHistoryMessages; import us.tastybento.bskyblock.database.managers.PlayersManager; @@ -64,7 +65,7 @@ public class BSkyBlock extends JavaPlugin{ Settings.dbName = "ASkyBlock"; Settings.dbUsername = "username"; Settings.dbPassword = "password"; - */ + */ playersManager = new PlayersManager(this); islandsManager = new IslandsManager(this); // Only load metrics if set to true in config diff --git a/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java b/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java index 46ca3038c..baf8054dc 100755 --- a/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java +++ b/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java @@ -1,31 +1,60 @@ package us.tastybento.bskyblock.commands; +import java.util.List; + import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import us.tastybento.bskyblock.BSkyBlock; +import us.tastybento.bskyblock.util.VaultHelper; public class AdminCommand extends BSBCommand{ + + BSkyBlock plugin; public AdminCommand(BSkyBlock plugin) { super(plugin, true); - // TODO Auto-generated constructor stub } @Override public void setup() { - // TODO Auto-generated method stub + /* /asadmin delete - delete name's island */ + registerArgument(new String[] {"delete"}, new CommandArgumentHandler() { + + @Override + public boolean canExecute(CommandSender sender, String label, String[] args) { + // TODO Auto-generated method stub + return true; + } + + @Override + public void onExecute(CommandSender sender, String label, String[] args) { + + } + + @Override + public List onTabComplete(CommandSender sender, String label, String[] args) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String[] getHelp(CommandSender sender, String label){ + return new String[] {null, plugin.getLocale(sender).get("help.admin.delete")}; + } + }); } @Override public boolean canExecute(CommandSender sender, String label) { // TODO Auto-generated method stub - return false; + return true; } @Override public void onExecuteDefault(CommandSender sender, String label, String[] args) { - // TODO Auto-generated method stub + } diff --git a/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java b/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java index 8d58c268c..26b7c7add 100755 --- a/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java +++ b/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java @@ -255,11 +255,11 @@ public class IslandCommand extends BSBCommand{ } Player player = (Player)sender; if (plugin.getIslands().hasIsland(player.getUniqueId())) { - // Delete island - new DeleteIslandBlocks(plugin, plugin.getIslands().getIsland(player.getUniqueId())); + plugin.getIslands().deletePlayerIsland(player.getUniqueId(), true); // Create new island Schematic schematic = plugin.getSchematics().getSchematic("default"); - plugin.getIslands().newIsland(player, schematic); + plugin.getIslands().newIsland(player, schematic); + } else { Util.sendMessage(player, plugin.getLocale(player.getUniqueId()).get("error.noIsland")); } diff --git a/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java index b5c511604..ff904bedd 100644 --- a/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java @@ -49,6 +49,11 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { protected String createInsertQuery() { return ""; // not used } + @Override + protected String createDeleteQuery() { + // TODO Auto-generated method stub + return null; + } /** * Creates a filled with values from the corresponding * database file @@ -220,7 +225,7 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { // Check if this field is the mandatory UniqueId field. This is used to identify this instantiation of the class if (method.getName().equals("getUniqueId")) { // If the object does not have a unique name assigned to it already, one is created at random - plugin.getLogger().info("DEBUG: uniqueId = " + value); + //plugin.getLogger().info("DEBUG: uniqueId = " + value); String id = (String)value; if (id.isEmpty()) { id = databaseConnecter.getUniqueId(type.getSimpleName()); @@ -230,7 +235,7 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { // Save the name for when the file is saved filename = id; } - // UUID's need special serialization + // Collections need special serialization if (propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(Map.class)) { // Maps need to have keys serialized //plugin.getLogger().info("DEBUG: Map for " + field.getName()); @@ -296,6 +301,10 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { // If the value is null as a string, return null return null; } + // Bukkit may have deserialized the object already + if (clazz.equals(value.getClass())) { + return value; + } // Types that need to be deserialized if (clazz.equals(UUID.class)) { value = UUID.fromString((String)value); @@ -325,4 +334,21 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { return value; } + @Override + protected void deleteObject(T instance) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException { + // The file name of the Yaml file. + PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", type); + Method method = propertyDescriptor.getReadMethod(); + String fileName = (String) method.invoke(instance); + if (!fileName.endsWith(".yml")) { + fileName = fileName + ".yml"; + } + File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME); + File tableFolder = new File(dataFolder, type.getSimpleName()); + if (tableFolder.exists()) { + File file = new File(tableFolder, fileName); + file.delete(); + } + } + } diff --git a/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java index c95809429..f860af428 100644 --- a/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java @@ -8,6 +8,7 @@ import java.util.List; import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.database.DatabaseConnecter; +import us.tastybento.bskyblock.database.objects.Island; /** * An abstract class that handles insert/select-operations into/from a database @@ -36,6 +37,7 @@ public abstract class AbstractDatabaseHandler { /** The SQL-select- and insert query */ protected final String selectQuery; protected final String insertQuery; + protected final String deleteQuery; protected BSkyBlock plugin; @@ -57,15 +59,17 @@ public abstract class AbstractDatabaseHandler { this.type = type; this.selectQuery = createSelectQuery(); this.insertQuery = createInsertQuery(); + this.deleteQuery = createDeleteQuery(); } /** - * Create the SQL-String to insert into / select from the database + * Create the SQL-String to insert into / select / delete from the database * Not used in the flat file database * @return the SQL-String */ protected abstract String createSelectQuery(); protected abstract String createInsertQuery(); + protected abstract String createDeleteQuery(); /** * @@ -142,4 +146,17 @@ public abstract class AbstractDatabaseHandler { */ protected abstract void saveObject(T instance) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, SecurityException, InstantiationException, NoSuchMethodException; + /** + * Deletes the object with the unique id from the database + * @param instance + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws IntrospectionException + * @throws SQLException + * @throws SecurityException + * @throws NoSuchMethodException + */ + protected abstract void deleteObject(T instance) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, NoSuchMethodException, SecurityException; + } diff --git a/src/main/java/us/tastybento/bskyblock/database/managers/IslandsManager.java b/src/main/java/us/tastybento/bskyblock/database/managers/IslandsManager.java index b6de81e58..fd842b2a3 100644 --- a/src/main/java/us/tastybento/bskyblock/database/managers/IslandsManager.java +++ b/src/main/java/us/tastybento/bskyblock/database/managers/IslandsManager.java @@ -1,5 +1,7 @@ package us.tastybento.bskyblock.database.managers; +import java.beans.IntrospectionException; +import java.lang.reflect.InvocationTargetException; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; @@ -30,6 +32,7 @@ import us.tastybento.bskyblock.database.objects.Island; import us.tastybento.bskyblock.generators.IslandWorld; import us.tastybento.bskyblock.schematics.Schematic; import us.tastybento.bskyblock.schematics.Schematic.PasteReason; +import us.tastybento.bskyblock.util.DeleteIslandBlocks; import us.tastybento.bskyblock.util.SafeSpotTeleport; import us.tastybento.bskyblock.util.Util; @@ -175,6 +178,55 @@ public class IslandsManager { } } + /** + * Delete island owned by UniqueId + * @param uniqueId + */ + public void deleteIsland(UUID uniqueId){ + if (islandsByUUID.containsKey(uniqueId)) { + Island island = islandsByLocation.get(uniqueId); + islandsByUUID.remove(uniqueId); + islandsByLocation.remove(island.getCenter()); + } + } + + /** + * Delete Island + * Called when an island is restarted or reset + * + * @param player + * - player name String + * @param removeBlocks + * - true to remove the island blocks + */ + public void deletePlayerIsland(final UUID player, boolean removeBlocks) { + // Removes the island + //getLogger().info("DEBUG: deleting player island"); + //CoopPlay.getInstance().clearAllIslandCoops(player); + //getWarpSignsListener().removeWarp(player); + Island island = getIsland(player); + if (island != null) { + if (removeBlocks) { + removePlayersFromIsland(island, player); + new DeleteIslandBlocks(plugin, island); + try { + handler.deleteObject(island); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else { + island.setLocked(false); + island.setOwner(null); + } + //getServer().getPluginManager().callEvent(new IslandDeleteEvent(player, island.getCenter())); + } else { + plugin.getLogger().severe("Could not delete player: " + player.toString() + " island!"); + //plugin.getServer().getPluginManager().callEvent(new IslandDeleteEvent(player, null)); + } + //players.zeroPlayerData(player); + } + public Island getSpawn(){ return spawn; } @@ -423,7 +475,7 @@ public class IslandsManager { return true; } - + /** * Determines a safe teleport spot on player's island or the team island * they belong to. @@ -601,7 +653,7 @@ public class IslandsManager { // Nothing worked return null; } - + /** * Checks if this location is safe for a player to teleport to. Used by * warps and boat exits Unsafe is any liquid or air and also if there's no @@ -682,7 +734,7 @@ public class IslandsManager { //Bukkit.getLogger().info("DEBUG: safe!"); return true; } - + /** * Makes an island using schematic. No permission checks are made. They have to be decided * before this method is called. @@ -690,15 +742,16 @@ public class IslandsManager { * @param schematic */ public void newIsland(final Player player, final Schematic schematic) { + plugin.getLogger().info("DEBUG: new island"); //long time = System.nanoTime(); final UUID playerUUID = player.getUniqueId(); boolean firstTime = false; if (!plugin.getPlayers().hasIsland(playerUUID)) { firstTime = true; } - //plugin.getLogger().info("DEBUG: finding island location"); + plugin.getLogger().info("DEBUG: finding island location"); Location next = getNextIsland(player.getUniqueId()); - //plugin.getLogger().info("DEBUG: found " + next); + plugin.getLogger().info("DEBUG: found " + next); // Clear any old home locations (they should be clear, but just in case) plugin.getPlayers().clearHomeLocations(playerUUID); @@ -829,17 +882,20 @@ public class IslandsManager { * @return Location of island spot */ private Location getNextIsland(UUID playerUUID) { + plugin.getLogger().info("DEBUG: last = " + last); // Find the next free spot if (last == null) { last = new Location(IslandWorld.getIslandWorld(), Settings.islandXOffset + Settings.islandStartX, Settings.islandHeight, Settings.islandZOffset + Settings.islandStartZ); } Location next = last.clone(); - - while (plugin.getIslands().isIsland(next)) { + plugin.getLogger().info("DEBUG: last 2 = " + last); + do { + plugin.getLogger().info("DEBUG: getting next loc"); next = nextGridLocation(next); - } + } while (isIsland(next)); // Make the last next, last last = next.clone(); + plugin.getLogger().info("DEBUG: last 3 = " + last); return next; } @@ -851,7 +907,7 @@ public class IslandsManager { * @return Location of next free island */ private Location nextGridLocation(final Location lastIsland) { - // plugin.getLogger().info("DEBUG nextIslandLocation"); + plugin.getLogger().info("DEBUG: nextIslandLocation - island distance = " + Settings.islandDistance); final int x = lastIsland.getBlockX(); final int z = lastIsland.getBlockZ(); final Location nextPos = lastIsland; @@ -878,5 +934,39 @@ public class IslandsManager { nextPos.setZ(nextPos.getZ() - Settings.islandDistance); return nextPos; } + + /** + * This removes players from an island overworld and nether - used when reseting or deleting an island + * Mobs are killed when the chunks are refreshed. + * @param island to remove players from + * @param uuid + */ + public void removePlayersFromIsland(final Island island, UUID uuid) { + // Teleport players away + for (Player player : plugin.getServer().getOnlinePlayers()) { + if (island.inIslandSpace(player.getLocation().getBlockX(), player.getLocation().getBlockZ())) { + //plugin.getLogger().info("DEBUG: in island space"); + // Teleport island players to their island home + if (!player.getUniqueId().equals(uuid) && (plugin.getPlayers().hasIsland(player.getUniqueId()) || plugin.getPlayers().inTeam(player.getUniqueId()))) { + //plugin.getLogger().info("DEBUG: home teleport"); + homeTeleport(player); + } else { + //plugin.getLogger().info("DEBUG: move player to spawn"); + // Move player to spawn + Island spawn = getSpawn(); + if (spawn != null) { + // go to island spawn + player.teleport(IslandWorld.getIslandWorld().getSpawnLocation()); + //plugin.getLogger().warning("During island deletion player " + player.getName() + " sent to spawn."); + } else { + if (!player.performCommand(Settings.SPAWNCOMMAND)) { + plugin.getLogger().warning( + "During island deletion player " + player.getName() + " could not be sent to spawn so was dropped, sorry."); + } + } + } + } + } + } } diff --git a/src/main/java/us/tastybento/bskyblock/database/managers/PlayersManager.java b/src/main/java/us/tastybento/bskyblock/database/managers/PlayersManager.java index 8ee0b7cfe..b97d83a29 100644 --- a/src/main/java/us/tastybento/bskyblock/database/managers/PlayersManager.java +++ b/src/main/java/us/tastybento/bskyblock/database/managers/PlayersManager.java @@ -108,14 +108,14 @@ public class PlayersManager{ public Players addPlayer(final UUID playerUUID) { if (playerUUID == null) return null; - plugin.getLogger().info("DEBUG: added player " + playerUUID); + //plugin.getLogger().info("DEBUG: added player " + playerUUID); if (!playerCache.containsKey(playerUUID)) { plugin.getLogger().info("DEBUG: new player"); final Players player = new Players(playerUUID); playerCache.put(playerUUID, player); return player; } else { - plugin.getLogger().info("DEBUG: returning cache"); + //plugin.getLogger().info("DEBUG: known player"); return playerCache.get(playerUUID); } } diff --git a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java index bd8ce3659..d5034e3e9 100644 --- a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java @@ -16,7 +16,6 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -293,6 +292,10 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { return sb.toString(); } + protected String createDeleteQuery() { + return "DELETE FROM [table_name] WHERE uniqueId = ?"; + } + /** * Inserts a into the corresponding database-table * @@ -721,4 +724,63 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { return value; } + /* (non-Javadoc) + * @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#deleteObject(java.lang.Object) + */ + @Override + protected void deleteObject(T instance) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, IntrospectionException, SQLException, NoSuchMethodException, SecurityException { + // Delete this object from all tables + Connection connection = null; + PreparedStatement preparedStatement = null; + + try { + // Try to connect to the database + connection = databaseConnecter.createConnection(); + // Get the uniqueId. As each class extends DataObject, it must have this method in it. + Method getUniqueId = type.getMethod("getUniqueId"); + String uniqueId = (String) getUniqueId.invoke(instance); + //plugin.getLogger().info("DEBUG: Unique Id = " + uniqueId); + if (uniqueId.isEmpty()) { + throw new SQLException("uniqueId is blank"); + } + // Delete from the main table + // First substitution is the table name + // deleteQuery is created in super from the createInsertQuery() method + preparedStatement = connection.prepareStatement(deleteQuery.replace("[table_name]", "`" + type.getCanonicalName() + "`")); + // Second is the unique ID + preparedStatement.setString(1, uniqueId); + preparedStatement.addBatch(); + plugin.getLogger().info("DEBUG: DELETE Query " + preparedStatement.toString()); + preparedStatement.executeBatch(); + // Delete from any sub tables created from the object + // Run through the fields in the class using introspection + for (Field field : type.getDeclaredFields()) { + // Get the field's property descriptor + PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); + // Delete Collection tables + if (propertyDescriptor.getPropertyType().equals(Set.class) || + propertyDescriptor.getPropertyType().equals(Map.class) || + propertyDescriptor.getPropertyType().equals(HashMap.class) || + propertyDescriptor.getPropertyType().equals(ArrayList.class)) { + // First substitution is the table name + preparedStatement = connection.prepareStatement(deleteQuery.replace("[table_name]", "`" + type.getCanonicalName() + "." + field.getName() + "`")); + // Second is the unique ID + preparedStatement.setString(1, uniqueId); + preparedStatement.addBatch(); + // Execute + plugin.getLogger().info("DEBUG: " + preparedStatement.toString()); + preparedStatement.executeBatch(); + } + } + } finally { + // Close properly + MySQLDatabaseResourceCloser.close(preparedStatement); + } + + } + + + } diff --git a/src/main/java/us/tastybento/bskyblock/util/DeleteIslandBlocks.java b/src/main/java/us/tastybento/bskyblock/util/DeleteIslandBlocks.java index d78af771e..794a4b93b 100644 --- a/src/main/java/us/tastybento/bskyblock/util/DeleteIslandBlocks.java +++ b/src/main/java/us/tastybento/bskyblock/util/DeleteIslandBlocks.java @@ -47,12 +47,21 @@ public class DeleteIslandBlocks { //private HashMap blocksToClear = new HashMap(); private NMSAbstraction nms = null; + /** + * Deletes the island + * @param plugin + * @param island + */ public DeleteIslandBlocks(final BSkyBlock plugin, final Island island) { + plugin.getLogger().info("DEBUG: deleting the island"); final World world = island.getCenter().getWorld(); if (world == null) return; // Determine if blocks need to be cleaned up or not boolean cleanUpBlocks = false; + plugin.getLogger().info("DEBUG: island protection = " + island.getProtectionRange()); + // DEBUG + island.setProtectionRange(Settings.islandDistance); if (Settings.islandDistance - island.getProtectionRange() < 16) { cleanUpBlocks = true; } @@ -61,8 +70,7 @@ public class DeleteIslandBlocks { final int minz = island.getMinProtectedZ(); final int maxx = island.getMinProtectedX() + island.getProtectionRange(); final int maxz = island.getMinProtectedZ() + island.getProtectionRange(); - // plugin.getLogger().info("DEBUG: protection limits are: " + minx + - // ", " + minz + " to " + maxx + ", " + maxz ); + plugin.getLogger().info("DEBUG: protection limits are: " + minx + ", " + minz + " to " + maxx + ", " + maxz ); int islandSpacing = Settings.islandDistance - island.getProtectionRange(); int minxX = (island.getCenter().getBlockX() - range - islandSpacing); int minzZ = (island.getCenter().getBlockZ() - range - islandSpacing);