WIP: implement better sql schema - towards #77

This commit is contained in:
Luck
2016-12-24 20:30:35 +00:00
Unverified
parent d34013396a
commit 6874cfd5b1
15 changed files with 1307 additions and 364 deletions
@@ -30,6 +30,7 @@ import lombok.Getter;
public enum Dependency {
MYSQL_DRIVER("https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.6/mysql-connector-java-5.1.6.jar", "5.1.6", "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"),
POSTGRESQL_DRIVER("https://repo1.maven.org/maven2/org/postgresql/postgresql/9.4.1212/postgresql-9.4.1212.jar", "9.4.1212", "org.postgresql.ds.PGSimpleDataSource"),
H2_DRIVER("https://repo1.maven.org/maven2/com/h2database/h2/1.4.193/h2-1.4.193.jar", "1.4.193", "org.h2.Driver"),
SQLITE_DRIVER("https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.15.1/sqlite-jdbc-3.15.1.jar", "3.15.1", "org.sqlite.JDBC"),
HIKARI("https://repo1.maven.org/maven2/com/zaxxer/HikariCP/2.5.1/HikariCP-2.5.1.jar", "2.5.1", "com.zaxxer.hikari.HikariDataSource"),
@@ -60,6 +60,7 @@ public class DependencyManager {
.put(StorageType.YAML, ImmutableList.of())
.put(StorageType.MONGODB, ImmutableList.of(Dependency.MONGODB_DRIVER))
.put(StorageType.MYSQL, ImmutableList.of(Dependency.MYSQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
.put(StorageType.POSTGRESQL, ImmutableList.of(Dependency.POSTGRESQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
.put(StorageType.SQLITE, ImmutableList.of(Dependency.SQLITE_DRIVER))
.put(StorageType.H2, ImmutableList.of(Dependency.H2_DRIVER))
.build();
@@ -30,7 +30,7 @@ import me.lucko.luckperms.common.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.backing.AbstractBacking;
import me.lucko.luckperms.common.storage.backing.JSONBacking;
import me.lucko.luckperms.common.storage.backing.MongoDBBacking;
import me.lucko.luckperms.common.storage.backing.SQLBacking;
import me.lucko.luckperms.common.storage.backing.SQLLegacyBacking;
import me.lucko.luckperms.common.storage.backing.YAMLBacking;
import me.lucko.luckperms.common.storage.backing.sqlprovider.H2Provider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.MySQLProvider;
@@ -120,11 +120,11 @@ public class StorageFactory {
private static AbstractBacking makeBacking(StorageType method, LuckPermsPlugin plugin) {
switch (method) {
case MYSQL:
return new SQLBacking(plugin, new MySQLProvider(plugin.getConfiguration().getDatabaseValues()));
return new SQLLegacyBacking(plugin, new MySQLProvider(plugin.getConfiguration().getDatabaseValues()));
case SQLITE:
return new SQLBacking(plugin, new SQLiteProvider(new File(plugin.getDataFolder(), "luckperms.sqlite")));
return new SQLLegacyBacking(plugin, new SQLiteProvider(new File(plugin.getDataFolder(), "luckperms.sqlite")));
case H2:
return new SQLBacking(plugin, new H2Provider(new File(plugin.getDataFolder(), "luckperms.db")));
return new SQLLegacyBacking(plugin, new H2Provider(new File(plugin.getDataFolder(), "luckperms.db")));
case MONGODB:
return new MongoDBBacking(plugin, plugin.getConfiguration().getDatabaseValues());
case YAML:
@@ -34,6 +34,7 @@ public enum StorageType {
YAML("yaml", "yml"),
MONGODB("mongodb"),
MYSQL("mysql"),
POSTGRESQL("postgresql"),
SQLITE("sqlite"),
H2("h2");
@@ -22,106 +22,76 @@
package me.lucko.luckperms.common.storage.backing;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.LuckPermsPlugin;
import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Log;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.storage.backing.sqlprovider.H2Provider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.MySQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLiteProvider;
import me.lucko.luckperms.common.storage.backing.utils.NodeDataHolder;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static me.lucko.luckperms.common.core.model.PermissionHolder.exportToLegacy;
import static me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider.QueryPS;
import static me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider.QueryRS;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SQLBacking extends AbstractBacking {
private static final Type NM_TYPE = new TypeToken<Map<String, Boolean>>() {}.getType();
private static final Type T_TYPE = new TypeToken<List<String>>() {}.getType();
private static final String MYSQL_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private static final Type LIST_STRING_TYPE = new TypeToken<List<String>>() {}.getType();
private static final String H2_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private static final String USER_PERMISSIONS_SELECT = "SELECT permission, value, server, world, expiry, contexts FROM {prefix}user_permissions WHERE uuid=?";
private static final String USER_PERMISSIONS_DELETE = "DELETE FROM {prefix}user_permissions WHERE uuid=?";
private static final String USER_PERMISSIONS_INSERT = "INSERT INTO {prefix}user_permissions VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String USER_PERMISSIONS_SELECT_DISTINCT = "SELECT DISTINCT uuid FROM {prefix}user_permissions";
private static final String SQLITE_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`));";
private static final String SQLITE_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INTEGER PRIMARY KEY NOT NULL, `time` BIG INT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL);";
private static final Map<Class<? extends SQLProvider>, String[]> INIT_QUERIES = ImmutableMap.<Class<? extends SQLProvider>, String[]>builder()
.put(MySQLProvider.class, new String[]{MYSQL_CREATETABLE_UUID, MYSQL_CREATETABLE_USERS, MYSQL_CREATETABLE_GROUPS, MYSQL_CREATETABLE_TRACKS, MYSQL_CREATETABLE_ACTION})
.put(H2Provider.class, new String[]{H2_CREATETABLE_UUID, H2_CREATETABLE_USERS, H2_CREATETABLE_GROUPS, H2_CREATETABLE_TRACKS, H2_CREATETABLE_ACTION})
.put(SQLiteProvider.class, new String[]{SQLITE_CREATETABLE_UUID, SQLITE_CREATETABLE_USERS, SQLITE_CREATETABLE_GROUPS, SQLITE_CREATETABLE_TRACKS, SQLITE_CREATETABLE_ACTION})
.build();
private static final String USER_INSERT = "INSERT INTO lp_users VALUES(?, ?, ?, ?)";
private static final String USER_SELECT = "SELECT * FROM lp_users WHERE uuid=?";
private static final String USER_SELECT_ALL = "SELECT uuid FROM lp_users";
private static final String USER_UPDATE = "UPDATE lp_users SET name=?, primary_group = ?, perms=? WHERE uuid=?";
private static final String USER_DELETE = "DELETE FROM lp_users WHERE uuid=?";
private static final String USER_DELETE_ALL = "DELETE FROM lp_users WHERE perms=?";
private static final String PLAYER_SELECT = "SELECT username, primary_group FROM {prefix}players WHERE uuid=?";
private static final String GROUP_INSERT = "INSERT INTO lp_groups VALUES(?, ?)";
private static final String GROUP_SELECT = "SELECT perms FROM lp_groups WHERE name=?";
private static final String GROUP_SELECT_ALL = "SELECT * FROM lp_groups";
private static final String GROUP_UPDATE = "UPDATE lp_groups SET perms=? WHERE name=?";
private static final String GROUP_DELETE = "DELETE FROM lp_groups WHERE name=?";
private static final String TRACK_INSERT = "INSERT INTO {prefix}tracks VALUES(?, ?)";
private static final String TRACK_SELECT = "SELECT groups FROM {prefix}tracks WHERE name=?";
private static final String TRACK_SELECT_ALL = "SELECT * FROM {prefix}tracks";
private static final String TRACK_UPDATE = "UPDATE {prefix}tracks SET groups=? WHERE name=?";
private static final String TRACK_DELETE = "DELETE FROM {prefix}tracks WHERE name=?";
private static final String TRACK_INSERT = "INSERT INTO lp_tracks VALUES(?, ?)";
private static final String TRACK_SELECT = "SELECT groups FROM lp_tracks WHERE name=?";
private static final String TRACK_SELECT_ALL = "SELECT * FROM lp_tracks";
private static final String TRACK_UPDATE = "UPDATE lp_tracks SET groups=? WHERE name=?";
private static final String TRACK_DELETE = "DELETE FROM lp_tracks WHERE name=?";
private static final String ACTION_INSERT = "INSERT INTO {prefix}actions(`time`, `actor_uuid`, `actor_name`, `type`, `acted_uuid`, `acted_name`, `action`) VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String ACTION_SELECT_ALL = "SELECT * FROM {prefix}actions";
private static final String UUIDCACHE_INSERT = "INSERT INTO lp_uuid VALUES(?, ?)";
private static final String UUIDCACHE_SELECT = "SELECT uuid FROM lp_uuid WHERE name=?";
private static final String UUIDCACHE_SELECT_NAME = "SELECT name FROM lp_uuid WHERE uuid=?";
private static final String UUIDCACHE_UPDATE = "UPDATE lp_uuid SET uuid=? WHERE name=?";
private static final String ACTION_INSERT = "INSERT INTO lp_actions(`time`, `actor_uuid`, `actor_name`, `type`, `acted_uuid`, `acted_name`, `action`) VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String ACTION_SELECT_ALL = "SELECT * FROM lp_actions";
private final Gson gson;
private final SQLProvider provider;
private final Function<String, String> prefix;
public SQLBacking(LuckPermsPlugin plugin, SQLProvider provider) {
public SQLBacking(LuckPermsPlugin plugin, SQLProvider provider, String prefix) {
super(plugin, provider.getName());
this.provider = provider;
this.prefix = s -> s.replace("{prefix}", prefix);
gson = new Gson();
}
private boolean runQuery(String query, QueryPS queryPS) {
private boolean runQuery(String query, SQLProvider.QueryPS queryPS) {
return provider.runQuery(query, queryPS);
}
private boolean runQuery(String query, QueryPS queryPS, QueryRS queryRS) {
private boolean runQuery(String query, SQLProvider.QueryPS queryPS, SQLProvider.QueryRS queryRS) {
return provider.runQuery(query, queryPS, queryRS);
}
@@ -129,17 +99,12 @@ public class SQLBacking extends AbstractBacking {
return provider.runQuery(query);
}
private boolean runQuery(String query, QueryRS queryRS) {
private boolean runQuery(String query, SQLProvider.QueryRS queryRS) {
return provider.runQuery(query, queryRS);
}
private boolean setupTables(String[] tableQueries) {
boolean success = true;
for (String q : tableQueries) {
if (!runQuery(q)) success = false;
}
return success && cleanupUsers();
public boolean tableExists(String table) throws SQLException {
return provider.getConnection().getMetaData().getTables(null, null, table.toUpperCase(), null).next();
}
@Override
@@ -147,15 +112,45 @@ public class SQLBacking extends AbstractBacking {
try {
provider.init();
if (!setupTables(INIT_QUERIES.get(provider.getClass()))) {
plugin.getLog().severe("Error occurred whilst initialising the database.");
shutdown();
} else {
setAcceptingLogins(true);
// Init tables
if (!tableExists(prefix + "user_permissions")) {
String schemaFileName = "lp-schema-" + provider.getName().toLowerCase() + ".sql";
try (InputStream is = plugin.getClass().getResourceAsStream("sql/" + schemaFileName)) {
if (is == null) {
throw new Exception("Couldn't locate schema file for " + provider.getName());
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
try (Statement s = provider.getConnection().createStatement()) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("--") || line.startsWith("#")) continue;
sb.append(line);
// check for end of declaration
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = prefix.apply(sb.toString().trim());
if (!result.isEmpty()) s.addBatch(result);
// reset
sb = new StringBuilder();
}
}
s.executeBatch();
}
}
}
}
setAcceptingLogins(true);
} catch (Exception e) {
e.printStackTrace();
plugin.getLog().severe("Error occurred whilst initialising the database.");
shutdown();
}
}
@@ -170,7 +165,7 @@ public class SQLBacking extends AbstractBacking {
@Override
public boolean logAction(LogEntry entry) {
return runQuery(ACTION_INSERT, preparedStatement -> {
return runQuery(prefix.apply(ACTION_INSERT), preparedStatement -> {
preparedStatement.setLong(1, entry.getTimestamp());
preparedStatement.setString(2, entry.getActor().toString());
preparedStatement.setString(3, entry.getActorName());
@@ -184,7 +179,7 @@ public class SQLBacking extends AbstractBacking {
@Override
public Log getLog() {
final Log.Builder log = Log.builder();
boolean success = runQuery(ACTION_SELECT_ALL, resultSet -> {
boolean success = runQuery(prefix.apply(ACTION_SELECT_ALL), resultSet -> {
while (resultSet.next()) {
final String actedUuid = resultSet.getString("acted_uuid");
LogEntry e = new LogEntry(
@@ -208,56 +203,61 @@ public class SQLBacking extends AbstractBacking {
User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username));
user.getIoLock().lock();
try {
// screw "effectively final"
final String[] perms = new String[1];
final String[] pg = new String[1];
final String[] name = new String[1];
final boolean[] exists = {false};
boolean s = runQuery(USER_SELECT,
preparedStatement -> preparedStatement.setString(1, user.getUuid().toString()),
resultSet -> {
if (resultSet.next()) {
// User exists.
exists[0] = true;
perms[0] = resultSet.getString("perms");
pg[0] = resultSet.getString("primary_group");
name[0] = resultSet.getString("name");
List<NodeDataHolder> data = new ArrayList<>();
AtomicReference<String> primaryGroup = new AtomicReference<>(null);
AtomicReference<String> userName = new AtomicReference<>();
boolean s = runQuery(
prefix.apply(USER_PERMISSIONS_SELECT),
ps -> ps.setString(1, user.getUuid().toString()),
rs -> {
while (rs.next()) {
String permission = rs.getString("permission");
boolean value = rs.getBoolean("value");
String server = rs.getString("server");
String world = rs.getString("world");
long expiry = rs.getLong("expiry");
String contexts = rs.getString("contexts");
data.add(NodeDataHolder.of(permission, value, server, world, expiry, contexts));
}
return true;
}
);
if (!s) {
boolean s2 = runQuery(
prefix.apply(PLAYER_SELECT),
ps -> ps.setString(1, user.getUuid().toString()),
rs -> {
if (rs.next()) {
userName.set(rs.getString("username"));
primaryGroup.set(rs.getString("primary_group"));
}
return true;
}
);
if (!s || !s2) {
return false;
}
if (exists[0]) {
// User exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
if (!data.isEmpty()) {
Set<Node> nodes = data.stream().map(NodeDataHolder::toNode).collect(Collectors.toSet());
user.setNodes(nodes);
user.setPrimaryGroup(pg[0]);
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
String pg = primaryGroup.get();
if (pg == null) {
pg = "default";
}
String name = userName.get();
if (name == null) {
name = "null";
}
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(name[0]);
} else {
if (!name[0].equals(user.getName())) {
save = true;
}
user.setName(name);
}
if (save) {
String json = gson.toJson(exportToLegacy(user.getNodes()));
runQuery(USER_UPDATE, preparedStatement -> {
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPrimaryGroup());
preparedStatement.setString(3, json);
preparedStatement.setString(4, user.getUuid().toString());
});
}
user.setPrimaryGroup(pg);
} else {
if (GenericUserManager.shouldSave(user)) {
@@ -276,55 +276,39 @@ public class SQLBacking extends AbstractBacking {
@Override
public boolean saveUser(User user) {
if (!GenericUserManager.shouldSave(user)) {
user.getIoLock().lock();
try {
return runQuery(USER_DELETE, preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
});
} finally {
user.getIoLock().unlock();
}
}
boolean shouldSave = GenericUserManager.shouldSave(user);
user.getIoLock().lock();
try {
final boolean[] exists = {false};
boolean success = runQuery(USER_SELECT,
preparedStatement -> preparedStatement.setString(1, user.getUuid().toString()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
}
return true;
}
);
if (!success) {
boolean s = runQuery(prefix.apply(USER_PERMISSIONS_DELETE), preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
});
if (!s) {
return false;
}
final String s = gson.toJson(exportToLegacy(user.getNodes()));
if (exists[0]) {
// User exists, let's update.
return runQuery(USER_UPDATE, preparedStatement -> {
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPrimaryGroup());
preparedStatement.setString(3, s);
preparedStatement.setString(4, user.getUuid().toString());
});
} else {
// Doesn't already exist, let's insert.
return runQuery(USER_INSERT, preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
preparedStatement.setString(2, user.getName());
preparedStatement.setString(3, user.getPrimaryGroup());
preparedStatement.setString(4, s);
});
if (!shouldSave) {
return true;
}
List<NodeDataHolder> data = user.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toList());
try (PreparedStatement ps = provider.getConnection().prepareStatement(prefix.apply(USER_PERMISSIONS_INSERT))) {
for (NodeDataHolder nd : data) {
ps.setString(1, user.getUuid().toString());
ps.setString(2, nd.getPermission());
ps.setBoolean(3, nd.isValue());
ps.setString(4, nd.getServer());
ps.setString(5, nd.getWorld());
ps.setLong(6, nd.getExpiry());
ps.setString(7, nd.getContexts());
ps.addBatch();
}
ps.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
return true;
} finally {
user.getIoLock().unlock();
}
@@ -332,18 +316,16 @@ public class SQLBacking extends AbstractBacking {
@Override
public boolean cleanupUsers() {
return runQuery(USER_DELETE_ALL, preparedStatement -> {
preparedStatement.setString(1, "{\"group.default\":true}");
});
return true; // TODO
}
@Override
public Set<UUID> getUniqueUsers() {
Set<UUID> uuids = new HashSet<>();
boolean success = runQuery(USER_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String uuid = resultSet.getString("uuid");
boolean success = runQuery(prefix.apply(USER_PERMISSIONS_SELECT_DISTINCT), rs -> {
while (rs.next()) {
String uuid = rs.getString("uuid");
uuids.add(UUID.fromString(uuid));
}
return true;
@@ -354,134 +336,27 @@ public class SQLBacking extends AbstractBacking {
@Override
public boolean createAndLoadGroup(String name) {
Group group = plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
final boolean[] exists = {false};
final String[] perms = new String[1];
boolean s = runQuery(GROUP_SELECT,
preparedStatement -> preparedStatement.setString(1, group.getName()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
perms[0] = resultSet.getString("perms");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// Group exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
group.setNodes(nodes);
return true;
} else {
String json = gson.toJson(exportToLegacy(group.getNodes()));
return runQuery(GROUP_INSERT, preparedStatement -> {
preparedStatement.setString(1, group.getName());
preparedStatement.setString(2, json);
});
}
} finally {
group.getIoLock().unlock();
}
return false;
}
@Override
public boolean loadGroup(String name) {
Group group = plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
final String[] perms = new String[1];
boolean s = runQuery(GROUP_SELECT,
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> {
if (resultSet.next()) {
perms[0] = resultSet.getString("perms");
return true;
}
return false;
}
);
if (!s) {
return false;
}
// Group exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
group.setNodes(nodes);
return true;
} finally {
group.getIoLock().unlock();
}
return false;
}
@Override
public boolean loadAllGroups() {
List<String> groups = new ArrayList<>();
boolean b = runQuery(GROUP_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String name = resultSet.getString("name");
groups.add(name);
}
return true;
});
if (!b) {
return false;
}
for (String g : groups) {
if (!loadGroup(g)) {
b = false;
}
}
if (b) {
GroupManager gm = plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
}
return b;
return false;
}
@Override
public boolean saveGroup(Group group) {
group.getIoLock().lock();
try {
String json = gson.toJson(exportToLegacy(group.getNodes()));
return runQuery(GROUP_UPDATE, preparedStatement -> {
preparedStatement.setString(1, json);
preparedStatement.setString(2, group.getName());
});
} finally {
group.getIoLock().unlock();
}
return false;
}
@Override
public boolean deleteGroup(Group group) {
group.getIoLock().lock();
boolean success;
try {
success = runQuery(GROUP_DELETE, preparedStatement -> {
preparedStatement.setString(1, group.getName());
});
} finally {
group.getIoLock().unlock();
}
if (success) plugin.getGroupManager().unload(group);
return success;
return false;
}
@Override
@@ -489,15 +364,16 @@ public class SQLBacking extends AbstractBacking {
Track track = plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
final boolean[] exists = {false};
final String[] groups = new String[1];
AtomicBoolean exists = new AtomicBoolean(false);
AtomicReference<String> groups = new AtomicReference<>(null);
boolean s = runQuery(TRACK_SELECT,
preparedStatement -> preparedStatement.setString(1, track.getName()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
groups[0] = resultSet.getString("groups");
boolean s = runQuery(
prefix.apply(TRACK_SELECT),
ps -> ps.setString(1, track.getName()),
rs -> {
if (rs.next()) {
exists.set(true);
groups.set(rs.getString("groups"));
}
return true;
}
@@ -507,9 +383,9 @@ public class SQLBacking extends AbstractBacking {
return false;
}
if (exists[0]) {
if (exists.get()) {
// Track exists, let's load.
track.setGroups(gson.fromJson(groups[0], T_TYPE));
track.setGroups(gson.fromJson(groups.get(), LIST_STRING_TYPE));
return true;
} else {
String json = gson.toJson(track.getGroups());
@@ -529,12 +405,13 @@ public class SQLBacking extends AbstractBacking {
Track track = plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
final String[] groups = {null};
boolean s = runQuery(TRACK_SELECT,
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> {
if (resultSet.next()) {
groups[0] = resultSet.getString("groups");
AtomicReference<String> groups = new AtomicReference<>(null);
boolean s = runQuery(
TRACK_SELECT,
ps -> ps.setString(1, name),
rs -> {
if (rs.next()) {
groups.set(rs.getString("groups"));
return true;
}
return false;
@@ -545,7 +422,7 @@ public class SQLBacking extends AbstractBacking {
return false;
}
track.setGroups(gson.fromJson(groups[0], T_TYPE));
track.setGroups(gson.fromJson(groups.get(), LIST_STRING_TYPE));
return true;
} finally {
@@ -556,13 +433,16 @@ public class SQLBacking extends AbstractBacking {
@Override
public boolean loadAllTracks() {
List<String> tracks = new ArrayList<>();
boolean b = runQuery(TRACK_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String name = resultSet.getString("name");
tracks.add(name);
}
return true;
});
boolean b = runQuery(
prefix.apply(TRACK_SELECT_ALL),
rs -> {
while (rs.next()) {
String name = rs.getString("name");
tracks.add(name);
}
return true;
}
);
if (!b) {
return false;
@@ -588,10 +468,13 @@ public class SQLBacking extends AbstractBacking {
track.getIoLock().lock();
try {
String s = gson.toJson(track.getGroups());
return runQuery(TRACK_UPDATE, preparedStatement -> {
preparedStatement.setString(1, s);
preparedStatement.setString(2, track.getName());
});
return runQuery(
prefix.apply(TRACK_UPDATE),
ps -> {
ps.setString(1, s);
ps.setString(2, track.getName());
}
);
} finally {
track.getIoLock().unlock();
}
@@ -602,9 +485,12 @@ public class SQLBacking extends AbstractBacking {
track.getIoLock().lock();
boolean success;
try {
success = runQuery(TRACK_DELETE, preparedStatement -> {
preparedStatement.setString(1, track.getName());
});
success = runQuery(
prefix.apply(TRACK_DELETE),
ps -> {
ps.setString(1, track.getName());
}
);
} finally {
track.getIoLock().unlock();
}
@@ -615,70 +501,16 @@ public class SQLBacking extends AbstractBacking {
@Override
public boolean saveUUIDData(String username, UUID uuid) {
final String u = username.toLowerCase();
final boolean[] update = {false};
boolean s = runQuery(UUIDCACHE_SELECT,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
update[0] = true;
}
return true;
}
);
if (!s) {
return false;
}
if (update[0]) {
return runQuery(UUIDCACHE_UPDATE, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
preparedStatement.setString(2, u);
});
} else {
return runQuery(UUIDCACHE_INSERT, preparedStatement -> {
preparedStatement.setString(1, u);
preparedStatement.setString(2, uuid.toString());
});
}
return false;
}
@Override
public UUID getUUID(String username) {
final String u = username.toLowerCase();
final UUID[] uuid = {null};
boolean success = runQuery(UUIDCACHE_SELECT,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
uuid[0] = UUID.fromString(resultSet.getString("uuid"));
return true;
}
return false;
}
);
return success ? uuid[0] : null;
return null;
}
@Override
public String getName(UUID uuid) {
final String u = uuid.toString();
final String[] name = {null};
boolean success = runQuery(UUIDCACHE_SELECT_NAME,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
name[0] = resultSet.getString("name");
return true;
}
return false;
}
);
return success ? name[0] : null;
return null;
}
}
@@ -0,0 +1,685 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.backing;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.common.LuckPermsPlugin;
import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Log;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.storage.backing.sqlprovider.H2Provider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.MySQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider;
import me.lucko.luckperms.common.storage.backing.sqlprovider.SQLiteProvider;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static me.lucko.luckperms.common.core.model.PermissionHolder.exportToLegacy;
import static me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider.QueryPS;
import static me.lucko.luckperms.common.storage.backing.sqlprovider.SQLProvider.QueryRS;
public class SQLLegacyBacking extends AbstractBacking {
private static final Type NM_TYPE = new TypeToken<Map<String, Boolean>>() {}.getType();
private static final Type T_TYPE = new TypeToken<List<String>>() {}.getType();
private static final String MYSQL_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String MYSQL_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String H2_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private static final String SQLITE_CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`));";
private static final String SQLITE_CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`));";
private static final String SQLITE_CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INTEGER PRIMARY KEY NOT NULL, `time` BIG INT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL);";
private static final Map<Class<? extends SQLProvider>, String[]> INIT_QUERIES = ImmutableMap.<Class<? extends SQLProvider>, String[]>builder()
.put(MySQLProvider.class, new String[]{MYSQL_CREATETABLE_UUID, MYSQL_CREATETABLE_USERS, MYSQL_CREATETABLE_GROUPS, MYSQL_CREATETABLE_TRACKS, MYSQL_CREATETABLE_ACTION})
.put(H2Provider.class, new String[]{H2_CREATETABLE_UUID, H2_CREATETABLE_USERS, H2_CREATETABLE_GROUPS, H2_CREATETABLE_TRACKS, H2_CREATETABLE_ACTION})
.put(SQLiteProvider.class, new String[]{SQLITE_CREATETABLE_UUID, SQLITE_CREATETABLE_USERS, SQLITE_CREATETABLE_GROUPS, SQLITE_CREATETABLE_TRACKS, SQLITE_CREATETABLE_ACTION})
.build();
private static final String USER_INSERT = "INSERT INTO lp_users VALUES(?, ?, ?, ?)";
private static final String USER_SELECT = "SELECT * FROM lp_users WHERE uuid=?";
private static final String USER_SELECT_ALL = "SELECT uuid FROM lp_users";
private static final String USER_UPDATE = "UPDATE lp_users SET name=?, primary_group = ?, perms=? WHERE uuid=?";
private static final String USER_DELETE = "DELETE FROM lp_users WHERE uuid=?";
private static final String USER_DELETE_ALL = "DELETE FROM lp_users WHERE perms=?";
private static final String GROUP_INSERT = "INSERT INTO lp_groups VALUES(?, ?)";
private static final String GROUP_SELECT = "SELECT perms FROM lp_groups WHERE name=?";
private static final String GROUP_SELECT_ALL = "SELECT * FROM lp_groups";
private static final String GROUP_UPDATE = "UPDATE lp_groups SET perms=? WHERE name=?";
private static final String GROUP_DELETE = "DELETE FROM lp_groups WHERE name=?";
private static final String TRACK_INSERT = "INSERT INTO lp_tracks VALUES(?, ?)";
private static final String TRACK_SELECT = "SELECT groups FROM lp_tracks WHERE name=?";
private static final String TRACK_SELECT_ALL = "SELECT * FROM lp_tracks";
private static final String TRACK_UPDATE = "UPDATE lp_tracks SET groups=? WHERE name=?";
private static final String TRACK_DELETE = "DELETE FROM lp_tracks WHERE name=?";
private static final String UUIDCACHE_INSERT = "INSERT INTO lp_uuid VALUES(?, ?)";
private static final String UUIDCACHE_SELECT = "SELECT uuid FROM lp_uuid WHERE name=?";
private static final String UUIDCACHE_SELECT_NAME = "SELECT name FROM lp_uuid WHERE uuid=?";
private static final String UUIDCACHE_UPDATE = "UPDATE lp_uuid SET uuid=? WHERE name=?";
private static final String ACTION_INSERT = "INSERT INTO lp_actions(`time`, `actor_uuid`, `actor_name`, `type`, `acted_uuid`, `acted_name`, `action`) VALUES(?, ?, ?, ?, ?, ?, ?)";
private static final String ACTION_SELECT_ALL = "SELECT * FROM lp_actions";
private final Gson gson;
private final SQLProvider provider;
public SQLLegacyBacking(LuckPermsPlugin plugin, SQLProvider provider) {
super(plugin, provider.getName());
this.provider = provider;
gson = new Gson();
}
private boolean runQuery(String query, QueryPS queryPS) {
return provider.runQuery(query, queryPS);
}
private boolean runQuery(String query, QueryPS queryPS, QueryRS queryRS) {
return provider.runQuery(query, queryPS, queryRS);
}
private boolean runQuery(String query) {
return provider.runQuery(query);
}
private boolean runQuery(String query, QueryRS queryRS) {
return provider.runQuery(query, queryRS);
}
private boolean setupTables(String[] tableQueries) {
boolean success = true;
for (String q : tableQueries) {
if (!runQuery(q)) success = false;
}
return success && cleanupUsers();
}
@Override
public void init() {
try {
provider.init();
if (!setupTables(INIT_QUERIES.get(provider.getClass()))) {
plugin.getLog().severe("Error occurred whilst initialising the database.");
shutdown();
} else {
setAcceptingLogins(true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void shutdown() {
try {
provider.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean logAction(LogEntry entry) {
return runQuery(ACTION_INSERT, preparedStatement -> {
preparedStatement.setLong(1, entry.getTimestamp());
preparedStatement.setString(2, entry.getActor().toString());
preparedStatement.setString(3, entry.getActorName());
preparedStatement.setString(4, Character.toString(entry.getType()));
preparedStatement.setString(5, entry.getActed() == null ? "null" : entry.getActed().toString());
preparedStatement.setString(6, entry.getActedName());
preparedStatement.setString(7, entry.getAction());
});
}
@Override
public Log getLog() {
final Log.Builder log = Log.builder();
boolean success = runQuery(ACTION_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
final String actedUuid = resultSet.getString("acted_uuid");
LogEntry e = new LogEntry(
resultSet.getLong("time"),
UUID.fromString(resultSet.getString("actor_uuid")),
resultSet.getString("actor_name"),
resultSet.getString("type").toCharArray()[0],
actedUuid.equals("null") ? null : UUID.fromString(actedUuid),
resultSet.getString("acted_name"),
resultSet.getString("action")
);
log.add(e);
}
return true;
});
return success ? log.build() : null;
}
@Override
public boolean loadUser(UUID uuid, String username) {
User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username));
user.getIoLock().lock();
try {
// screw "effectively final"
final String[] perms = new String[1];
final String[] pg = new String[1];
final String[] name = new String[1];
final boolean[] exists = {false};
boolean s = runQuery(USER_SELECT,
preparedStatement -> preparedStatement.setString(1, user.getUuid().toString()),
resultSet -> {
if (resultSet.next()) {
// User exists.
exists[0] = true;
perms[0] = resultSet.getString("perms");
pg[0] = resultSet.getString("primary_group");
name[0] = resultSet.getString("name");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// User exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
user.setNodes(nodes);
user.setPrimaryGroup(pg[0]);
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(name[0]);
} else {
if (!name[0].equals(user.getName())) {
save = true;
}
}
if (save) {
String json = gson.toJson(exportToLegacy(user.getNodes()));
runQuery(USER_UPDATE, preparedStatement -> {
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPrimaryGroup());
preparedStatement.setString(3, json);
preparedStatement.setString(4, user.getUuid().toString());
});
}
} else {
if (GenericUserManager.shouldSave(user)) {
user.clearNodes();
user.setPrimaryGroup(null);
plugin.getUserManager().giveDefaultIfNeeded(user, false);
}
}
return true;
} finally {
user.getIoLock().unlock();
user.getRefreshBuffer().requestDirectly();
}
}
@Override
public boolean saveUser(User user) {
if (!GenericUserManager.shouldSave(user)) {
user.getIoLock().lock();
try {
return runQuery(USER_DELETE, preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
});
} finally {
user.getIoLock().unlock();
}
// return true above ^^^^^
}
user.getIoLock().lock();
try {
final boolean[] exists = {false};
boolean success = runQuery(USER_SELECT,
preparedStatement -> preparedStatement.setString(1, user.getUuid().toString()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
}
return true;
}
);
if (!success) {
return false;
}
final String s = gson.toJson(exportToLegacy(user.getNodes()));
if (exists[0]) {
// User exists, let's update.
return runQuery(USER_UPDATE, preparedStatement -> {
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPrimaryGroup());
preparedStatement.setString(3, s);
preparedStatement.setString(4, user.getUuid().toString());
});
} else {
// Doesn't already exist, let's insert.
return runQuery(USER_INSERT, preparedStatement -> {
preparedStatement.setString(1, user.getUuid().toString());
preparedStatement.setString(2, user.getName());
preparedStatement.setString(3, user.getPrimaryGroup());
preparedStatement.setString(4, s);
});
}
} finally {
user.getIoLock().unlock();
}
}
@Override
public boolean cleanupUsers() {
return runQuery(USER_DELETE_ALL, preparedStatement -> {
preparedStatement.setString(1, "{\"group.default\":true}");
});
}
@Override
public Set<UUID> getUniqueUsers() {
Set<UUID> uuids = new HashSet<>();
boolean success = runQuery(USER_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String uuid = resultSet.getString("uuid");
uuids.add(UUID.fromString(uuid));
}
return true;
});
return success ? uuids : null;
}
@Override
public boolean createAndLoadGroup(String name) {
Group group = plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
final boolean[] exists = {false};
final String[] perms = new String[1];
boolean s = runQuery(GROUP_SELECT,
preparedStatement -> preparedStatement.setString(1, group.getName()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
perms[0] = resultSet.getString("perms");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// Group exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
group.setNodes(nodes);
return true;
} else {
String json = gson.toJson(exportToLegacy(group.getNodes()));
return runQuery(GROUP_INSERT, preparedStatement -> {
preparedStatement.setString(1, group.getName());
preparedStatement.setString(2, json);
});
}
} finally {
group.getIoLock().unlock();
}
}
@Override
public boolean loadGroup(String name) {
Group group = plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
final String[] perms = new String[1];
boolean s = runQuery(GROUP_SELECT,
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> {
if (resultSet.next()) {
perms[0] = resultSet.getString("perms");
return true;
}
return false;
}
);
if (!s) {
return false;
}
// Group exists, let's load.
Map<String, Boolean> nodes = gson.fromJson(perms[0], NM_TYPE);
group.setNodes(nodes);
return true;
} finally {
group.getIoLock().unlock();
}
}
@Override
public boolean loadAllGroups() {
List<String> groups = new ArrayList<>();
boolean b = runQuery(GROUP_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String name = resultSet.getString("name");
groups.add(name);
}
return true;
});
if (!b) {
return false;
}
for (String g : groups) {
if (!loadGroup(g)) {
b = false;
}
}
if (b) {
GroupManager gm = plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
}
return b;
}
@Override
public boolean saveGroup(Group group) {
group.getIoLock().lock();
try {
String json = gson.toJson(exportToLegacy(group.getNodes()));
return runQuery(GROUP_UPDATE, preparedStatement -> {
preparedStatement.setString(1, json);
preparedStatement.setString(2, group.getName());
});
} finally {
group.getIoLock().unlock();
}
}
@Override
public boolean deleteGroup(Group group) {
group.getIoLock().lock();
boolean success;
try {
success = runQuery(GROUP_DELETE, preparedStatement -> {
preparedStatement.setString(1, group.getName());
});
} finally {
group.getIoLock().unlock();
}
if (success) plugin.getGroupManager().unload(group);
return success;
}
@Override
public boolean createAndLoadTrack(String name) {
Track track = plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
final boolean[] exists = {false};
final String[] groups = new String[1];
boolean s = runQuery(TRACK_SELECT,
preparedStatement -> preparedStatement.setString(1, track.getName()),
resultSet -> {
if (resultSet.next()) {
exists[0] = true;
groups[0] = resultSet.getString("groups");
}
return true;
}
);
if (!s) {
return false;
}
if (exists[0]) {
// Track exists, let's load.
track.setGroups(gson.fromJson(groups[0], T_TYPE));
return true;
} else {
String json = gson.toJson(track.getGroups());
return runQuery(TRACK_INSERT, preparedStatement -> {
preparedStatement.setString(1, track.getName());
preparedStatement.setString(2, json);
});
}
} finally {
track.getIoLock().unlock();
}
}
@Override
public boolean loadTrack(String name) {
Track track = plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
final String[] groups = {null};
boolean s = runQuery(TRACK_SELECT,
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> {
if (resultSet.next()) {
groups[0] = resultSet.getString("groups");
return true;
}
return false;
}
);
if (!s) {
return false;
}
track.setGroups(gson.fromJson(groups[0], T_TYPE));
return true;
} finally {
track.getIoLock().unlock();
}
}
@Override
public boolean loadAllTracks() {
List<String> tracks = new ArrayList<>();
boolean b = runQuery(TRACK_SELECT_ALL, resultSet -> {
while (resultSet.next()) {
String name = resultSet.getString("name");
tracks.add(name);
}
return true;
});
if (!b) {
return false;
}
for (String t : tracks) {
if (!loadTrack(t)) {
b = false;
}
}
if (b) {
TrackManager tm = plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
}
return b;
}
@Override
public boolean saveTrack(Track track) {
track.getIoLock().lock();
try {
String s = gson.toJson(track.getGroups());
return runQuery(TRACK_UPDATE, preparedStatement -> {
preparedStatement.setString(1, s);
preparedStatement.setString(2, track.getName());
});
} finally {
track.getIoLock().unlock();
}
}
@Override
public boolean deleteTrack(Track track) {
track.getIoLock().lock();
boolean success;
try {
success = runQuery(TRACK_DELETE, preparedStatement -> {
preparedStatement.setString(1, track.getName());
});
} finally {
track.getIoLock().unlock();
}
if (success) plugin.getTrackManager().unload(track);
return success;
}
@Override
public boolean saveUUIDData(String username, UUID uuid) {
final String u = username.toLowerCase();
final boolean[] update = {false};
boolean s = runQuery(UUIDCACHE_SELECT,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
update[0] = true;
}
return true;
}
);
if (!s) {
return false;
}
if (update[0]) {
return runQuery(UUIDCACHE_UPDATE, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
preparedStatement.setString(2, u);
});
} else {
return runQuery(UUIDCACHE_INSERT, preparedStatement -> {
preparedStatement.setString(1, u);
preparedStatement.setString(2, uuid.toString());
});
}
}
@Override
public UUID getUUID(String username) {
final String u = username.toLowerCase();
final UUID[] uuid = {null};
boolean success = runQuery(UUIDCACHE_SELECT,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
uuid[0] = UUID.fromString(resultSet.getString("uuid"));
return true;
}
return false;
}
);
return success ? uuid[0] : null;
}
@Override
public String getName(UUID uuid) {
final String u = uuid.toString();
final String[] name = {null};
boolean success = runQuery(UUIDCACHE_SELECT_NAME,
preparedStatement -> preparedStatement.setString(1, u),
resultSet -> {
if (resultSet.next()) {
name[0] = resultSet.getString("name");
return true;
}
return false;
}
);
return success ? name[0] : null;
}
}
@@ -77,6 +77,7 @@ public class MySQLProvider extends SQLProvider {
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(10)); // 10000
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(5)); // 5000
config.setValidationTimeout(TimeUnit.SECONDS.toMillis(3)); // 3000
config.setInitializationFailFast(true);
config.setConnectionTestQuery("/* LuckPerms ping */ SELECT 1");
hikari = new HikariDataSource(config);
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.backing.sqlprovider;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import me.lucko.luckperms.common.storage.DatastoreConfiguration;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
public class PostgreSQLProvider extends SQLProvider {
private final DatastoreConfiguration configuration;
private HikariDataSource hikari;
public PostgreSQLProvider(DatastoreConfiguration configuration) {
super("PostgreSQL");
this.configuration = configuration;
}
@Override
public void init() throws Exception {
HikariConfig config = new HikariConfig();
String address = configuration.getAddress();
String[] addressSplit = address.split(":");
address = addressSplit[0];
String port = addressSplit.length > 1 ? addressSplit[1] : "3306";
String database = configuration.getDatabase();
String username = configuration.getUsername();
String password = configuration.getPassword();
config.setMaximumPoolSize(configuration.getPoolSize());
config.setPoolName("luckperms");
config.setDataSourceClassName("org.postgresql.ds.PGSimpleDataSource");
config.addDataSourceProperty("serverName", address);
config.addDataSourceProperty("portNumber", port);
config.addDataSourceProperty("databaseName", database);
config.addDataSourceProperty("user", username);
config.addDataSourceProperty("password", password);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(10)); // 10000
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(5)); // 5000
config.setValidationTimeout(TimeUnit.SECONDS.toMillis(3)); // 3000
config.setInitializationFailFast(true);
config.setConnectionTestQuery("/* LuckPerms ping */ SELECT 1");
hikari = new HikariDataSource(config);
}
@Override
public void shutdown() throws Exception {
if (hikari != null) {
hikari.close();
}
}
@Override
public Connection getConnection() throws SQLException {
return hikari.getConnection();
}
@Override
public boolean runQuery(String query, QueryPS queryPS) {
boolean success = false;
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = getConnection();
if (connection == null || connection.isClosed()) {
throw new IllegalStateException("SQL connection is null");
}
preparedStatement = connection.prepareStatement(query);
queryPS.onRun(preparedStatement);
preparedStatement.execute();
success = true;
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(preparedStatement);
close(connection);
}
return success;
}
@Override
public boolean runQuery(String query, QueryPS queryPS, QueryRS queryRS) {
boolean success = false;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
if (connection == null || connection.isClosed()) {
throw new IllegalStateException("SQL connection is null");
}
preparedStatement = connection.prepareStatement(query);
queryPS.onRun(preparedStatement);
resultSet = preparedStatement.executeQuery();
success = queryRS.onResult(resultSet);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(resultSet);
close(preparedStatement);
close(connection);
}
return success;
}
}
@@ -64,7 +64,6 @@ public abstract class SQLProvider {
return runQuery(query, EMPTY_PS, queryRS);
}
@FunctionalInterface
public interface QueryPS {
void onRun(PreparedStatement preparedStatement) throws SQLException;
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.backing.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import me.lucko.luckperms.api.Node;
@Getter
@ToString
@AllArgsConstructor(staticName = "of")
public class NodeDataHolder {
public static NodeDataHolder fromNode(Node node) {
// TODO
return null;
}
private final String permission;
private final boolean value;
private final String server;
private final String world;
private final long expiry;
private final String contexts;
public Node toNode() {
// TODO
return null;
}
}