Database support

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

View File

@ -4,6 +4,9 @@
<classpathentry kind="src" path="resources" excluding="**/*.java"/>
<classpathentry kind="output" path="target/classes"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="M2_REPO/me/lucko/luckperms/luckperms-api/4.2/luckperms-api-4.2.jar" sourcepath="M2_REPO/me/lucko/luckperms/luckperms-api/4.2/luckperms-api-4.2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/com/zaxxer/HikariCP/3.2.0/HikariCP-3.2.0.jar" sourcepath="M2_REPO/com/zaxxer/HikariCP/3.2.0/HikariCP-3.2.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar" sourcepath="M2_REPO/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/spigotmc/spigot-api/1.12-R0.1-SNAPSHOT/spigot-api-1.12-R0.1-SNAPSHOT.jar" sourcepath="M2_REPO/org/spigotmc/spigot-api/1.12-R0.1-SNAPSHOT/spigot-api-1.12-R0.1-SNAPSHOT-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/commons-lang/commons-lang/2.6/commons-lang-2.6.jar" sourcepath="M2_REPO/commons-lang/commons-lang/2.6/commons-lang-2.6-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar" sourcepath="M2_REPO/com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1-sources.jar"/>
@ -13,10 +16,4 @@
<classpathentry kind="var" path="M2_REPO/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar" sourcepath="M2_REPO/com/google/code/gson/gson/2.8.0/gson-2.8.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/yaml/snakeyaml/1.18/snakeyaml-1.18.jar" sourcepath="M2_REPO/org/yaml/snakeyaml/1.18/snakeyaml-1.18-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/net/md-5/bungeecord-chat/1.12-SNAPSHOT/bungeecord-chat-1.12-SNAPSHOT.jar" sourcepath="M2_REPO/net/md-5/bungeecord-chat/1.12-SNAPSHOT/bungeecord-chat-1.12-SNAPSHOT-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/me/lucko/luckperms/luckperms-api/4.2/luckperms-api-4.2.jar" sourcepath="M2_REPO/me/lucko/luckperms/luckperms-api/4.2/luckperms-api-4.2-sources.jar"/>
<classpathentry kind="src" path="/BountifulAPI"/>
<classpathentry kind="var" path="M2_REPO/commons-io/commons-io/1.3.2/commons-io-1.3.2.jar" sourcepath="M2_REPO/commons-io/commons-io/1.3.2/commons-io-1.3.2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/bukkit/bukkit/1.13-R0.1-SNAPSHOT/bukkit-1.13-R0.1-SNAPSHOT.jar" sourcepath="M2_REPO/org/bukkit/bukkit/1.13-R0.1-SNAPSHOT/bukkit-1.13-R0.1-SNAPSHOT-sources.jar"/>
<classpathentry kind="lib" path="/home/erik/Dokumenty/Java/marwland/res/MassiveCore.jar"/>
<classpathentry kind="lib" path="/home/erik/Dokumenty/Java/marwland/res/Factions.jar"/>
</classpath>

View File

@ -2,9 +2,7 @@
<projectDescription>
<name>MarwCore</name>
<comment>Core plugin for MarwLand.cz Spigot-based Minecraft servers. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
<projects>
<project>BountifulAPI</project>
</projects>
<projects/>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>

View File

@ -58,8 +58,8 @@
<artifactId>luckperms-api</artifactId>
</dependency>
<dependency>
<groupId>com.connorlinfoot</groupId>
<artifactId>BountifulAPI</artifactId>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,2 +1,18 @@
#modules:
# - cz.marwland.mc.features.Borders
dataSource:
method: 'mariadb'
address: 'localhost'
database: 'factions'
username: 'root'
password: ''
table-prefix: 'f_'
pool-settings:
maximum-pool-size: 10
minimum-idle: 10
maximum-lifetime: 1800000 # 30 minutes
connection-timeout: 5000 # 5 seconds
properties:
useUnicode: true
characterEncoding: utf8

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;
}
}