Database support

This commit is contained in:
2018-10-01 07:00:28 +02:00
parent 84138aea05
commit e97a43c9e2
12 changed files with 397 additions and 11 deletions

View File

@@ -4,13 +4,20 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import cz.marwland.mc.core.config.ConfigManager;
import cz.marwland.mc.core.features.Feature;
import cz.marwland.mc.core.features.ModuleClassLoader;
import cz.marwland.mc.core.storage.SQLStorage;
import cz.marwland.mc.core.storage.StorageCredentials;
import cz.marwland.mc.core.storage.impl.MariaDbConnectionFactory;
import cz.marwland.mc.core.storage.impl.MySqlConnectionFactory;
import cz.marwland.mc.core.util.ConfigUtil;
public class MarwCore extends JavaPlugin {
@@ -19,6 +26,7 @@ public class MarwCore extends JavaPlugin {
private static MarwCore INSTANCE = null;
private File modulesFolder = null;
private ModuleClassLoader moduleClassLoader;
private SQLStorage sqlStorage;
@Override
public void onEnable() {
@@ -28,6 +36,7 @@ public class MarwCore extends JavaPlugin {
configManager = new ConfigManager(this);
configManager.registerConfig("config.yml");
configManager.loadConfig("config.yml");
this.loadDatabase();
modulesFolder = this.getModulesFolderPath().toFile();
modulesFolder.mkdirs();
@@ -38,6 +47,7 @@ public class MarwCore extends JavaPlugin {
public void onDisable() {
this.features.forEach((k, v) -> v.onDisable());
this.configManager.save();
this.sqlStorage.shutdown();
}
@Override
@@ -48,6 +58,7 @@ public class MarwCore extends JavaPlugin {
public void reload() {
this.reloadConfig();
this.features.forEach((k, v) -> v.onDisable());
this.loadDatabase();
this.loadAndEnableModules();
}
@@ -107,4 +118,43 @@ public class MarwCore extends JavaPlugin {
return this.moduleClassLoader;
}
public void loadDatabase() {
if (this.sqlStorage != null)
this.sqlStorage.shutdown();
FileConfiguration cfg = this.getConfigManager().getConfig("config.yml");
Map<String, String> props = ConfigUtil.getStringMap(cfg, "data.pool-settings.properties");
if (props == null)
props = new HashMap<>();
StorageCredentials creds = new StorageCredentials(
cfg.getString("dataSource.address"),
cfg.getString("dataSource.database"),
cfg.getString("dataSource.username"),
cfg.getString("dataSource.password"),
cfg.getString("dataSource.table-prefix"),
cfg.getInt("dataSource.pool-settings.maximum-pool-size"),
cfg.getInt("dataSource.pool-settings.minimum-idle"),
cfg.getInt("dataSource.pool-settings.maximum-lifetime"),
cfg.getInt("dataSource.pool-settings.connection-timeout"),
props
);
String method = cfg.getString("method");
if (method.equalsIgnoreCase("mariadb")) {
this.sqlStorage = new SQLStorage(new MariaDbConnectionFactory(creds));
} else if (method.equalsIgnoreCase("mysql")) {
this.sqlStorage = new SQLStorage(new MySqlConnectionFactory(creds));
} else {
this.getLogger().log(Level.CONFIG, "Invalid dataSource.method in config.yml: " + method);
return;
}
sqlStorage.init();
}
public SQLStorage getStorage() {
return this.sqlStorage;
}
}

View File

@@ -0,0 +1,18 @@
package cz.marwland.mc.core.storage;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Function;
public interface ConnectionFactory {
void init();
void shutdown();
Connection getConnection() throws SQLException;
Function<String, String> getStatementProcessor();
StorageCredentials getConfiguration();
}

View File

@@ -0,0 +1,84 @@
package cz.marwland.mc.core.storage;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public abstract class HikariConnectionFactory implements ConnectionFactory {
protected final StorageCredentials configuration;
private HikariDataSource hikari;
public HikariConnectionFactory(StorageCredentials configuration) {
this.configuration = configuration;
}
protected String getDriverClass() {
return null;
}
protected void appendProperties(HikariConfig config, StorageCredentials credentials) {
for (Map.Entry<String, String> property : credentials.getProperties().entrySet()) {
config.addDataSourceProperty(property.getKey(), property.getValue());
}
}
protected void appendConfigurationInfo(HikariConfig config) {
String address = this.configuration.getAddress();
String[] addressSplit = address.split(":");
address = addressSplit[0];
String port = addressSplit.length > 1 ? addressSplit[1] : "3306";
config.setDataSourceClassName(getDriverClass());
config.addDataSourceProperty("serverName", address);
config.addDataSourceProperty("port", port);
config.addDataSourceProperty("databaseName", this.configuration.getDatabase());
config.setUsername(this.configuration.getUsername());
config.setPassword(this.configuration.getPassword());
}
@Override
public void init() {
HikariConfig config = new HikariConfig();
config.setPoolName("luckperms-hikari");
appendConfigurationInfo(config);
appendProperties(config, this.configuration);
config.setMaximumPoolSize(this.configuration.getMaxPoolSize());
config.setMinimumIdle(this.configuration.getMinIdleConnections());
config.setMaxLifetime(this.configuration.getMaxLifetime());
config.setConnectionTimeout(this.configuration.getConnectionTimeout());
// don't perform any initial connection validation - we subsequently call #getConnection
// to setup the schema anyways
config.setInitializationFailTimeout(-1);
this.hikari = new HikariDataSource(config);
}
@Override
public void shutdown() {
if (this.hikari != null) {
this.hikari.close();
}
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = this.hikari.getConnection();
if (connection == null) {
throw new SQLException("Unable to get a connection from the pool.");
}
return connection;
}
@Override
public StorageCredentials getConfiguration() {
return this.configuration;
}
}

View File

@@ -0,0 +1,40 @@
package cz.marwland.mc.core.storage;
import java.util.function.Function;
public class SQLStorage {
private final ConnectionFactory connectionFactory;
private final StorageCredentials configuration;
private final Function<String, String> statementProcessor;
public SQLStorage(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
this.configuration = connectionFactory.getConfiguration();
this.statementProcessor = connectionFactory.getStatementProcessor().compose(s -> s.replace("{prefix}", this.configuration.getTablePrefix()));
}
public ConnectionFactory getConnectionFactory() {
return this.connectionFactory;
}
public StorageCredentials getConfiguration() {
return this.configuration;
}
public Function<String, String> getStatementProcessor() {
return this.statementProcessor;
}
public void init() {
this.connectionFactory.init();
}
public void shutdown() {
try {
this.connectionFactory.shutdown();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

View File

@@ -0,0 +1,70 @@
package cz.marwland.mc.core.storage;
import java.util.Map;
public class StorageCredentials {
private final String address;
private final String database;
private final String username;
private final String password;
private final String tablePrefix;
private final int maxPoolSize;
private final int minIdleConnections;
private final int maxLifetime;
private final int connectionTimeout;
private final Map<String, String> properties;
public StorageCredentials(String address, String database, String username, String password, String tablePrefix, int maxPoolSize, int minIdleConnections, int maxLifetime, int connectionTimeout, Map<String, String> properties) {
this.address = address;
this.database = database;
this.username = username;
this.password = password;
this.tablePrefix = tablePrefix;
this.maxPoolSize = maxPoolSize;
this.minIdleConnections = minIdleConnections;
this.maxLifetime = maxLifetime;
this.connectionTimeout = connectionTimeout;
this.properties = properties;
}
public String getAddress() {
return this.address;
}
public String getDatabase() {
return this.database;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public String getTablePrefix() {
return this.tablePrefix;
}
public int getMaxPoolSize() {
return this.maxPoolSize;
}
public int getMinIdleConnections() {
return this.minIdleConnections;
}
public int getMaxLifetime() {
return this.maxLifetime;
}
public int getConnectionTimeout() {
return this.connectionTimeout;
}
public Map<String, String> getProperties() {
return this.properties;
}
}

View File

@@ -0,0 +1,45 @@
package cz.marwland.mc.core.storage.impl;
import com.zaxxer.hikari.HikariConfig;
import cz.marwland.mc.core.storage.HikariConnectionFactory;
import cz.marwland.mc.core.storage.StorageCredentials;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MariaDbConnectionFactory extends HikariConnectionFactory {
public MariaDbConnectionFactory(StorageCredentials configuration) {
super(configuration);
}
@Override
protected String getDriverClass() {
return "org.mariadb.jdbc.MariaDbDataSource";
}
@Override
protected void appendProperties(HikariConfig config, StorageCredentials credentials) {
Set<Map.Entry<String, String>> properties = credentials.getProperties().entrySet();
if (properties.isEmpty()) {
return;
}
String propertiesString = properties.stream().map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(";"));
// kinda hacky. this will call #setProperties on the datasource, which will
// append these options
// onto the connections.
config.addDataSourceProperty("properties", propertiesString);
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace("'", "`"); // use backticks for quotes
}
}

View File

@@ -0,0 +1,43 @@
package cz.marwland.mc.core.storage.impl;
import java.util.function.Function;
import com.zaxxer.hikari.HikariConfig;
import cz.marwland.mc.core.storage.HikariConnectionFactory;
import cz.marwland.mc.core.storage.StorageCredentials;
public class MySqlConnectionFactory extends HikariConnectionFactory {
public MySqlConnectionFactory(StorageCredentials configuration) {
super(configuration);
}
@Override
protected String getDriverClass() {
return "com.mysql.jdbc.jdbc2.optional.MysqlDataSource";
}
@Override
protected void appendProperties(HikariConfig config, StorageCredentials credentials) {
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("alwaysSendSetIsolation", "false");
config.addDataSourceProperty("cacheServerConfiguration", "true");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("cacheCallableStmts", "true");
// append configurable properties
super.appendProperties(config, credentials);
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace("'", "`"); // use backticks for quotes
}
}

View File

@@ -0,0 +1,25 @@
package cz.marwland.mc.core.util;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
public class ConfigUtil {
public static Map<String, String> getStringMap(FileConfiguration cfg, String path) {
Map<String, String> map = new HashMap<>();
ConfigurationSection section = cfg.getConfigurationSection(path);
if (section == null)
return null;
for (String key : section.getKeys(false)) {
map.put(key, section.getString(key));
}
return map;
}
}