diff --git a/pom.xml b/pom.xml index c73fa8ff..9e30a902 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,14 @@ jar + + com.jolbox + bonecp + 0.8.0.RELEASE + compile + jar + + com.google.guava guava @@ -167,13 +175,27 @@ compile jar - + junit junit - 4.8.1 + 4.11 test jar + + + org.hamcrest + hamcrest-core + + + + + + org.hamcrest + hamcrest-library + 1.2.1 + test + true @@ -338,6 +360,7 @@ net.sf.opencsv:opencsv org.khelekore:prtree + com.jolbox:bonecp diff --git a/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/AbstractJob.java b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/AbstractJob.java new file mode 100644 index 00000000..44e6e3ec --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/AbstractJob.java @@ -0,0 +1,78 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.protection.database.mysql; + +import com.sk89q.worldguard.bukkit.ConfigurationManager; +import org.yaml.snakeyaml.error.YAMLException; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +@SuppressWarnings("ProtectedField") +abstract class AbstractJob { + + protected final MySQLDatabaseImpl database; + protected final ConfigurationManager config; + protected final Connection conn; + protected final Logger logger; + + protected AbstractJob(MySQLDatabaseImpl database, Connection conn) { + checkNotNull(database); + checkNotNull(conn); + this.database = database; + this.config = database.getConfiguration(); + this.conn = conn; + this.logger = database.getLogger(); + } + + static void closeQuietly(ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException ignored) {} + } + } + + static void closeQuietly(Statement st) { + if (st != null) { + try { + st.close(); + } catch (SQLException ignored) {} + } + } + + protected Object sqlUnmarshal(String rawValue) { + try { + return database.getYaml().load(rawValue); + } catch (YAMLException e) { + return String.valueOf(rawValue); + } + } + + protected String sqlMarshal(Object rawObject) { + return database.getYaml().dump(rawObject); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/MySQLDatabaseImpl.java b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/MySQLDatabaseImpl.java new file mode 100644 index 00000000..5b4e09ad --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/MySQLDatabaseImpl.java @@ -0,0 +1,215 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.protection.database.mysql; + +import com.jolbox.bonecp.BoneCP; +import com.jolbox.bonecp.BoneCPConfig; +import com.sk89q.worldguard.bukkit.ConfigurationManager; +import com.sk89q.worldguard.protection.databases.AbstractAsynchronousDatabase; +import com.sk89q.worldguard.protection.databases.InvalidTableFormatException; +import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * For internal use. Do not subclass. + */ +public class MySQLDatabaseImpl extends AbstractAsynchronousDatabase { + + private final ConfigurationManager config; + private final Logger logger; + + private final BoneCP connectionPool; + private final Yaml yaml = createYaml(); + private final int worldId; + + private Map regions = new HashMap(); + + public MySQLDatabaseImpl(ConfigurationManager config, String worldName, Logger logger) throws ProtectionDatabaseException { + checkNotNull(config); + checkNotNull(worldName); + checkNotNull(logger); + + this.config = config; + this.logger = logger; + + BoneCPConfig poolConfig = new BoneCPConfig(); + poolConfig.setJdbcUrl(config.sqlDsn); + poolConfig.setUsername(config.sqlUsername); + poolConfig.setPassword(config.sqlPassword); + + try { + connectionPool = new BoneCP(poolConfig); + } catch (SQLException e) { + throw new ProtectionDatabaseException("Failed to connect to the database", e); + } + + try { + worldId = chooseWorldId(worldName); + } catch (SQLException e) { + throw new ProtectionDatabaseException("Failed to choose the ID for this world", e); + } + } + + private int chooseWorldId(String worldName) throws SQLException { + Connection conn = getConnection(); + PreparedStatement worldStmt = null; + ResultSet worldResult = null; + PreparedStatement insertWorldStatement = null; + + try { + PreparedStatement verTest = null; + try { + // Test if the database is up to date, if not throw a critical error + verTest = conn.prepareStatement("SELECT `world_id` FROM `" + config.sqlTablePrefix + "region_cuboid` LIMIT 0, 1;"); + verTest.execute(); + } catch (SQLException ex) { + throw new InvalidTableFormatException("region_storage_update_20110325.sql"); + } finally { + AbstractJob.closeQuietly(verTest); + } + + worldStmt = conn.prepareStatement( + "SELECT `id` FROM `" + config.sqlTablePrefix + "world` WHERE `name` = ? LIMIT 0, 1" + ); + + worldStmt.setString(1, worldName); + worldResult = worldStmt.executeQuery(); + + if (worldResult.first()) { + return worldResult.getInt("id"); + } else { + insertWorldStatement = conn.prepareStatement( + "INSERT INTO " + + "`" + config.sqlTablePrefix + "world` " + + "(`id`, `name`) VALUES (null, ?)", + Statement.RETURN_GENERATED_KEYS + ); + + insertWorldStatement.setString(1, worldName); + insertWorldStatement.execute(); + ResultSet generatedKeys = insertWorldStatement.getGeneratedKeys(); + + if (generatedKeys.first()) { + return generatedKeys.getInt(1); + } else { + throw new SQLException("Expected result, got none"); + } + } + } finally { + AbstractJob.closeQuietly(worldResult); + AbstractJob.closeQuietly(worldStmt); + AbstractJob.closeQuietly(insertWorldStatement); + } + } + + private static Yaml createYaml() { + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setDefaultFlowStyle(FlowStyle.FLOW); + Representer representer = new Representer(); + representer.setDefaultFlowStyle(FlowStyle.FLOW); + + // We have to use this in order to properly save non-string values + return new Yaml(new SafeConstructor(), new Representer(), options); + } + + ConfigurationManager getConfiguration() { + return this.config; + } + + Logger getLogger() { + return logger; + } + + Yaml getYaml() { + return yaml; + } + + int getWorldId() { + return worldId; + } + + private Connection getConnection() throws SQLException { + return connectionPool.getConnection(); + } + + @Override + public Map getRegions() { + return regions; + } + + @Override + public void setRegions(Map regions) { + this.regions = regions; + } + + @Override + protected void performLoad() throws ProtectionDatabaseException { + Connection connection = null; + try { + connection = getConnection(); + setRegions(new RegionLoader(this, connection, regions).call()); + } catch (SQLException e) { + throw new ProtectionDatabaseException("Failed to load regions database", e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException ignored) { + } + } + } + } + + @Override + protected void performSave() throws ProtectionDatabaseException { + Connection connection = null; + try { + connection = getConnection(); + new RegionCommitter(this, connection, getRegions()).call(); + } catch (SQLException e) { + throw new ProtectionDatabaseException("Failed to save regions database", e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException ignored) { + } + } + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/RegionCommitter.java b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/RegionCommitter.java new file mode 100644 index 00000000..4a13f7f2 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/RegionCommitter.java @@ -0,0 +1,652 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.protection.database.mysql; + +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; +import com.sk89q.worldguard.protection.databases.RegionDBUtil; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +class RegionCommitter extends AbstractJob implements Callable { + + private final Map regions; + private final int worldId; + + RegionCommitter(MySQLDatabaseImpl database, Connection conn, Map regions) { + super(database, conn); + checkNotNull(regions); + this.regions = regions; + this.worldId = database.getWorldId(); + } + + @Override + public Object call() throws SQLException, ProtectionDatabaseException { + + /* + * As we don't get notified on the creation/removal of regions: + * 1) We get a list of all of the in-database regions + * 2) We iterate over all of the in-memory regions + * 2a) If the region is in the database, we update the database and + * remove the region from the in-database list + * b) If the region is not in the database, we insert it + * 3) We iterate over what remains of the in-database list and remove + * them from the database + * + * TODO: Look at adding/removing/updating the database when the in + * memory region is created/remove/updated + * + * @see com.sk89q.worldguard.protection.databases.ProtectionDatabase#save() + */ + + List regionsInDatabase = new ArrayList(); + + PreparedStatement getAllRegionsStatement = null; + ResultSet getAllRegionsResult = null; + try { + getAllRegionsStatement = this.conn.prepareStatement( + "SELECT `region`.`id` FROM " + + "`" + config.sqlTablePrefix + "region` AS `region` " + + "WHERE `world_id` = ? " + ); + + getAllRegionsStatement.setInt(1, this.worldId); + getAllRegionsResult = getAllRegionsStatement.executeQuery(); + + while(getAllRegionsResult.next()) { + regionsInDatabase.add(getAllRegionsResult.getString("id")); + } + } catch (SQLException ex) { + logger.warning("Could not get region list for save comparison: " + ex.getMessage()); + } finally { + closeQuietly(getAllRegionsResult); + closeQuietly(getAllRegionsStatement); + } + + for (Map.Entry entry : regions.entrySet()) { + String name = entry.getKey(); + ProtectedRegion region = entry.getValue(); + + try { + if (regionsInDatabase.contains(name)) { + regionsInDatabase.remove(name); + + if (region instanceof ProtectedCuboidRegion) { + updateRegionCuboid( (ProtectedCuboidRegion) region ); + } else if (region instanceof ProtectedPolygonalRegion) { + updateRegionPoly2D( (ProtectedPolygonalRegion) region ); + } else if (region instanceof GlobalProtectedRegion) { + updateRegionGlobal( (GlobalProtectedRegion) region ); + } else { + this.updateRegion(region, region.getClass().getCanonicalName()); + } + } else { + if (region instanceof ProtectedCuboidRegion) { + insertRegionCuboid( (ProtectedCuboidRegion) region ); + } else if (region instanceof ProtectedPolygonalRegion) { + insertRegionPoly2D( (ProtectedPolygonalRegion) region ); + } else if (region instanceof GlobalProtectedRegion) { + insertRegionGlobal( (GlobalProtectedRegion) region ); + } else { + this.insertRegion(region, region.getClass().getCanonicalName()); + } + } + } catch (SQLException ex) { + logger.warning("Could not save region " + region.getId().toLowerCase() + ": " + ex.getMessage()); + throw new ProtectionDatabaseException(ex); + } + } + + for (Map.Entry entry : regions.entrySet()) { + PreparedStatement setParentStatement = null; + try { + if (entry.getValue().getParent() == null) continue; + + setParentStatement = this.conn.prepareStatement( + "UPDATE `" + config.sqlTablePrefix + "region` SET " + + "`parent` = ? " + + "WHERE `id` = ? AND `world_id` = " + this.worldId + ); + + setParentStatement.setString(1, entry.getValue().getParent().getId().toLowerCase()); + setParentStatement.setString(2, entry.getValue().getId().toLowerCase()); + + setParentStatement.execute(); + } catch (SQLException ex) { + logger.warning("Could not save region parents " + entry.getValue().getId().toLowerCase() + ": " + ex.getMessage()); + throw new ProtectionDatabaseException(ex); + } finally { + closeQuietly(setParentStatement); + } + } + + for (String name : regionsInDatabase) { + PreparedStatement removeRegion = null; + try { + removeRegion = this.conn.prepareStatement( + "DELETE FROM `" + config.sqlTablePrefix + "region` WHERE `id` = ? " + ); + + removeRegion.setString(1, name); + removeRegion.execute(); + } catch (SQLException ex) { + logger.warning("Could not remove region from database " + name + ": " + ex.getMessage()); + } finally { + closeQuietly(removeRegion); + } + } + + return null; + } + + /* + * Returns the database id for the user + * If it doesn't exits it adds the user and returns the id. + */ + private Map getUserIds(String... usernames) { + Map users = new HashMap(); + + if (usernames.length < 1) return users; + + ResultSet findUsersResults = null; + PreparedStatement insertUserStatement = null; + PreparedStatement findUsersStatement = null; + try { + findUsersStatement = this.conn.prepareStatement( + String.format( + "SELECT " + + "`user`.`id`, " + + "`user`.`name` " + + "FROM `" + config.sqlTablePrefix + "user` AS `user` " + + "WHERE `name` IN (%s)", + RegionDBUtil.preparePlaceHolders(usernames.length) + ) + ); + + RegionDBUtil.setValues(findUsersStatement, usernames); + + findUsersResults = findUsersStatement.executeQuery(); + + while(findUsersResults.next()) { + users.put(findUsersResults.getString("name"), findUsersResults.getInt("id")); + } + + insertUserStatement = this.conn.prepareStatement( + "INSERT INTO " + + "`" + config.sqlTablePrefix + "user` ( " + + "`id`, " + + "`name`" + + ") VALUES (null, ?)", + Statement.RETURN_GENERATED_KEYS + ); + + for (String username : usernames) { + if (!users.containsKey(username)) { + insertUserStatement.setString(1, username); + insertUserStatement.execute(); + ResultSet generatedKeys = insertUserStatement.getGeneratedKeys(); + if (generatedKeys.first()) { + users.put(username, generatedKeys.getInt(1)); + } else { + logger.warning("Could not get the database id for user " + username); + } + } + } + } catch (SQLException ex) { + ex.printStackTrace(); + logger.warning("Could not get the database id for the users " + usernames.toString() + "\n\t" + ex.getMessage()); + Throwable t = ex.getCause(); + while (t != null) { + logger.warning(t.getMessage()); + t = t.getCause(); + } + } finally { + closeQuietly(findUsersResults); + closeQuietly(findUsersStatement); + closeQuietly(insertUserStatement); + } + + return users; + } + + /* + * Returns the database id for the groups + * If it doesn't exits it adds the group and returns the id. + */ + private Map getGroupIds(String... groupnames) { + Map groups = new HashMap(); + + if (groupnames.length < 1) return groups; + + PreparedStatement findGroupsStatement = null; + ResultSet findGroupsResults = null; + PreparedStatement insertGroupStatement = null; + try { + findGroupsStatement = this.conn.prepareStatement( + String.format( + "SELECT " + + "`group`.`id`, " + + "`group`.`name` " + + "FROM `" + config.sqlTablePrefix + "group` AS `group` " + + "WHERE `name` IN (%s)", + RegionDBUtil.preparePlaceHolders(groupnames.length) + ) + ); + + RegionDBUtil.setValues(findGroupsStatement, groupnames); + + findGroupsResults = findGroupsStatement.executeQuery(); + + while(findGroupsResults.next()) { + groups.put(findGroupsResults.getString("name"), findGroupsResults.getInt("id")); + } + + insertGroupStatement = this.conn.prepareStatement( + "INSERT INTO " + + "`" + config.sqlTablePrefix + "group` ( " + + "`id`, " + + "`name`" + + ") VALUES (null, ?)", + Statement.RETURN_GENERATED_KEYS + ); + + for (String groupname : groupnames) { + if (!groups.containsKey(groupname)) { + insertGroupStatement.setString(1, groupname); + insertGroupStatement.execute(); + ResultSet generatedKeys = insertGroupStatement.getGeneratedKeys(); + if (generatedKeys.first()) { + groups.put(groupname, generatedKeys.getInt(1)); + } else { + logger.warning("Could not get the database id for user " + groupname); + } + } + } + } catch (SQLException ex) { + logger.warning("Could not get the database id for the groups " + groupnames.toString() + ex.getMessage()); + } finally { + closeQuietly(findGroupsResults); + closeQuietly(findGroupsStatement); + closeQuietly(insertGroupStatement); + } + + return groups; + } + + private void updateFlags(ProtectedRegion region) throws SQLException { + PreparedStatement clearCurrentFlagStatement = null; + try { + clearCurrentFlagStatement = this.conn.prepareStatement( + "DELETE FROM `" + config.sqlTablePrefix + "region_flag` " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + ); + + clearCurrentFlagStatement.setString(1, region.getId().toLowerCase()); + clearCurrentFlagStatement.execute(); + + for (Map.Entry, Object> entry : region.getFlags().entrySet()) { + if (entry.getValue() == null) continue; + + Object flag = sqlMarshal(marshalFlag(entry.getKey(), entry.getValue())); + + PreparedStatement insertFlagStatement = null; + try { + insertFlagStatement = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region_flag` ( " + + "`id`, " + + "`region_id`, " + + "`world_id`, " + + "`flag`, " + + "`value` " + + ") VALUES (null, ?, " + this.worldId + ", ?, ?)" + ); + + insertFlagStatement.setString(1, region.getId().toLowerCase()); + insertFlagStatement.setString(2, entry.getKey().getName()); + insertFlagStatement.setObject(3, flag); + + insertFlagStatement.execute(); + } finally { + closeQuietly(insertFlagStatement); + } + } + } finally { + closeQuietly(clearCurrentFlagStatement); + } + } + + private void updatePlayerAndGroups(ProtectedRegion region, Boolean owners) throws SQLException { + DefaultDomain domain; + + if (owners) { + domain = region.getOwners(); + } else { + domain = region.getMembers(); + } + + PreparedStatement deleteUsersForRegion = null; + PreparedStatement insertUsersForRegion = null; + PreparedStatement deleteGroupsForRegion = null; + PreparedStatement insertGroupsForRegion = null; + + try { + deleteUsersForRegion = this.conn.prepareStatement( + "DELETE FROM `" + config.sqlTablePrefix + "region_players` " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + " " + + "AND `owner` = ?" + ); + + deleteUsersForRegion.setString(1, region.getId().toLowerCase()); + deleteUsersForRegion.setBoolean(2, owners); + deleteUsersForRegion.execute(); + + insertUsersForRegion = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region_players` " + + "(`region_id`, `world_id`, `user_id`, `owner`) " + + "VALUES (?, " + this.worldId + ", ?, ?)" + ); + + Set var = domain.getPlayers(); + + for (Integer player : getUserIds(var.toArray(new String[var.size()])).values()) { + insertUsersForRegion.setString(1, region.getId().toLowerCase()); + insertUsersForRegion.setInt(2, player); + insertUsersForRegion.setBoolean(3, owners); + + insertUsersForRegion.execute(); + } + + deleteGroupsForRegion = this.conn.prepareStatement( + "DELETE FROM `" + config.sqlTablePrefix + "region_groups` " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + " " + + "AND `owner` = ?" + ); + + deleteGroupsForRegion.setString(1, region.getId().toLowerCase()); + deleteGroupsForRegion.setBoolean(2, owners); + deleteGroupsForRegion.execute(); + + insertGroupsForRegion = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region_groups` " + + "(`region_id`, `world_id`, `group_id`, `owner`) " + + "VALUES (?, " + this.worldId + ", ?, ?)" + ); + + Set groupVar = domain.getGroups(); + for (Integer group : getGroupIds(groupVar.toArray(new String[groupVar.size()])).values()) { + insertGroupsForRegion.setString(1, region.getId().toLowerCase()); + insertGroupsForRegion.setInt(2, group); + insertGroupsForRegion.setBoolean(3, owners); + + insertGroupsForRegion.execute(); + } + } finally { + closeQuietly(deleteGroupsForRegion); + closeQuietly(deleteUsersForRegion); + closeQuietly(insertGroupsForRegion); + closeQuietly(insertUsersForRegion); + } + } + + @SuppressWarnings("unchecked") + private Object marshalFlag(Flag flag, Object val) { + return flag.marshal( (V) val ); + } + + private void insertRegion(ProtectedRegion region, String type) throws SQLException { + PreparedStatement insertRegionStatement = null; + try { + insertRegionStatement = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region` (" + + "`id`, " + + "`world_id`, " + + "`type`, " + + "`priority`, " + + "`parent` " + + ") VALUES (?, ?, ?, ?, null)" + ); + + insertRegionStatement.setString(1, region.getId().toLowerCase()); + insertRegionStatement.setInt(2, this.worldId); + insertRegionStatement.setString(3, type); + insertRegionStatement.setInt(4, region.getPriority()); + + insertRegionStatement.execute(); + } finally { + closeQuietly(insertRegionStatement); + } + + updateFlags(region); + + updatePlayerAndGroups(region, false); + updatePlayerAndGroups(region, true); + } + + private void insertRegionCuboid(ProtectedCuboidRegion region) throws SQLException { + insertRegion(region, "cuboid"); + + PreparedStatement insertCuboidRegionStatement = null; + try { + insertCuboidRegionStatement = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region_cuboid` (" + + "`region_id`, " + + "`world_id`, " + + "`min_z`, " + + "`min_y`, " + + "`min_x`, " + + "`max_z`, " + + "`max_y`, " + + "`max_x` " + + ") VALUES (?, " + this.worldId + ", ?, ?, ?, ?, ?, ?)" + ); + + BlockVector min = region.getMinimumPoint(); + BlockVector max = region.getMaximumPoint(); + + insertCuboidRegionStatement.setString(1, region.getId().toLowerCase()); + insertCuboidRegionStatement.setInt(2, min.getBlockZ()); + insertCuboidRegionStatement.setInt(3, min.getBlockY()); + insertCuboidRegionStatement.setInt(4, min.getBlockX()); + insertCuboidRegionStatement.setInt(5, max.getBlockZ()); + insertCuboidRegionStatement.setInt(6, max.getBlockY()); + insertCuboidRegionStatement.setInt(7, max.getBlockX()); + + insertCuboidRegionStatement.execute(); + } finally { + closeQuietly(insertCuboidRegionStatement); + } + } + + private void insertRegionPoly2D(ProtectedPolygonalRegion region) throws SQLException { + insertRegion(region, "poly2d"); + + PreparedStatement insertPoly2dRegionStatement = null; + try { + insertPoly2dRegionStatement = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region_poly2d` (" + + "`region_id`, " + + "`world_id`, " + + "`max_y`, " + + "`min_y` " + + ") VALUES (?, " + this.worldId + ", ?, ?)" + ); + + insertPoly2dRegionStatement.setString(1, region.getId().toLowerCase()); + insertPoly2dRegionStatement.setInt(2, region.getMaximumPoint().getBlockY()); + insertPoly2dRegionStatement.setInt(3, region.getMinimumPoint().getBlockY()); + + insertPoly2dRegionStatement.execute(); + } finally { + closeQuietly(insertPoly2dRegionStatement); + } + + updatePoly2dPoints(region); + } + + private void updatePoly2dPoints(ProtectedPolygonalRegion region) throws SQLException { + PreparedStatement clearPoly2dPointsForRegionStatement = null; + PreparedStatement insertPoly2dPointStatement = null; + + try { + clearPoly2dPointsForRegionStatement = this.conn.prepareStatement( + "DELETE FROM `" + config.sqlTablePrefix + "region_poly2d_point` " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + ); + + clearPoly2dPointsForRegionStatement.setString(1, region.getId().toLowerCase()); + + clearPoly2dPointsForRegionStatement.execute(); + + insertPoly2dPointStatement = this.conn.prepareStatement( + "INSERT INTO `" + config.sqlTablePrefix + "region_poly2d_point` (" + + "`id`, " + + "`region_id`, " + + "`world_id`, " + + "`z`, " + + "`x` " + + ") VALUES (null, ?, " + this.worldId + ", ?, ?)" + ); + + String lowerId = region.getId().toLowerCase(); + for (BlockVector2D point : region.getPoints()) { + insertPoly2dPointStatement.setString(1, lowerId); + insertPoly2dPointStatement.setInt(2, point.getBlockZ()); + insertPoly2dPointStatement.setInt(3, point.getBlockX()); + + insertPoly2dPointStatement.execute(); + } + } finally { + closeQuietly(clearPoly2dPointsForRegionStatement); + closeQuietly(insertPoly2dPointStatement); + } + } + + private void insertRegionGlobal(GlobalProtectedRegion region) throws SQLException { + insertRegion(region, "global"); + } + + private void updateRegion(ProtectedRegion region, String type) throws SQLException { + PreparedStatement updateRegionStatement = null; + try { + updateRegionStatement = this.conn.prepareStatement( + "UPDATE `" + config.sqlTablePrefix + "region` SET " + + "`priority` = ? WHERE `id` = ? AND `world_id` = " + this.worldId + ); + + updateRegionStatement.setInt(1, region.getPriority()); + updateRegionStatement.setString(2, region.getId().toLowerCase()); + + updateRegionStatement.execute(); + } finally { + closeQuietly(updateRegionStatement); + } + + updateFlags(region); + + updatePlayerAndGroups(region, false); + updatePlayerAndGroups(region, true); + } + + private void updateRegionCuboid(ProtectedCuboidRegion region) throws SQLException { + updateRegion(region, "cuboid"); + + PreparedStatement updateCuboidRegionStatement = null; + try { + updateCuboidRegionStatement = this.conn.prepareStatement( + "UPDATE `" + config.sqlTablePrefix + "region_cuboid` SET " + + "`min_z` = ?, " + + "`min_y` = ?, " + + "`min_x` = ?, " + + "`max_z` = ?, " + + "`max_y` = ?, " + + "`max_x` = ? " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + ); + + BlockVector min = region.getMinimumPoint(); + BlockVector max = region.getMaximumPoint(); + + updateCuboidRegionStatement.setInt(1, min.getBlockZ()); + updateCuboidRegionStatement.setInt(2, min.getBlockY()); + updateCuboidRegionStatement.setInt(3, min.getBlockX()); + updateCuboidRegionStatement.setInt(4, max.getBlockZ()); + updateCuboidRegionStatement.setInt(5, max.getBlockY()); + updateCuboidRegionStatement.setInt(6, max.getBlockX()); + updateCuboidRegionStatement.setString(7, region.getId().toLowerCase()); + + updateCuboidRegionStatement.execute(); + } finally { + closeQuietly(updateCuboidRegionStatement); + } + } + + private void updateRegionPoly2D(ProtectedPolygonalRegion region) throws SQLException { + updateRegion(region, "poly2d"); + + PreparedStatement updatePoly2dRegionStatement = null; + try { + updatePoly2dRegionStatement = this.conn.prepareStatement( + "UPDATE `" + config.sqlTablePrefix + "region_poly2d` SET " + + "`max_y` = ?, " + + "`min_y` = ? " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + ); + + updatePoly2dRegionStatement.setInt(1, region.getMaximumPoint().getBlockY()); + updatePoly2dRegionStatement.setInt(2, region.getMinimumPoint().getBlockY()); + updatePoly2dRegionStatement.setString(3, region.getId().toLowerCase()); + + updatePoly2dRegionStatement.execute(); + } finally { + closeQuietly(updatePoly2dRegionStatement); + } + updatePoly2dPoints(region); + } + + private void updateRegionGlobal(GlobalProtectedRegion region) throws SQLException { + updateRegion(region, "global"); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/RegionLoader.java b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/RegionLoader.java new file mode 100644 index 00000000..59ad301d --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/internal/protection/database/mysql/RegionLoader.java @@ -0,0 +1,446 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.protection.database.mysql; + +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.DefaultFlag; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +class RegionLoader extends AbstractJob implements Callable> { + + private final Map regions; + private final int worldId; + + private Map cuboidRegions; + private Map poly2dRegions; + private Map globalRegions; + private Map parentSets; + + RegionLoader(MySQLDatabaseImpl database, Connection conn, Map regions) { + super(database, conn); + checkNotNull(regions); + this.regions = regions; + this.worldId = database.getWorldId(); + } + + @Override + public Map call() { + parentSets = new HashMap(); + + // We load the cuboid regions first, as this is likely to be the + // largest dataset. This should save time in regards to the putAll()s + this.loadCuboid(); + Map regions = this.cuboidRegions; + this.cuboidRegions = null; + + this.loadPoly2d(); + regions.putAll(this.poly2dRegions); + this.poly2dRegions = null; + + this.loadGlobal(); + regions.putAll(this.globalRegions); + this.globalRegions = null; + + // Relink parents // Taken verbatim from YAMLDatabase + for (Map.Entry entry : parentSets.entrySet()) { + ProtectedRegion parent = regions.get(entry.getValue()); + if (parent != null) { + try { + entry.getKey().setParent(parent); + } catch (CircularInheritanceException e) { + logger.warning("Circular inheritance detect with '" + + entry.getValue() + "' detected as a parent"); + } + } else { + logger.warning("Unknown region parent: " + entry.getValue()); + } + } + + return regions; + } + + private void loadFlags(ProtectedRegion region) { + // @TODO: Iterate _ONCE_ + PreparedStatement flagsStatement = null; + ResultSet flagsResultSet = null; + try { + flagsStatement = this.conn.prepareStatement( + "SELECT " + + "`region_flag`.`flag`, " + + "`region_flag`.`value` " + + "FROM `" + config.sqlTablePrefix + "region_flag` AS `region_flag` " + + "WHERE `region_id` = ? " + + "AND `world_id` = " + this.worldId + ); + + flagsStatement.setString(1, region.getId().toLowerCase()); + flagsResultSet = flagsStatement.executeQuery(); + + Map regionFlags = new HashMap(); + while (flagsResultSet.next()) { + regionFlags.put( + flagsResultSet.getString("flag"), + sqlUnmarshal(flagsResultSet.getString("value")) + ); + } + + // @TODO: Make this better + for (Flag flag : DefaultFlag.getFlags()) { + Object o = regionFlags.get(flag.getName()); + if (o != null) { + setFlag(region, flag, o); + } + } + } catch (SQLException ex) { + logger.warning( + "Unable to load flags for region " + + region.getId().toLowerCase() + ": " + ex.getMessage() + ); + } finally { + closeQuietly(flagsResultSet); + closeQuietly(flagsStatement); + } + } + + private void setFlag(ProtectedRegion region, Flag flag, Object rawValue) { + T val = flag.unmarshal(rawValue); + if (val == null) { + logger.warning("Failed to parse flag '" + flag.getName() + + "' with value '" + rawValue.toString() + "'"); + return; + } + region.setFlag(flag, val); + } + + private void loadOwnersAndMembers(ProtectedRegion region) { + DefaultDomain owners = new DefaultDomain(); + DefaultDomain members = new DefaultDomain(); + + ResultSet userSet = null; + PreparedStatement usersStatement = null; + try { + usersStatement = this.conn.prepareStatement( + "SELECT " + + "`user`.`name`, " + + "`region_players`.`owner` " + + "FROM `" + config.sqlTablePrefix + "region_players` AS `region_players` " + + "LEFT JOIN `" + config.sqlTablePrefix + "user` AS `user` ON ( " + + "`region_players`.`user_id` = " + + "`user`.`id`) " + + "WHERE `region_players`.`region_id` = ? " + + "AND `region_players`.`world_id` = " + this.worldId + ); + + usersStatement.setString(1, region.getId().toLowerCase()); + userSet = usersStatement.executeQuery(); + while (userSet.next()) { + if (userSet.getBoolean("owner")) { + owners.addPlayer(userSet.getString("name")); + } else { + members.addPlayer(userSet.getString("name")); + } + } + } catch (SQLException ex) { + logger.warning("Unable to load users for region " + region.getId().toLowerCase() + ": " + ex.getMessage()); + } finally { + closeQuietly(userSet); + closeQuietly(usersStatement); + } + + PreparedStatement groupsStatement = null; + ResultSet groupSet = null; + try { + groupsStatement = this.conn.prepareStatement( + "SELECT " + + "`group`.`name`, " + + "`region_groups`.`owner` " + + "FROM `" + config.sqlTablePrefix + "region_groups` AS `region_groups` " + + "LEFT JOIN `" + config.sqlTablePrefix + "group` AS `group` ON ( " + + "`region_groups`.`group_id` = " + + "`group`.`id`) " + + "WHERE `region_groups`.`region_id` = ? " + + "AND `region_groups`.`world_id` = " + this.worldId + ); + + groupsStatement.setString(1, region.getId().toLowerCase()); + groupSet = groupsStatement.executeQuery(); + while (groupSet.next()) { + if (groupSet.getBoolean("owner")) { + owners.addGroup(groupSet.getString("name")); + } else { + members.addGroup(groupSet.getString("name")); + } + } + } catch (SQLException ex) { + logger.warning("Unable to load groups for region " + region.getId().toLowerCase() + ": " + ex.getMessage()); + } finally { + closeQuietly(groupSet); + closeQuietly(groupsStatement); + } + + region.setOwners(owners); + region.setMembers(members); + } + + private void loadGlobal() { + Map regions = + new HashMap(); + + PreparedStatement globalRegionStatement = null; + ResultSet globalResultSet = null; + try { + globalRegionStatement = this.conn.prepareStatement( + "SELECT " + + "`region`.`id`, " + + "`region`.`priority`, " + + "`parent`.`id` AS `parent` " + + "FROM `" + config.sqlTablePrefix + "region` AS `region` " + + "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + + "ON (`region`.`parent` = `parent`.`id` " + + "AND `region`.`world_id` = `parent`.`world_id`) " + + "WHERE `region`.`type` = 'global' " + + "AND `region`.`world_id` = ? " + ); + + globalRegionStatement.setInt(1, this.worldId); + globalResultSet = globalRegionStatement.executeQuery(); + + while (globalResultSet.next()) { + ProtectedRegion region = new GlobalProtectedRegion(globalResultSet.getString("id")); + + region.setPriority(globalResultSet.getInt("priority")); + + this.loadFlags(region); + this.loadOwnersAndMembers(region); + + regions.put(globalResultSet.getString("id"), region); + + String parentId = globalResultSet.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } catch (SQLException ex) { + ex.printStackTrace(); + logger.warning("Unable to load regions from sql database: " + ex.getMessage()); + Throwable t = ex.getCause(); + while (t != null) { + logger.warning("\t\tCause: " + t.getMessage()); + t = t.getCause(); + } + } finally { + closeQuietly(globalResultSet); + closeQuietly(globalRegionStatement); + } + + globalRegions = regions; + } + + private void loadCuboid() { + Map regions = new HashMap(); + + PreparedStatement cuboidRegionStatement = null; + ResultSet cuboidResultSet = null; + try { + cuboidRegionStatement = this.conn.prepareStatement( + "SELECT " + + "`region_cuboid`.`min_z`, " + + "`region_cuboid`.`min_y`, " + + "`region_cuboid`.`min_x`, " + + "`region_cuboid`.`max_z`, " + + "`region_cuboid`.`max_y`, " + + "`region_cuboid`.`max_x`, " + + "`region`.`id`, " + + "`region`.`priority`, " + + "`parent`.`id` AS `parent` " + + "FROM `" + config.sqlTablePrefix + "region_cuboid` AS `region_cuboid` " + + "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `region` " + + "ON (`region_cuboid`.`region_id` = `region`.`id` " + + "AND `region_cuboid`.`world_id` = `region`.`world_id`) " + + "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + + "ON (`region`.`parent` = `parent`.`id` " + + "AND `region`.`world_id` = `parent`.`world_id`) " + + "WHERE `region`.`world_id` = ? " + ); + + cuboidRegionStatement.setInt(1, this.worldId); + cuboidResultSet = cuboidRegionStatement.executeQuery(); + + while (cuboidResultSet.next()) { + Vector pt1 = new Vector( + cuboidResultSet.getInt("min_x"), + cuboidResultSet.getInt("min_y"), + cuboidResultSet.getInt("min_z") + ); + Vector pt2 = new Vector( + cuboidResultSet.getInt("max_x"), + cuboidResultSet.getInt("max_y"), + cuboidResultSet.getInt("max_z") + ); + + BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); + BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); + ProtectedRegion region = new ProtectedCuboidRegion( + cuboidResultSet.getString("id"), + min, + max + ); + + region.setPriority(cuboidResultSet.getInt("priority")); + + this.loadFlags(region); + this.loadOwnersAndMembers(region); + + regions.put(cuboidResultSet.getString("id"), region); + + String parentId = cuboidResultSet.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + + } catch (SQLException ex) { + ex.printStackTrace(); + logger.warning("Unable to load regions from sql database: " + ex.getMessage()); + Throwable t = ex.getCause(); + while (t != null) { + logger.warning("\t\tCause: " + t.getMessage()); + t = t.getCause(); + } + } finally { + closeQuietly(cuboidResultSet); + closeQuietly(cuboidRegionStatement); + } + + cuboidRegions = regions; + } + + private void loadPoly2d() { + Map regions = new HashMap(); + + PreparedStatement poly2dRegionStatement = null; + ResultSet poly2dResultSet = null; + PreparedStatement poly2dVectorStatement = null; + try { + poly2dRegionStatement = this.conn.prepareStatement( + "SELECT " + + "`region_poly2d`.`min_y`, " + + "`region_poly2d`.`max_y`, " + + "`region`.`id`, " + + "`region`.`priority`, " + + "`parent`.`id` AS `parent` " + + "FROM `" + config.sqlTablePrefix + "region_poly2d` AS `region_poly2d` " + + "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `region` " + + "ON (`region_poly2d`.`region_id` = `region`.`id` " + + "AND `region_poly2d`.`world_id` = `region`.`world_id`) " + + "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + + "ON (`region`.`parent` = `parent`.`id` " + + "AND `region`.`world_id` = `parent`.`world_id`) " + + "WHERE `region`.`world_id` = ? " + ); + + poly2dRegionStatement.setInt(1, this.worldId); + poly2dResultSet = poly2dRegionStatement.executeQuery(); + + poly2dVectorStatement = this.conn.prepareStatement( + "SELECT " + + "`region_poly2d_point`.`x`, " + + "`region_poly2d_point`.`z` " + + "FROM `" + config.sqlTablePrefix + "region_poly2d_point` AS `region_poly2d_point` " + + "WHERE `region_poly2d_point`.`region_id` = ? " + + "AND `region_poly2d_point`.`world_id` = " + this.worldId + ); + + while (poly2dResultSet.next()) { + String id = poly2dResultSet.getString("id"); + + Integer minY = poly2dResultSet.getInt("min_y"); + Integer maxY = poly2dResultSet.getInt("max_y"); + List points = new ArrayList(); + + poly2dVectorStatement.setString(1, id); + ResultSet poly2dVectorResultSet = poly2dVectorStatement.executeQuery(); + + while (poly2dVectorResultSet.next()) { + points.add(new BlockVector2D( + poly2dVectorResultSet.getInt("x"), + poly2dVectorResultSet.getInt("z") + )); + } + + if (points.size() < 3) { + logger.warning(String.format("Invalid polygonal region '%s': region only has %d point(s). Ignoring.", id, points.size())); + continue; + } + + closeQuietly(poly2dVectorResultSet); + + ProtectedRegion region = new ProtectedPolygonalRegion(id, points, minY, maxY); + + region.setPriority(poly2dResultSet.getInt("priority")); + + this.loadFlags(region); + this.loadOwnersAndMembers(region); + + regions.put(poly2dResultSet.getString("id"), region); + + String parentId = poly2dResultSet.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } catch (SQLException ex) { + ex.printStackTrace(); + logger.warning("Unable to load regions from sql database: " + ex.getMessage()); + Throwable t = ex.getCause(); + while (t != null) { + logger.warning("\t\tCause: " + t.getMessage()); + t = t.getCause(); + } + } finally { + closeQuietly(poly2dResultSet); + closeQuietly(poly2dRegionStatement); + closeQuietly(poly2dVectorStatement); + } + + poly2dRegions = regions; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java index 3bd7a8ab..0eac245d 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java +++ b/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java @@ -43,7 +43,7 @@ * asynchronously loading and saving region data while only allowing one * single operation (either load or save) occurring at a given time. */ -abstract class AbstractAsynchronousDatabase extends AbstractProtectionDatabase { +public abstract class AbstractAsynchronousDatabase extends AbstractProtectionDatabase { private static final Logger log = Logger.getLogger(AbstractAsynchronousDatabase.class.getName()); diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java index ff5470da..e237a22e 100755 --- a/src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java +++ b/src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java @@ -20,1171 +20,15 @@ package com.sk89q.worldguard.protection.databases; -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.BlockVector2D; -import com.sk89q.worldedit.Vector; import com.sk89q.worldguard.bukkit.ConfigurationManager; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.flags.DefaultFlag; -import com.sk89q.worldguard.protection.flags.Flag; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.DumperOptions.FlowStyle; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.SafeConstructor; -import org.yaml.snakeyaml.error.YAMLException; -import org.yaml.snakeyaml.representer.Representer; +import com.sk89q.worldguard.internal.protection.database.mysql.MySQLDatabaseImpl; -import java.sql.*; -import java.util.*; -import java.util.logging.Level; import java.util.logging.Logger; -public class MySQLDatabase extends AbstractProtectionDatabase { - private final Logger logger; +public class MySQLDatabase extends MySQLDatabaseImpl { - private Yaml yaml; - - private Map regions; - - private Map cuboidRegions; - private Map poly2dRegions; - private Map globalRegions; - private Map parentSets; - - private final ConfigurationManager config; - - private Connection conn; - private int worldDbId = -1; // The database will never have an id of -1; - - public MySQLDatabase(ConfigurationManager config, String world, Logger logger) throws ProtectionDatabaseException { - this.config = config; - String world1 = world; - this.logger = logger; - - PreparedStatement worldStmt = null; - ResultSet worldResult = null; - PreparedStatement insertWorldStatement = null; - try { - connect(); - - PreparedStatement verTest = null; - try { - // Test if the database is up to date, if not throw a critical error - verTest = this.conn.prepareStatement( - "SELECT `world_id` FROM `" + config.sqlTablePrefix + "region_cuboid` LIMIT 0,1;" - ); - verTest.execute(); - } catch (SQLException ex) { - throw new InvalidTableFormatException( - "region_storage_update_20110325.sql" - ); - } finally { - closeResource(verTest); - } - - worldStmt = conn.prepareStatement( - "SELECT `id` FROM " + - "`" + config.sqlTablePrefix + "world` " + - "WHERE `name` = ? LIMIT 0,1" - ); - - worldStmt.setString(1, world1); - worldResult = worldStmt.executeQuery(); - - if (worldResult.first()) { - this.worldDbId = worldResult.getInt("id"); - } else { - insertWorldStatement = this.conn.prepareStatement( - "INSERT INTO " + - "`" + config.sqlTablePrefix + "world` " + - "(`id`, `name`) VALUES (null, ?)", - Statement.RETURN_GENERATED_KEYS - ); - - insertWorldStatement.setString(1, world); - insertWorldStatement.execute(); - ResultSet generatedKeys = insertWorldStatement.getGeneratedKeys(); - if (generatedKeys.first()) { - this.worldDbId = generatedKeys.getInt(1); - } - } - } catch (SQLException ex) { - logger.log(Level.SEVERE, ex.getMessage(), ex); - // We havn't connected to the databases, or there was an error - // initialising the world record, so there is no point continuing - return; - } finally { - closeResource(worldResult); - closeResource(worldStmt); - closeResource(insertWorldStatement); - } - - if (this.worldDbId <= 0) { - logger.log(Level.SEVERE, "Could not find or create the world"); - // There was an error initialising the world record, so there is - // no point continuing - return; - } - - DumperOptions options = new DumperOptions(); - options.setIndent(2); - options.setDefaultFlowStyle(FlowStyle.FLOW); - Representer representer = new Representer(); - representer.setDefaultFlowStyle(FlowStyle.FLOW); - - // We have to use this in order to properly save non-string values - yaml = new Yaml(new SafeConstructor(), new Representer(), options); + public MySQLDatabase(ConfigurationManager config, String worldName, Logger logger) throws ProtectionDatabaseException { + super(config, worldName, logger); } - private void connect() throws SQLException { - if (conn != null) { - // Check if the connection is still alive/valid. - try { - conn.isValid(2); - } catch (SQLException ex) { - // Test if validation failed because the connection is dead, - // and if it is mark the connection as closed (the MySQL Driver - // does not ensure that the connection is marked as closed unless - // the close() method has been called. - if ("08S01".equals(ex.getSQLState())) { - conn.close(); - } - } - } - if (conn == null || conn.isClosed()) { - conn = DriverManager.getConnection(config.sqlDsn, config.sqlUsername, config.sqlPassword); - } - } - - private void loadFlags(ProtectedRegion region) { - // @TODO: Iterate _ONCE_ - PreparedStatement flagsStatement = null; - ResultSet flagsResultSet = null; - try { - flagsStatement = this.conn.prepareStatement( - "SELECT " + - "`region_flag`.`flag`, " + - "`region_flag`.`value` " + - "FROM `" + config.sqlTablePrefix + "region_flag` AS `region_flag` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId - ); - - flagsStatement.setString(1, region.getId().toLowerCase()); - flagsResultSet = flagsStatement.executeQuery(); - - Map regionFlags = new HashMap(); - while (flagsResultSet.next()) { - regionFlags.put( - flagsResultSet.getString("flag"), - sqlUnmarshal(flagsResultSet.getString("value")) - ); - } - - // @TODO: Make this better - for (Flag flag : DefaultFlag.getFlags()) { - Object o = regionFlags.get(flag.getName()); - if (o != null) { - setFlag(region, flag, o); - } - } - } catch (SQLException ex) { - logger.warning( - "Unable to load flags for region " - + region.getId().toLowerCase() + ": " + ex.getMessage() - ); - } finally { - closeResource(flagsResultSet); - closeResource(flagsStatement); - } - } - - private void setFlag(ProtectedRegion region, Flag flag, Object rawValue) { - T val = flag.unmarshal(rawValue); - if (val == null) { - logger.warning("Failed to parse flag '" + flag.getName() - + "' with value '" + rawValue.toString() + "'"); - return; - } - region.setFlag(flag, val); - } - - private void loadOwnersAndMembers(ProtectedRegion region) { - DefaultDomain owners = new DefaultDomain(); - DefaultDomain members = new DefaultDomain(); - - ResultSet userSet = null; - PreparedStatement usersStatement = null; - try { - usersStatement = this.conn.prepareStatement( - "SELECT " + - "`user`.`name`, " + - "`region_players`.`owner` " + - "FROM `" + config.sqlTablePrefix + "region_players` AS `region_players` " + - "LEFT JOIN `" + config.sqlTablePrefix + "user` AS `user` ON ( " + - "`region_players`.`user_id` = " + - "`user`.`id`) " + - "WHERE `region_players`.`region_id` = ? " + - "AND `region_players`.`world_id` = " + this.worldDbId - ); - - usersStatement.setString(1, region.getId().toLowerCase()); - userSet = usersStatement.executeQuery(); - while(userSet.next()) { - if (userSet.getBoolean("owner")) { - owners.addPlayer(userSet.getString("name")); - } else { - members.addPlayer(userSet.getString("name")); - } - } - } catch (SQLException ex) { - logger.warning("Unable to load users for region " + region.getId().toLowerCase() + ": " + ex.getMessage()); - } finally { - closeResource(userSet); - closeResource(usersStatement); - } - - PreparedStatement groupsStatement = null; - ResultSet groupSet = null; - try { - groupsStatement = this.conn.prepareStatement( - "SELECT " + - "`group`.`name`, " + - "`region_groups`.`owner` " + - "FROM `" + config.sqlTablePrefix + "region_groups` AS `region_groups` " + - "LEFT JOIN `" + config.sqlTablePrefix + "group` AS `group` ON ( " + - "`region_groups`.`group_id` = " + - "`group`.`id`) " + - "WHERE `region_groups`.`region_id` = ? " + - "AND `region_groups`.`world_id` = " + this.worldDbId - ); - - groupsStatement.setString(1, region.getId().toLowerCase()); - groupSet = groupsStatement.executeQuery(); - while(groupSet.next()) { - if (groupSet.getBoolean("owner")) { - owners.addGroup(groupSet.getString("name")); - } else { - members.addGroup(groupSet.getString("name")); - } - } - } catch (SQLException ex) { - logger.warning("Unable to load groups for region " + region.getId().toLowerCase() + ": " + ex.getMessage()); - } finally { - closeResource(groupSet); - closeResource(groupsStatement); - } - - region.setOwners(owners); - region.setMembers(members); - } - - private void loadGlobal() { - Map regions = - new HashMap(); - - PreparedStatement globalRegionStatement = null; - ResultSet globalResultSet = null; - try { - globalRegionStatement = this.conn.prepareStatement( - "SELECT " + - "`region`.`id`, " + - "`region`.`priority`, " + - "`parent`.`id` AS `parent` " + - "FROM `" + config.sqlTablePrefix + "region` AS `region` " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + - "ON (`region`.`parent` = `parent`.`id` " + - "AND `region`.`world_id` = `parent`.`world_id`) " + - "WHERE `region`.`type` = 'global' " + - "AND `region`.`world_id` = ? " - ); - - globalRegionStatement.setInt(1, this.worldDbId); - globalResultSet = globalRegionStatement.executeQuery(); - - while (globalResultSet.next()) { - ProtectedRegion region = new GlobalProtectedRegion(globalResultSet.getString("id")); - - region.setPriority(globalResultSet.getInt("priority")); - - this.loadFlags(region); - this.loadOwnersAndMembers(region); - - regions.put(globalResultSet.getString("id"), region); - - String parentId = globalResultSet.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Unable to load regions from sql database: " + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning("\t\tCause: " + t.getMessage()); - t = t.getCause(); - } - } finally { - closeResource(globalResultSet); - closeResource(globalRegionStatement); - } - - globalRegions = regions; - } - - private void loadCuboid() { - Map regions = - new HashMap(); - - PreparedStatement cuboidRegionStatement = null; - ResultSet cuboidResultSet = null; - try { - cuboidRegionStatement = this.conn.prepareStatement( - "SELECT " + - "`region_cuboid`.`min_z`, " + - "`region_cuboid`.`min_y`, " + - "`region_cuboid`.`min_x`, " + - "`region_cuboid`.`max_z`, " + - "`region_cuboid`.`max_y`, " + - "`region_cuboid`.`max_x`, " + - "`region`.`id`, " + - "`region`.`priority`, " + - "`parent`.`id` AS `parent` " + - "FROM `" + config.sqlTablePrefix + "region_cuboid` AS `region_cuboid` " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `region` " + - "ON (`region_cuboid`.`region_id` = `region`.`id` " + - "AND `region_cuboid`.`world_id` = `region`.`world_id`) " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + - "ON (`region`.`parent` = `parent`.`id` " + - "AND `region`.`world_id` = `parent`.`world_id`) " + - "WHERE `region`.`world_id` = ? " - ); - - cuboidRegionStatement.setInt(1, this.worldDbId); - cuboidResultSet = cuboidRegionStatement.executeQuery(); - - while (cuboidResultSet.next()) { - Vector pt1 = new Vector( - cuboidResultSet.getInt("min_x"), - cuboidResultSet.getInt("min_y"), - cuboidResultSet.getInt("min_z") - ); - Vector pt2 = new Vector( - cuboidResultSet.getInt("max_x"), - cuboidResultSet.getInt("max_y"), - cuboidResultSet.getInt("max_z") - ); - - BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); - BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); - ProtectedRegion region = new ProtectedCuboidRegion( - cuboidResultSet.getString("id"), - min, - max - ); - - region.setPriority(cuboidResultSet.getInt("priority")); - - this.loadFlags(region); - this.loadOwnersAndMembers(region); - - regions.put(cuboidResultSet.getString("id"), region); - - String parentId = cuboidResultSet.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } - - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Unable to load regions from sql database: " + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning("\t\tCause: " + t.getMessage()); - t = t.getCause(); - } - } finally { - closeResource(cuboidResultSet); - closeResource(cuboidRegionStatement); - } - - cuboidRegions = regions; - } - - private void loadPoly2d() { - Map regions = - new HashMap(); - - PreparedStatement poly2dRegionStatement = null; - ResultSet poly2dResultSet = null; - PreparedStatement poly2dVectorStatement = null; - try { - poly2dRegionStatement = this.conn.prepareStatement( - "SELECT " + - "`region_poly2d`.`min_y`, " + - "`region_poly2d`.`max_y`, " + - "`region`.`id`, " + - "`region`.`priority`, " + - "`parent`.`id` AS `parent` " + - "FROM `" + config.sqlTablePrefix + "region_poly2d` AS `region_poly2d` " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `region` " + - "ON (`region_poly2d`.`region_id` = `region`.`id` " + - "AND `region_poly2d`.`world_id` = `region`.`world_id`) " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + - "ON (`region`.`parent` = `parent`.`id` " + - "AND `region`.`world_id` = `parent`.`world_id`) " + - "WHERE `region`.`world_id` = ? " - ); - - poly2dRegionStatement.setInt(1, this.worldDbId); - poly2dResultSet = poly2dRegionStatement.executeQuery(); - - poly2dVectorStatement = this.conn.prepareStatement( - "SELECT " + - "`region_poly2d_point`.`x`, " + - "`region_poly2d_point`.`z` " + - "FROM `" + config.sqlTablePrefix + "region_poly2d_point` AS `region_poly2d_point` " + - "WHERE `region_poly2d_point`.`region_id` = ? " + - "AND `region_poly2d_point`.`world_id` = " + this.worldDbId - ); - - while (poly2dResultSet.next()) { - String id = poly2dResultSet.getString("id"); - - Integer minY = poly2dResultSet.getInt("min_y"); - Integer maxY = poly2dResultSet.getInt("max_y"); - List points = new ArrayList(); - - poly2dVectorStatement.setString(1, id); - ResultSet poly2dVectorResultSet = poly2dVectorStatement.executeQuery(); - - while(poly2dVectorResultSet.next()) { - points.add(new BlockVector2D( - poly2dVectorResultSet.getInt("x"), - poly2dVectorResultSet.getInt("z") - )); - } - - if (points.size() < 3) { - logger.warning(String.format("Invalid polygonal region '%s': region only has %d point(s). Ignoring.", id, points.size())); - continue; - } - - closeResource(poly2dVectorResultSet); - - ProtectedRegion region = new ProtectedPolygonalRegion(id, points, minY, maxY); - - region.setPriority(poly2dResultSet.getInt("priority")); - - this.loadFlags(region); - this.loadOwnersAndMembers(region); - - regions.put(poly2dResultSet.getString("id"), region); - - String parentId = poly2dResultSet.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Unable to load regions from sql database: " + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning("\t\tCause: " + t.getMessage()); - t = t.getCause(); - } - } finally { - closeResource(poly2dResultSet); - closeResource(poly2dRegionStatement); - closeResource(poly2dVectorStatement); - } - - poly2dRegions = regions; - } - - @Override - public void load() throws ProtectionDatabaseException { - try { - connect(); - } catch (SQLException ex) { - throw new ProtectionDatabaseException(ex); - } - - parentSets = new HashMap(); - - // We load the cuboid regions first, as this is likely to be the - // largest dataset. This should save time in regards to the putAll()s - this.loadCuboid(); - Map regions = this.cuboidRegions; - this.cuboidRegions = null; - - this.loadPoly2d(); - regions.putAll(this.poly2dRegions); - this.poly2dRegions = null; - - this.loadGlobal(); - regions.putAll(this.globalRegions); - this.globalRegions = null; - - // Relink parents // Taken verbatim from YAMLDatabase - for (Map.Entry entry : parentSets.entrySet()) { - ProtectedRegion parent = regions.get(entry.getValue()); - if (parent != null) { - try { - entry.getKey().setParent(parent); - } catch (CircularInheritanceException e) { - logger.warning("Circular inheritance detect with '" - + entry.getValue() + "' detected as a parent"); - } - } else { - logger.warning("Unknown region parent: " + entry.getValue()); - } - } - - this.regions = regions; - } - - - /* - * Returns the database id for the user - * If it doesn't exits it adds the user and returns the id. - */ - private Map getUserIds(String... usernames) { - Map users = new HashMap(); - - if (usernames.length < 1) return users; - - ResultSet findUsersResults = null; - PreparedStatement insertUserStatement = null; - PreparedStatement findUsersStatement = null; - try { - findUsersStatement = this.conn.prepareStatement( - String.format( - "SELECT " + - "`user`.`id`, " + - "`user`.`name` " + - "FROM `" + config.sqlTablePrefix + "user` AS `user` " + - "WHERE `name` IN (%s)", - RegionDBUtil.preparePlaceHolders(usernames.length) - ) - ); - - RegionDBUtil.setValues(findUsersStatement, usernames); - - findUsersResults = findUsersStatement.executeQuery(); - - while(findUsersResults.next()) { - users.put(findUsersResults.getString("name"), findUsersResults.getInt("id")); - } - - insertUserStatement = this.conn.prepareStatement( - "INSERT INTO " + - "`" + config.sqlTablePrefix + "user` ( " + - "`id`, " + - "`name`" + - ") VALUES (null, ?)", - Statement.RETURN_GENERATED_KEYS - ); - - for (String username : usernames) { - if (!users.containsKey(username)) { - insertUserStatement.setString(1, username); - insertUserStatement.execute(); - ResultSet generatedKeys = insertUserStatement.getGeneratedKeys(); - if (generatedKeys.first()) { - users.put(username, generatedKeys.getInt(1)); - } else { - logger.warning("Could not get the database id for user " + username); - } - } - } - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Could not get the database id for the users " + usernames.toString() + "\n\t" + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning(t.getMessage()); - t = t.getCause(); - } - } finally { - closeResource(findUsersResults); - closeResource(findUsersStatement); - closeResource(insertUserStatement); - } - - return users; - } - - - /* - * Returns the database id for the groups - * If it doesn't exits it adds the group and returns the id. - */ - private Map getGroupIds(String... groupnames) { - Map groups = new HashMap(); - - if (groupnames.length < 1) return groups; - - PreparedStatement findGroupsStatement = null; - ResultSet findGroupsResults = null; - PreparedStatement insertGroupStatement = null; - try { - findGroupsStatement = this.conn.prepareStatement( - String.format( - "SELECT " + - "`group`.`id`, " + - "`group`.`name` " + - "FROM `" + config.sqlTablePrefix + "group` AS `group` " + - "WHERE `name` IN (%s)", - RegionDBUtil.preparePlaceHolders(groupnames.length) - ) - ); - - RegionDBUtil.setValues(findGroupsStatement, groupnames); - - findGroupsResults = findGroupsStatement.executeQuery(); - - while(findGroupsResults.next()) { - groups.put(findGroupsResults.getString("name"), findGroupsResults.getInt("id")); - } - - insertGroupStatement = this.conn.prepareStatement( - "INSERT INTO " + - "`" + config.sqlTablePrefix + "group` ( " + - "`id`, " + - "`name`" + - ") VALUES (null, ?)", - Statement.RETURN_GENERATED_KEYS - ); - - for (String groupname : groupnames) { - if (!groups.containsKey(groupname)) { - insertGroupStatement.setString(1, groupname); - insertGroupStatement.execute(); - ResultSet generatedKeys = insertGroupStatement.getGeneratedKeys(); - if (generatedKeys.first()) { - groups.put(groupname, generatedKeys.getInt(1)); - } else { - logger.warning("Could not get the database id for user " + groupname); - } - } - } - } catch (SQLException ex) { - logger.warning("Could not get the database id for the groups " + groupnames.toString() + ex.getMessage()); - } finally { - closeResource(findGroupsResults); - closeResource(findGroupsStatement); - closeResource(insertGroupStatement); - } - - return groups; - } - - /* - * As we don't get notified on the creation/removal of regions: - * 1) We get a list of all of the in-database regions - * 2) We iterate over all of the in-memory regions - * 2a) If the region is in the database, we update the database and - * remove the region from the in-database list - * b) If the region is not in the database, we insert it - * 3) We iterate over what remains of the in-database list and remove - * them from the database - * - * TODO: Look at adding/removing/updating the database when the in - * memory region is created/remove/updated - * - * @see com.sk89q.worldguard.protection.databases.ProtectionDatabase#save() - */ - @Override - public void save() throws ProtectionDatabaseException { - try { - connect(); - } catch (SQLException ex) { - throw new ProtectionDatabaseException(ex); - } - - List regionsInDatabase = new ArrayList(); - - PreparedStatement getAllRegionsStatement = null; - ResultSet getAllRegionsResult = null; - try { - getAllRegionsStatement = this.conn.prepareStatement( - "SELECT `region`.`id` FROM " + - "`" + config.sqlTablePrefix + "region` AS `region` " + - "WHERE `world_id` = ? " - ); - - getAllRegionsStatement.setInt(1, this.worldDbId); - getAllRegionsResult = getAllRegionsStatement.executeQuery(); - - while(getAllRegionsResult.next()) { - regionsInDatabase.add(getAllRegionsResult.getString("id")); - } - } catch (SQLException ex) { - logger.warning("Could not get region list for save comparison: " + ex.getMessage()); - } finally { - closeResource(getAllRegionsResult); - closeResource(getAllRegionsStatement); - } - - for (Map.Entry entry : regions.entrySet()) { - String name = entry.getKey(); - ProtectedRegion region = entry.getValue(); - - try { - if (regionsInDatabase.contains(name)) { - regionsInDatabase.remove(name); - - if (region instanceof ProtectedCuboidRegion) { - updateRegionCuboid( (ProtectedCuboidRegion) region ); - } else if (region instanceof ProtectedPolygonalRegion) { - updateRegionPoly2D( (ProtectedPolygonalRegion) region ); - } else if (region instanceof GlobalProtectedRegion) { - updateRegionGlobal( (GlobalProtectedRegion) region ); - } else { - this.updateRegion(region, region.getClass().getCanonicalName()); - } - } else { - if (region instanceof ProtectedCuboidRegion) { - insertRegionCuboid( (ProtectedCuboidRegion) region ); - } else if (region instanceof ProtectedPolygonalRegion) { - insertRegionPoly2D( (ProtectedPolygonalRegion) region ); - } else if (region instanceof GlobalProtectedRegion) { - insertRegionGlobal( (GlobalProtectedRegion) region ); - } else { - this.insertRegion(region, region.getClass().getCanonicalName()); - } - } - } catch (SQLException ex) { - logger.warning("Could not save region " + region.getId().toLowerCase() + ": " + ex.getMessage()); - throw new ProtectionDatabaseException(ex); - } - } - - for (Map.Entry entry : regions.entrySet()) { - PreparedStatement setParentStatement = null; - try { - if (entry.getValue().getParent() == null) continue; - - setParentStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region` SET " + - "`parent` = ? " + - "WHERE `id` = ? AND `world_id` = " + this.worldDbId - ); - - setParentStatement.setString(1, entry.getValue().getParent().getId().toLowerCase()); - setParentStatement.setString(2, entry.getValue().getId().toLowerCase()); - - setParentStatement.execute(); - } catch (SQLException ex) { - logger.warning("Could not save region parents " + entry.getValue().getId().toLowerCase() + ": " + ex.getMessage()); - throw new ProtectionDatabaseException(ex); - } finally { - closeResource(setParentStatement); - } - } - - for (String name : regionsInDatabase) { - PreparedStatement removeRegion = null; - try { - removeRegion = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region` WHERE `id` = ? " - ); - - removeRegion.setString(1, name); - removeRegion.execute(); - } catch (SQLException ex) { - logger.warning("Could not remove region from database " + name + ": " + ex.getMessage()); - } finally { - closeResource(removeRegion); - } - } - - } - - private void updateFlags(ProtectedRegion region) throws SQLException { - PreparedStatement clearCurrentFlagStatement = null; - try { - clearCurrentFlagStatement = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_flag` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId - ); - - clearCurrentFlagStatement.setString(1, region.getId().toLowerCase()); - clearCurrentFlagStatement.execute(); - - for (Map.Entry, Object> entry : region.getFlags().entrySet()) { - if (entry.getValue() == null) continue; - - Object flag = sqlMarshal(marshalFlag(entry.getKey(), entry.getValue())); - - PreparedStatement insertFlagStatement = null; - try { - insertFlagStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_flag` ( " + - "`id`, " + - "`region_id`, " + - "`world_id`, " + - "`flag`, " + - "`value` " + - ") VALUES (null, ?, " + this.worldDbId + ", ?, ?)" - ); - - insertFlagStatement.setString(1, region.getId().toLowerCase()); - insertFlagStatement.setString(2, entry.getKey().getName()); - insertFlagStatement.setObject(3, flag); - - insertFlagStatement.execute(); - } finally { - closeResource(insertFlagStatement); - } - } - } finally { - closeResource(clearCurrentFlagStatement); - } - } - - private void updatePlayerAndGroups(ProtectedRegion region, Boolean owners) throws SQLException { - DefaultDomain domain; - - if (owners) { - domain = region.getOwners(); - } else { - domain = region.getMembers(); - } - - PreparedStatement deleteUsersForRegion = null; - PreparedStatement insertUsersForRegion = null; - PreparedStatement deleteGroupsForRegion = null; - PreparedStatement insertGroupsForRegion = null; - - try { - deleteUsersForRegion = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_players` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId + " " + - "AND `owner` = ?" - ); - - deleteUsersForRegion.setString(1, region.getId().toLowerCase()); - deleteUsersForRegion.setBoolean(2, owners); - deleteUsersForRegion.execute(); - - insertUsersForRegion = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_players` " + - "(`region_id`, `world_id`, `user_id`, `owner`) " + - "VALUES (?, " + this.worldDbId + ", ?, ?)" - ); - - Set var = domain.getPlayers(); - - for (Integer player : getUserIds(var.toArray(new String[var.size()])).values()) { - insertUsersForRegion.setString(1, region.getId().toLowerCase()); - insertUsersForRegion.setInt(2, player); - insertUsersForRegion.setBoolean(3, owners); - - insertUsersForRegion.execute(); - } - - deleteGroupsForRegion = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_groups` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId + " " + - "AND `owner` = ?" - ); - - deleteGroupsForRegion.setString(1, region.getId().toLowerCase()); - deleteGroupsForRegion.setBoolean(2, owners); - deleteGroupsForRegion.execute(); - - insertGroupsForRegion = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_groups` " + - "(`region_id`, `world_id`, `group_id`, `owner`) " + - "VALUES (?, " + this.worldDbId + ", ?, ?)" - ); - - Set groupVar = domain.getGroups(); - for (Integer group : getGroupIds(groupVar.toArray(new String[groupVar.size()])).values()) { - insertGroupsForRegion.setString(1, region.getId().toLowerCase()); - insertGroupsForRegion.setInt(2, group); - insertGroupsForRegion.setBoolean(3, owners); - - insertGroupsForRegion.execute(); - } - } finally { - closeResource(deleteGroupsForRegion); - closeResource(deleteUsersForRegion); - closeResource(insertGroupsForRegion); - closeResource(insertUsersForRegion); - } - } - - @SuppressWarnings("unchecked") - private Object marshalFlag(Flag flag, Object val) { - return flag.marshal( (V) val ); - } - - private void insertRegion(ProtectedRegion region, String type) throws SQLException { - PreparedStatement insertRegionStatement = null; - try { - insertRegionStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region` (" + - "`id`, " + - "`world_id`, " + - "`type`, " + - "`priority`, " + - "`parent` " + - ") VALUES (?, ?, ?, ?, null)" - ); - - insertRegionStatement.setString(1, region.getId().toLowerCase()); - insertRegionStatement.setInt(2, this.worldDbId); - insertRegionStatement.setString(3, type); - insertRegionStatement.setInt(4, region.getPriority()); - - insertRegionStatement.execute(); - } finally { - closeResource(insertRegionStatement); - } - - updateFlags(region); - - updatePlayerAndGroups(region, false); - updatePlayerAndGroups(region, true); - } - - private void insertRegionCuboid(ProtectedCuboidRegion region) throws SQLException { - insertRegion(region, "cuboid"); - - PreparedStatement insertCuboidRegionStatement = null; - try { - insertCuboidRegionStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_cuboid` (" + - "`region_id`, " + - "`world_id`, " + - "`min_z`, " + - "`min_y`, " + - "`min_x`, " + - "`max_z`, " + - "`max_y`, " + - "`max_x` " + - ") VALUES (?, " + this.worldDbId + ", ?, ?, ?, ?, ?, ?)" - ); - - BlockVector min = region.getMinimumPoint(); - BlockVector max = region.getMaximumPoint(); - - insertCuboidRegionStatement.setString(1, region.getId().toLowerCase()); - insertCuboidRegionStatement.setInt(2, min.getBlockZ()); - insertCuboidRegionStatement.setInt(3, min.getBlockY()); - insertCuboidRegionStatement.setInt(4, min.getBlockX()); - insertCuboidRegionStatement.setInt(5, max.getBlockZ()); - insertCuboidRegionStatement.setInt(6, max.getBlockY()); - insertCuboidRegionStatement.setInt(7, max.getBlockX()); - - insertCuboidRegionStatement.execute(); - } finally { - closeResource(insertCuboidRegionStatement); - } - } - - private void insertRegionPoly2D(ProtectedPolygonalRegion region) throws SQLException { - insertRegion(region, "poly2d"); - - PreparedStatement insertPoly2dRegionStatement = null; - try { - insertPoly2dRegionStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_poly2d` (" + - "`region_id`, " + - "`world_id`, " + - "`max_y`, " + - "`min_y` " + - ") VALUES (?, " + this.worldDbId + ", ?, ?)" - ); - - insertPoly2dRegionStatement.setString(1, region.getId().toLowerCase()); - insertPoly2dRegionStatement.setInt(2, region.getMaximumPoint().getBlockY()); - insertPoly2dRegionStatement.setInt(3, region.getMinimumPoint().getBlockY()); - - insertPoly2dRegionStatement.execute(); - } finally { - closeResource(insertPoly2dRegionStatement); - } - - updatePoly2dPoints(region); - } - - private void updatePoly2dPoints(ProtectedPolygonalRegion region) throws SQLException { - PreparedStatement clearPoly2dPointsForRegionStatement = null; - PreparedStatement insertPoly2dPointStatement = null; - - try { - clearPoly2dPointsForRegionStatement = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_poly2d_point` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId - ); - - clearPoly2dPointsForRegionStatement.setString(1, region.getId().toLowerCase()); - - clearPoly2dPointsForRegionStatement.execute(); - - insertPoly2dPointStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_poly2d_point` (" + - "`id`, " + - "`region_id`, " + - "`world_id`, " + - "`z`, " + - "`x` " + - ") VALUES (null, ?, " + this.worldDbId + ", ?, ?)" - ); - - String lowerId = region.getId().toLowerCase(); - for (BlockVector2D point : region.getPoints()) { - insertPoly2dPointStatement.setString(1, lowerId); - insertPoly2dPointStatement.setInt(2, point.getBlockZ()); - insertPoly2dPointStatement.setInt(3, point.getBlockX()); - - insertPoly2dPointStatement.execute(); - } - } finally { - closeResource(clearPoly2dPointsForRegionStatement); - closeResource(insertPoly2dPointStatement); - } - } - - private void insertRegionGlobal(GlobalProtectedRegion region) throws SQLException { - insertRegion(region, "global"); - } - - private void updateRegion(ProtectedRegion region, String type) throws SQLException { - PreparedStatement updateRegionStatement = null; - try { - updateRegionStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region` SET " + - "`priority` = ? WHERE `id` = ? AND `world_id` = " + this.worldDbId - ); - - updateRegionStatement.setInt(1, region.getPriority()); - updateRegionStatement.setString(2, region.getId().toLowerCase()); - - updateRegionStatement.execute(); - } finally { - closeResource(updateRegionStatement); - } - - updateFlags(region); - - updatePlayerAndGroups(region, false); - updatePlayerAndGroups(region, true); - } - - private void updateRegionCuboid(ProtectedCuboidRegion region) throws SQLException { - updateRegion(region, "cuboid"); - - PreparedStatement updateCuboidRegionStatement = null; - try { - updateCuboidRegionStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region_cuboid` SET " + - "`min_z` = ?, " + - "`min_y` = ?, " + - "`min_x` = ?, " + - "`max_z` = ?, " + - "`max_y` = ?, " + - "`max_x` = ? " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId - ); - - BlockVector min = region.getMinimumPoint(); - BlockVector max = region.getMaximumPoint(); - - updateCuboidRegionStatement.setInt(1, min.getBlockZ()); - updateCuboidRegionStatement.setInt(2, min.getBlockY()); - updateCuboidRegionStatement.setInt(3, min.getBlockX()); - updateCuboidRegionStatement.setInt(4, max.getBlockZ()); - updateCuboidRegionStatement.setInt(5, max.getBlockY()); - updateCuboidRegionStatement.setInt(6, max.getBlockX()); - updateCuboidRegionStatement.setString(7, region.getId().toLowerCase()); - - updateCuboidRegionStatement.execute(); - } finally { - closeResource(updateCuboidRegionStatement); - } - } - - private void updateRegionPoly2D(ProtectedPolygonalRegion region) throws SQLException { - updateRegion(region, "poly2d"); - - PreparedStatement updatePoly2dRegionStatement = null; - try { - updatePoly2dRegionStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region_poly2d` SET " + - "`max_y` = ?, " + - "`min_y` = ? " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldDbId - ); - - updatePoly2dRegionStatement.setInt(1, region.getMaximumPoint().getBlockY()); - updatePoly2dRegionStatement.setInt(2, region.getMinimumPoint().getBlockY()); - updatePoly2dRegionStatement.setString(3, region.getId().toLowerCase()); - - updatePoly2dRegionStatement.execute(); - } finally { - closeResource(updatePoly2dRegionStatement); - } - updatePoly2dPoints(region); - } - - private void updateRegionGlobal(GlobalProtectedRegion region) throws SQLException { - updateRegion(region, "global"); - } - - private void closeResource(ResultSet rs) { - if (rs != null) { - try { - rs.close(); - } catch (SQLException e) {} - } - } - - private void closeResource(Statement st) { - if (st != null) { - try { - st.close(); - } catch (SQLException e) {} - } - } - - @Override - public Map getRegions() { - return regions; - } - - @Override - public void setRegions(Map regions) { - this.regions = regions; - } - - protected Object sqlUnmarshal(String rawValue) { - try { - return yaml.load(rawValue); - } catch (YAMLException e) { - return String.valueOf(rawValue); - } - } - - protected String sqlMarshal(Object rawObject) { - return yaml.dump(rawObject); - } } diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java index 32e71ae3..4b362c7d 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java +++ b/src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java @@ -48,6 +48,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -220,16 +221,25 @@ private void setFlag(ProtectedRegion region, Flag flag, Object rawValue) region.setFlag(flag, val); } + @SuppressWarnings("deprecation") private DefaultDomain parseDomain(YAMLNode node) { if (node == null) { return new DefaultDomain(); } DefaultDomain domain = new DefaultDomain(); - + for (String name : node.getStringList("players", null)) { domain.addPlayer(name); } + + for (String stringId : node.getStringList("uuids", null)) { + try { + domain.addPlayer(UUID.fromString(stringId)); + } catch (IllegalArgumentException e) { + logger.log(Level.WARNING, "Failed to parse UUID '" + stringId +"'", e); + } + } for (String name : node.getStringList("groups", null)) { domain.addGroup(name); @@ -310,33 +320,34 @@ private Map getFlagData(ProtectedRegion region) { } @SuppressWarnings("unchecked") - private void addMarshalledFlag(Map flagData, - Flag flag, Object val) { + private void addMarshalledFlag(Map flagData, Flag flag, Object val) { if (val == null) { return; } + flagData.put(flag.getName(), flag.marshal((V) val)); } + @SuppressWarnings("deprecation") private Map getDomainData(DefaultDomain domain) { Map domainData = new HashMap(); setDomainData(domainData, "players", domain.getPlayers()); + setDomainData(domainData, "uuids", domain.getUniqueIds()); setDomainData(domainData, "groups", domain.getGroups()); return domainData; } - private void setDomainData(Map domainData, - String key, Set domain) { - if (domain.size() == 0) { + private void setDomainData(Map domainData, String key, Set domain) { + if (domain.isEmpty()) { return; } List list = new ArrayList(); - for (String str : domain) { - list.add(str); + for (Object str : domain) { + list.add(String.valueOf(str)); } domainData.put(key, list);