package com.forgeessentials.permissions.persistence;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import com.forgeessentials.api.UserIdent;
import com.forgeessentials.api.permissions.AreaZone;
import com.forgeessentials.api.permissions.FEPermissions;
import com.forgeessentials.api.permissions.ServerZone;
import com.forgeessentials.api.permissions.WorldZone;
import com.forgeessentials.api.permissions.Zone;
import com.forgeessentials.api.permissions.Zone.PermissionList;
import com.forgeessentials.commons.selections.AreaBase;
import com.forgeessentials.commons.selections.AreaShape;
import com.forgeessentials.permissions.core.ZonePersistenceProvider;
import com.forgeessentials.util.EnumDBType;
import com.forgeessentials.util.output.LoggingHandler;
import com.google.common.base.Throwables;
public class SQLProvider extends ZonePersistenceProvider
{
private class TableInfo
{
public String name;
public Map<String, String> columns = new HashMap<>();
public Set<String> primaryKeys = new HashSet<>();
public Set<String> nullableKeys = new HashSet<>();
public TableInfo(String name)
{
this.name = name;
}
public String getCreateStatement()
{
StringBuilder sb = new StringBuilder("CREATE TABLE IF NOT EXISTS `");
sb.append(name);
sb.append("` (");
for (Entry<String, String> col : columns.entrySet())
{
sb.append("`");
sb.append(col.getKey());
sb.append("` ");
sb.append(col.getValue());
if (nullableKeys.contains(col.getKey()))
sb.append(", ");
else
sb.append(" NOT NULL, ");
}
sb.append("PRIMARY KEY (`");
sb.append(StringUtils.join(primaryKeys, "`, `"));
sb.append("`))");
return sb.toString();
}
public String createSelectStatement(Collection<?> fields)
{
for (Object f : fields)
if (!columns.containsKey(f))
throw new RuntimeException("Error in select statement.");
StringBuilder sb = new StringBuilder("SELECT `");
sb.append(StringUtils.join(fields, "`, `"));
sb.append("` FROM `");
sb.append(name);
sb.append("`");
return sb.toString();
}
public String createSelectStatement(String[] fields)
{
return createSelectStatement(Arrays.asList(fields));
}
public String createSelectStatement()
{
StringBuilder sb = new StringBuilder("SELECT * FROM `");
sb.append(name);
sb.append("`");
return sb.toString();
}
public String createInsertOrReplace(Map<String, Object> fieldsAndValues)
{
for (String f : fieldsAndValues.keySet())
if (!columns.containsKey(f))
throw new RuntimeException("Error in select statement.");
StringBuilder sb = new StringBuilder();
if (dbType == EnumDBType.H2_FILE)
sb.append("MERGE INTO `");
else
sb.append("REPLACE INTO `");
sb.append(name);
sb.append("` (`");
sb.append(StringUtils.join(fieldsAndValues.keySet(), "`, `"));
sb.append("`) VALUES ('");
sb.append(StringUtils.join(fieldsAndValues.values(), "', '"));
sb.append("')");
return sb.toString();
}
public String createTruncate()
{
return "TRUNCATE TABLE `" + name + "`";
}
public List<Map<String, Object>> loadList() throws SQLException
{
ResultSet resultSet = db.createStatement().executeQuery(createSelectStatement());
ResultSetMetaData meta = resultSet.getMetaData();
int columnsCount = meta.getColumnCount();
List<Map<String, Object>> list = new ArrayList<>();
while (resultSet.next())
{
Map<String, Object> row = new HashMap<>(columnsCount);
for (int columnIndex = 1; columnIndex <= columnsCount; ++columnIndex)
row.put(meta.getColumnName(columnIndex).toLowerCase(), resultSet.getObject(columnIndex));
list.add(row);
}
return list;
}
@SuppressWarnings("unused")
public Map<Integer, Map<String, Object>> loadIntMap(String key) throws SQLException
{
ResultSet resultSet = db.createStatement().executeQuery(createSelectStatement());
ResultSetMetaData meta = resultSet.getMetaData();
int columnsCount = meta.getColumnCount();
Map<Integer, Map<String, Object>> list = new HashMap<>();
while (resultSet.next())
{
Map<String, Object> row = new HashMap<>(columnsCount);
for (int columnIndex = 1; columnIndex <= columnsCount; ++columnIndex)
row.put(meta.getColumnName(columnIndex).toLowerCase(), resultSet.getObject(columnIndex));
list.put(resultSet.getInt(key), row);
}
return list;
}
public Map<String, String> loadMap(String key, String value) throws SQLException
{
List<String> fields = new ArrayList<>();
fields.add(key);
fields.add(value);
ResultSet resultSet = db.createStatement().executeQuery(createSelectStatement(fields));
Map<String, String> result = new HashMap<>();
while (resultSet.next())
result.put(resultSet.getString(key), resultSet.getString(value));
return result;
}
}
private static final String TABLE_PREFIX = "fepermissions_";
private static final String TABLE_INFO = "INFO";
private static final String TABLE_ZONE = "ZONE";
private static final String TABLE_GROUP_PERMISSIONS = "GROUP_PERMISSION";
private static final String TABLE_USER_PERMISSIONS = "USER_PERMISSION";
private static final String TABLE_USER = "USER";
// private static final String TABLE_GROUP = "GROUP";
private static final String INFO_MAX_ZONE_ID = "max_zone_id";
private static final String VERSION = "1.2";
private final Map<String, TableInfo> TABLES = setupTableData();
private Map<String, TableInfo> setupTableData()
{
HashMap<String, TableInfo> result = new HashMap<>();
TableInfo tbl;
tbl = new TableInfo(TABLE_PREFIX + "info");
tbl.columns.put("key", "VARCHAR(64)");
tbl.columns.put("value", "VARCHAR(64)");
tbl.primaryKeys.add("key");
result.put(TABLE_INFO, tbl);
// tbl = new TableInfo(TABLE_PREFIX + "group");
// tbl.columns.put("name", "VARCHAR(64)");
// tbl.primaryKeys.add("name");
// result.put(TABLE_GROUP, tbl);
tbl = new TableInfo(TABLE_PREFIX + "user");
tbl.columns.put("uuid", "VARCHAR(36)");
tbl.columns.put("name", "VARCHAR(128)");
tbl.primaryKeys.add("uuid");
tbl.nullableKeys.add("name");
result.put(TABLE_USER, tbl);
tbl = new TableInfo(TABLE_PREFIX + "zone");
tbl.columns.put("id", "INT");
tbl.columns.put("type", "INT");
tbl.columns.put("parent_id", "INT");
tbl.columns.put("name", "VARCHAR(64)");
tbl.columns.put("dimension", "INT");
tbl.columns.put("area", "VARCHAR(64)");
tbl.columns.put("shape", "VARCHAR(16)");
tbl.primaryKeys.add("id");
tbl.nullableKeys.add("parent_id");
tbl.nullableKeys.add("name");
tbl.nullableKeys.add("dimension");
tbl.nullableKeys.add("area");
tbl.nullableKeys.add("shape");
result.put(TABLE_ZONE, tbl);
tbl = new TableInfo(TABLE_PREFIX + "group_permission");
tbl.columns.put("group", "VARCHAR(64)");
tbl.columns.put("zone_id", "INT");
tbl.columns.put("permission", "VARCHAR(255)");
tbl.columns.put("value", "VARCHAR(1023)");
tbl.primaryKeys.add("group");
tbl.primaryKeys.add("zone_id");
tbl.primaryKeys.add("permission");
result.put(TABLE_GROUP_PERMISSIONS, tbl);
tbl = new TableInfo(TABLE_PREFIX + "user_permission");
tbl.columns.put("user", "VARCHAR(36)");
tbl.columns.put("zone_id", "INT");
tbl.columns.put("permission", "VARCHAR(255)");
tbl.columns.put("value", "VARCHAR(1023)");
tbl.primaryKeys.add("user");
tbl.primaryKeys.add("zone_id");
tbl.primaryKeys.add("permission");
result.put(TABLE_USER_PERMISSIONS, tbl);
return result;
}
// ------------------------------------------------------------
protected Connection db;
protected EnumDBType dbType;
// ------------------------------------------------------------
public SQLProvider(Connection connection, EnumDBType dbType)
{
this.db = connection;
this.dbType = dbType;
checkAndCreateTables();
String version = getVersion();
if (version == null)
{
setVersion(VERSION);
}
else
{
// check versions
if (!VERSION.equals(version))
{
LoggingHandler.felog.info("Version of permission database incorrect. May not load permissions correctly!");
}
if (version.equals("1.0"))
{
TableInfo tbl = TABLES.get(TABLE_ZONE);
executeUpdate("ALTER TABLE `" + tbl.name + "` ADD COLUMN `shape` " + tbl.columns.get("shape"));
setVersion(VERSION);
}
}
}
private Set<String> getExistingTables() throws SQLException
{
ResultSet resultSet = db.getMetaData().getTables(null, null, TABLE_PREFIX + "%", null);
Set<String> result = new HashSet<>();
while (resultSet.next())
result.add(resultSet.getString(3));
return result;
}
private boolean checkAndCreateTables()
{
boolean checkOk = true;
try
{
Set<String> existingTables = getExistingTables();
Statement statement = db.createStatement();
for (TableInfo tbl : TABLES.values())
{
if (existingTables.contains(tbl.name))
continue;
checkOk = false;
statement.executeUpdate(tbl.getCreateStatement());
existingTables.add(tbl.name);
}
}
catch (SQLException e)
{
Throwables.propagate(e);
}
return checkOk;
}
private void executeUpdate(String stmt)
{
try
{
db.createStatement().executeUpdate(stmt);
}
catch (SQLException e)
{
e.printStackTrace();
}
}
private String getVersion()
{
try
{
ResultSet result = db.createStatement().executeQuery(
TABLES.get(TABLE_INFO).createSelectStatement(new String[] { "value" }) + " WHERE `key` = 'version'");
if (result.next())
return result.getString(1);
return null;
}
catch (SQLException e)
{
return null;
}
}
private void setVersion(String version)
{
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("key", "version");
fieldsAndValues.put("value", version);
executeUpdate(TABLES.get(TABLE_INFO).createInsertOrReplace(fieldsAndValues));
}
@Override
public void save(ServerZone serverZone)
{
try
{
writeUserGroupPermissions(serverZone);
// Use a transaction to be able to rollback, if there is an error
// db.setAutoCommit(false);
// Truncate old data
db.createStatement().executeUpdate(TABLES.get(TABLE_ZONE).createTruncate());
// db.createStatement().executeUpdate(TABLES.get(TABLE_USER).createTruncate());
// db.createStatement().executeUpdate(TABLES.get(TABLE_GROUP).createTruncate());
db.createStatement().executeUpdate(TABLES.get(TABLE_GROUP_PERMISSIONS).createTruncate());
db.createStatement().executeUpdate(TABLES.get(TABLE_USER_PERMISSIONS).createTruncate());
for (UserIdent ident : serverZone.getKnownPlayers())
{
if (!ident.hasUuid())
continue;
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("uuid", ident.getOrGenerateUuid().toString());
if (ident.hasUsername())
fieldsAndValues.put("name", ident.getUsername());
db.createStatement().executeUpdate(TABLES.get(TABLE_USER).createInsertOrReplace(fieldsAndValues));
}
saveServerZone(serverZone);
saveZonePermissions(serverZone);
for (WorldZone worldZone : serverZone.getWorldZones().values())
{
saveWorldZone(worldZone);
saveZonePermissions(worldZone);
for (AreaZone areaZone : worldZone.getAreaZones())
{
saveAreaZone(areaZone);
saveZonePermissions(areaZone);
}
}
// db.commit();
// db.setAutoCommit(true);
}
catch (SQLException se)
{
try
{
db.rollback();
}
catch (SQLException se2)
{
// Ignore rollback-error
}
Throwables.propagate(se);
}
}
private void saveZonePermissions(Zone zone)
{
try
{
for (Entry<String, PermissionList> group : zone.getGroupPermissions().entrySet())
{
for (Entry<String, String> perm : group.getValue().entrySet())
{
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("group", group.getKey());
fieldsAndValues.put("zone_id", zone.getId());
fieldsAndValues.put("permission", perm.getKey());
fieldsAndValues.put("value", perm.getValue());
db.createStatement().executeUpdate(TABLES.get(TABLE_GROUP_PERMISSIONS).createInsertOrReplace(fieldsAndValues));
}
}
for (Entry<UserIdent, PermissionList> user : zone.getPlayerPermissions().entrySet())
{
for (Entry<String, String> perm : user.getValue().entrySet())
{
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("user", user.getKey().getOrGenerateUuid().toString());
fieldsAndValues.put("zone_id", zone.getId());
fieldsAndValues.put("permission", perm.getKey());
fieldsAndValues.put("value", perm.getValue());
db.createStatement().executeUpdate(TABLES.get(TABLE_USER_PERMISSIONS).createInsertOrReplace(fieldsAndValues));
}
}
// PreparedStatement pstmt = db.prepareStatement("UPDATE");
//
// // Add rows to a batch in a loop. Each iteration adds a new row.
// for (int i = 0; i < firstNames.length; i++)
// {
// // Add each parameter to the row.
// pstmt.setInt(1, i + 1);
// pstmt.setString(2, lastNames[i]);
// pstmt.setString(3, firstNames[i]);
// pstmt.setString(4, emails[i]);
// pstmt.setString(5, phoneNumbers[i]);
// // Add row to the batch.
// pstmt.addBatch();
// }
}
catch (SQLException e)
{
Throwables.propagate(e);
}
}
public void saveServerZone(ServerZone zone) throws SQLException
{
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("id", zone.getId());
fieldsAndValues.put("type", 0);
fieldsAndValues.put("parent_id", 0);
db.createStatement().executeUpdate(TABLES.get(TABLE_ZONE).createInsertOrReplace(fieldsAndValues));
fieldsAndValues = new HashMap<>();
fieldsAndValues.put("key", INFO_MAX_ZONE_ID);
fieldsAndValues.put("value", zone.getMaxZoneID());
db.createStatement().executeUpdate(TABLES.get(TABLE_INFO).createInsertOrReplace(fieldsAndValues));
}
private void saveWorldZone(WorldZone zone) throws SQLException
{
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("id", zone.getId());
fieldsAndValues.put("type", 1);
fieldsAndValues.put("parent_id", zone.getParent().getId());
fieldsAndValues.put("dimension", zone.getDimensionID());
db.createStatement().executeUpdate(TABLES.get(TABLE_ZONE).createInsertOrReplace(fieldsAndValues));
}
private void saveAreaZone(AreaZone zone) throws SQLException
{
Map<String, Object> fieldsAndValues = new HashMap<>();
fieldsAndValues.put("id", zone.getId());
fieldsAndValues.put("type", 2);
fieldsAndValues.put("parent_id", zone.getParent().getId());
fieldsAndValues.put("name", zone.getShortName());
fieldsAndValues.put("dimension", zone.getWorldZone().getDimensionID());
fieldsAndValues.put("area", zone.getArea().toString());
fieldsAndValues.put("shape", zone.getShape().toString());
db.createStatement().executeUpdate(TABLES.get(TABLE_ZONE).createInsertOrReplace(fieldsAndValues));
}
@Override
public ServerZone load()
{
try
{
Map<Integer, Zone> zones = new HashMap<>();
// Load data from SQL
Map<String, String> infoData = TABLES.get(TABLE_INFO).loadMap("key", "value");
List<Map<String, Object>> zonesData = TABLES.get(TABLE_ZONE).loadList();
List<Map<String, Object>> groupPermissions = TABLES.get(TABLE_GROUP_PERMISSIONS).loadList();
List<Map<String, Object>> userPermissions = TABLES.get(TABLE_USER_PERMISSIONS).loadList();
// Create server-zone
ServerZone serverZone = null;
for (Map<String, Object> zoneData : zonesData)
if (zoneData.get("type").equals(0))
{
serverZone = new ServerZone();
zones.put(serverZone.getId(), serverZone);
break;
}
// Check if server-zone could be created - otherwise save was corrupt or just not present
if (serverZone == null)
{
LoggingHandler.felog.error("Error loading permissions: Missing server-zone");
db.createStatement().executeUpdate(TABLES.get(TABLE_ZONE).createTruncate());
return null;
}
// Create world-zones
for (Map<String, Object> zoneData : zonesData)
if (zoneData.get("type").equals(1))
{
WorldZone zone = new WorldZone(serverZone, (Integer) zoneData.get("dimension"), (Integer) zoneData.get("id"));
zones.put(zone.getId(), zone);
}
// Create area-zones
for (Map<String, Object> zoneData : zonesData)
if (zoneData.get("type").equals(2))
{
WorldZone parentZone = (WorldZone) zones.get(zoneData.get("parent_id"));
if (parentZone != null)
{
AreaBase area = AreaBase.fromString((String) zoneData.get("area"));
if (area != null)
{
AreaZone zone = new AreaZone(parentZone, (String) zoneData.get("name"), area, (Integer) zoneData.get("id"));
AreaShape shape = AreaShape.getByName((String) zoneData.get("shape"));
if (shape != null)
zone.setShape(shape);
zones.put(zone.getId(), zone);
}
}
}
// Apply group permissions
for (Map<String, Object> permData : groupPermissions)
{
Zone zone = zones.get(permData.get("zone_id"));
if (zone != null)
{
zone.setGroupPermissionProperty((String) permData.get("group"), (String) permData.get("permission"), (String) permData.get("value"));
}
}
// Apply user permissions
for (Map<String, Object> permData : userPermissions)
{
Zone zone = zones.get(permData.get("zone_id"));
if (zone != null)
{
zone.setPlayerPermissionProperty(UserIdent.get((String) permData.get("user")), (String) permData.get("permission"),
(String) permData.get("value"));
}
}
// Load maxZoneId
try
{
serverZone.setMaxZoneId(Integer.parseInt(infoData.get(INFO_MAX_ZONE_ID)));
}
catch (NumberFormatException e)
{
}
// Make sure maxZoneId is valid
for (Zone zone : zones.values())
if (zone.getId() > serverZone.getMaxZoneID())
serverZone.setMaxZoneId(zone.getId());
// Add user to groups by fe.internal.player.groups permission
for (UserIdent ident : serverZone.getPlayerPermissions().keySet())
{
String groupList = serverZone.getPlayerPermission(ident, FEPermissions.PLAYER_GROUPS);
serverZone.clearPlayerPermission(ident, FEPermissions.PLAYER_GROUPS);
if (groupList == null)
continue;
String[] groups = groupList.split(",");
for (String group : groups)
{
serverZone.addPlayerToGroup(ident, group);
}
}
return serverZone;
}
catch (Exception e)
{
LoggingHandler.felog.error("Error loading permissions");
e.printStackTrace();
}
return null;
}
}