Reorganize MySQL region DB and added BoneCP as a dependency.

This commit is contained in:
sk89q 2014-08-01 19:19:48 -07:00
parent 418b8ff6a7
commit 51f98c9eb7
8 changed files with 1440 additions and 1171 deletions

27
pom.xml
View File

@ -138,6 +138,14 @@
<type>jar</type>
</dependency>
<dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp</artifactId>
<version>0.8.0.RELEASE</version>
<scope>compile</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@ -167,13 +175,27 @@
<scope>compile</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<version>4.11</version>
<scope>test</scope>
<type>jar</type>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.2.1</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
@ -338,6 +360,7 @@
<includes>
<include>net.sf.opencsv:opencsv</include>
<include>org.khelekore:prtree</include>
<include>com.jolbox:bonecp</include>
</includes>
</artifactSet>
</configuration>

View File

@ -0,0 +1,78 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,215 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion> regions = new HashMap<String, ProtectedRegion>();
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<String, ProtectedRegion> getRegions() {
return regions;
}
@Override
public void setRegions(Map<String, ProtectedRegion> 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) {
}
}
}
}
}

View File

@ -0,0 +1,652 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Object> {
private final Map<String, ProtectedRegion> regions;
private final int worldId;
RegionCommitter(MySQLDatabaseImpl database, Connection conn, Map<String, ProtectedRegion> 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<String> regionsInDatabase = new ArrayList<String>();
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<String, ProtectedRegion> 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<String, ProtectedRegion> 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<String,Integer> getUserIds(String... usernames) {
Map<String,Integer> users = new HashMap<String,Integer>();
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<String,Integer> getGroupIds(String... groupnames) {
Map<String,Integer> groups = new HashMap<String,Integer>();
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<Flag<?>, 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<String> 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<String> 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 <V> Object marshalFlag(Flag<V> 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");
}
}

View File

@ -0,0 +1,446 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Map<String, ProtectedRegion>> {
private final Map<String, ProtectedRegion> regions;
private final int worldId;
private Map<String, ProtectedRegion> cuboidRegions;
private Map<String, ProtectedRegion> poly2dRegions;
private Map<String, ProtectedRegion> globalRegions;
private Map<ProtectedRegion, String> parentSets;
RegionLoader(MySQLDatabaseImpl database, Connection conn, Map<String, ProtectedRegion> regions) {
super(database, conn);
checkNotNull(regions);
this.regions = regions;
this.worldId = database.getWorldId();
}
@Override
public Map<String, ProtectedRegion> call() {
parentSets = new HashMap<ProtectedRegion, String>();
// 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<String, ProtectedRegion> 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<ProtectedRegion, String> 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<String, Object> regionFlags = new HashMap<String, Object>();
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 <T> void setFlag(ProtectedRegion region, Flag<T> 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<String, ProtectedRegion> regions =
new HashMap<String, ProtectedRegion>();
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<String, ProtectedRegion> regions = new HashMap<String, ProtectedRegion>();
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<String, ProtectedRegion> regions = new HashMap<String, ProtectedRegion>();
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<BlockVector2D> points = new ArrayList<BlockVector2D>();
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;
}
}

View File

@ -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());

View File

@ -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 <T> void setFlag(ProtectedRegion region, Flag<T> 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<String, Object> getFlagData(ProtectedRegion region) {
}
@SuppressWarnings("unchecked")
private <V> void addMarshalledFlag(Map<String, Object> flagData,
Flag<V> flag, Object val) {
private <V> void addMarshalledFlag(Map<String, Object> flagData, Flag<V> flag, Object val) {
if (val == null) {
return;
}
flagData.put(flag.getName(), flag.marshal((V) val));
}
@SuppressWarnings("deprecation")
private Map<String, Object> getDomainData(DefaultDomain domain) {
Map<String, Object> domainData = new HashMap<String, Object>();
setDomainData(domainData, "players", domain.getPlayers());
setDomainData(domainData, "uuids", domain.getUniqueIds());
setDomainData(domainData, "groups", domain.getGroups());
return domainData;
}
private void setDomainData(Map<String, Object> domainData,
String key, Set<String> domain) {
if (domain.size() == 0) {
private void setDomainData(Map<String, Object> domainData, String key, Set<?> domain) {
if (domain.isEmpty()) {
return;
}
List<String> list = new ArrayList<String>();
for (String str : domain) {
list.add(str);
for (Object str : domain) {
list.add(String.valueOf(str));
}
domainData.put(key, list);