diff --git a/.classpath b/.classpath
index 4bdc242..9af01fe 100644
--- a/.classpath
+++ b/.classpath
@@ -4,6 +4,9 @@
+
+
+
@@ -13,10 +16,4 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.project b/.project
index 810a46b..a46745a 100644
--- a/.project
+++ b/.project
@@ -2,9 +2,7 @@
MarwCore
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.
-
- BountifulAPI
-
+
org.eclipse.buildship.core.gradleprojectbuilder
diff --git a/pom.xml b/pom.xml
index 8038b51..05f7d20 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,8 +58,8 @@
luckperms-api
- com.connorlinfoot
- BountifulAPI
+ com.zaxxer
+ HikariCP
\ No newline at end of file
diff --git a/resources/config.yml b/resources/config.yml
index dc85c86..7accd47 100644
--- a/resources/config.yml
+++ b/resources/config.yml
@@ -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
diff --git a/src/cz/marwland/mc/core/MarwCore.java b/src/cz/marwland/mc/core/MarwCore.java
index 3205a97..927f570 100644
--- a/src/cz/marwland/mc/core/MarwCore.java
+++ b/src/cz/marwland/mc/core/MarwCore.java
@@ -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 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;
+ }
+
}
diff --git a/src/cz/marwland/mc/core/storage/ConnectionFactory.java b/src/cz/marwland/mc/core/storage/ConnectionFactory.java
new file mode 100644
index 0000000..fc9f31d
--- /dev/null
+++ b/src/cz/marwland/mc/core/storage/ConnectionFactory.java
@@ -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 getStatementProcessor();
+
+ StorageCredentials getConfiguration();
+
+}
diff --git a/src/cz/marwland/mc/core/storage/HikariConnectionFactory.java b/src/cz/marwland/mc/core/storage/HikariConnectionFactory.java
new file mode 100644
index 0000000..6b5d598
--- /dev/null
+++ b/src/cz/marwland/mc/core/storage/HikariConnectionFactory.java
@@ -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 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;
+ }
+
+}
diff --git a/src/cz/marwland/mc/core/storage/SQLStorage.java b/src/cz/marwland/mc/core/storage/SQLStorage.java
new file mode 100644
index 0000000..278a4c1
--- /dev/null
+++ b/src/cz/marwland/mc/core/storage/SQLStorage.java
@@ -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 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 getStatementProcessor() {
+ return this.statementProcessor;
+ }
+
+ public void init() {
+ this.connectionFactory.init();
+ }
+
+ public void shutdown() {
+ try {
+ this.connectionFactory.shutdown();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+}
diff --git a/src/cz/marwland/mc/core/storage/StorageCredentials.java b/src/cz/marwland/mc/core/storage/StorageCredentials.java
new file mode 100644
index 0000000..06902c7
--- /dev/null
+++ b/src/cz/marwland/mc/core/storage/StorageCredentials.java
@@ -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 properties;
+
+ public StorageCredentials(String address, String database, String username, String password, String tablePrefix, int maxPoolSize, int minIdleConnections, int maxLifetime, int connectionTimeout, Map 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 getProperties() {
+ return this.properties;
+ }
+
+}
diff --git a/src/cz/marwland/mc/core/storage/impl/MariaDbConnectionFactory.java b/src/cz/marwland/mc/core/storage/impl/MariaDbConnectionFactory.java
new file mode 100644
index 0000000..bebab46
--- /dev/null
+++ b/src/cz/marwland/mc/core/storage/impl/MariaDbConnectionFactory.java
@@ -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> 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 getStatementProcessor() {
+ return s -> s.replace("'", "`"); // use backticks for quotes
+ }
+
+}
\ No newline at end of file
diff --git a/src/cz/marwland/mc/core/storage/impl/MySqlConnectionFactory.java b/src/cz/marwland/mc/core/storage/impl/MySqlConnectionFactory.java
new file mode 100644
index 0000000..a5b19e0
--- /dev/null
+++ b/src/cz/marwland/mc/core/storage/impl/MySqlConnectionFactory.java
@@ -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 getStatementProcessor() {
+ return s -> s.replace("'", "`"); // use backticks for quotes
+ }
+
+}
diff --git a/src/cz/marwland/mc/core/util/ConfigUtil.java b/src/cz/marwland/mc/core/util/ConfigUtil.java
new file mode 100644
index 0000000..32dc46b
--- /dev/null
+++ b/src/cz/marwland/mc/core/util/ConfigUtil.java
@@ -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 getStringMap(FileConfiguration cfg, String path) {
+ Map 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;
+ }
+
+}