lines = new ArrayList<>();
+
+ synchronized (databases) {
+ for (AbstractDatabase database : databases)
+ try {
+ for (String line : database.getSetupLines())
+ lines.add(line);
+ } catch (IOException e) {
+ logger.error("Error while reading setup lines from " + database.getName(), e);
+ }
+ }
+
+ return lines;
+ }
+}
diff --git a/src/net/foxgenesis/database/IDatabaseManager.java b/src/net/foxgenesis/database/IDatabaseManager.java
new file mode 100644
index 0000000..27ff8c2
--- /dev/null
+++ b/src/net/foxgenesis/database/IDatabaseManager.java
@@ -0,0 +1,19 @@
+package net.foxgenesis.database;
+
+import java.io.IOException;
+
+public interface IDatabaseManager extends AutoCloseable {
+ /**
+ * Check if all guild data has been processed and is ready for use.
+ *
+ * @return Returns {@code true} when all data has been loaded from the database
+ * @see #isConnectionValid()
+ */
+ public boolean isReady();
+
+ boolean register(AbstractDatabase database) throws IOException;
+
+ boolean isDatabaseRegistered(AbstractDatabase database);
+
+ String getName();
+}
diff --git a/src/net/foxgenesis/database/providers/MySQLConnectionProvider.java b/src/net/foxgenesis/database/providers/MySQLConnectionProvider.java
new file mode 100644
index 0000000..62be350
--- /dev/null
+++ b/src/net/foxgenesis/database/providers/MySQLConnectionProvider.java
@@ -0,0 +1,37 @@
+package net.foxgenesis.database.providers;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import net.foxgenesis.database.AConnectionProvider;
+
+public class MySQLConnectionProvider extends AConnectionProvider {
+
+ private final DataSource source;
+
+ public MySQLConnectionProvider(Properties properties) {
+ super(properties, "MySQL Connection Provider");
+
+ properties.putIfAbsent("dataSource.cachePrepStmts", true);
+ properties.putIfAbsent("dataSource.prepStmtCacheSize", 250);
+ properties.putIfAbsent("dataSource.prepStmtCacheSqlLimit", 2048);
+ properties.putIfAbsent("dataSource.useServerPrepStmts", true);
+ properties.putIfAbsent("dataSource.useLocalSessionState", true);
+ properties.putIfAbsent("dataSource.rewriteBatchedStatements", true);
+ properties.putIfAbsent("dataSource.cacheResultSetMetadata", true);
+ properties.putIfAbsent("dataSource.cacheServerConfiguration", true);
+ properties.putIfAbsent("dataSource.elideSetAutoCommits", false);
+ properties.putIfAbsent("dataSource.maintainTimeStats", true);
+
+ source = new HikariDataSource(new HikariConfig(properties));
+ }
+
+ @Override
+ protected Connection openConnection() throws SQLException { return source.getConnection(); }
+}
diff --git a/src/net/foxgenesis/util/ResourceUtils.java b/src/net/foxgenesis/util/ResourceUtils.java
index b1e1932..3eff219 100644
--- a/src/net/foxgenesis/util/ResourceUtils.java
+++ b/src/net/foxgenesis/util/ResourceUtils.java
@@ -5,6 +5,10 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
@@ -58,6 +62,18 @@ public static Properties getPropertiesResource(URL path) throws IOException {
return properties;
}
+
+ public static Properties getProperties(Path path, ModuleResource defaults) throws IOException {
+ // If file does not exist, create a new one and try to open it again
+ if(Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) {
+ defaults.writeToFile(path);
+ return getProperties(path, defaults);
+ }
+
+ Properties properties = new Properties();
+ properties.load(Files.newInputStream(path, StandardOpenOption.READ));
+ return properties;
+ }
public static String toString(@Nonnull InputStream input) throws IOException {
Objects.requireNonNull(input, "InputStream must not be null!");
@@ -78,6 +94,10 @@ public InputStream openStream() throws IOException {
logger.trace("Attempting to read resource: " + resourcePath);
return module.getResourceAsStream(resourcePath);
}
+
+ public void writeToFile(Path path) throws IOException {
+ Files.copy(openStream(), path);
+ }
public String readToString() throws IOException { return ResourceUtils.toString(openStream()); }
diff --git a/src/net/foxgenesis/util/function/QuadFunction.java b/src/net/foxgenesis/util/function/QuadFunction.java
new file mode 100644
index 0000000..d4044cf
--- /dev/null
+++ b/src/net/foxgenesis/util/function/QuadFunction.java
@@ -0,0 +1,49 @@
+package net.foxgenesis.util.function;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Represents a function that accepts two arguments and produces a result.
+ * This is the two-arity specialization of {@link Function}.
+ *
+ * This is a functional interface
+ * whose functional method is {@link #apply(Object, Object)}.
+ *
+ * @param the type of the first argument to the function
+ * @param the type of the second argument to the function
+ * @param the type of the result of the function
+ *
+ * @see Function
+ * @since 1.8
+ */
+@FunctionalInterface
+public interface QuadFunction {
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @param u the second function argument
+ * @return the function result
+ */
+ R apply(T t, U u, V v, W w);
+
+ /**
+ * Returns a composed function that first applies this function to
+ * its input, and then applies the {@code after} function to the result.
+ * If evaluation of either function throws an exception, it is relayed to
+ * the caller of the composed function.
+ *
+ * @param the type of output of the {@code after} function, and of the
+ * composed function
+ * @param after the function to apply after this function is applied
+ * @return a composed function that first applies this function and then
+ * applies the {@code after} function
+ * @throws NullPointerException if after is null
+ */
+ default QuadFunction andThen(Function super R, ? extends X> after) {
+ Objects.requireNonNull(after);
+ return (T t, U u, V v, W w) -> after.apply(apply(t, u, v, w));
+ }
+}
diff --git a/src/net/foxgenesis/watame/Main.java b/src/net/foxgenesis/watame/Main.java
index ce1037c..39c5d96 100644
--- a/src/net/foxgenesis/watame/Main.java
+++ b/src/net/foxgenesis/watame/Main.java
@@ -22,7 +22,7 @@ public class Main {
/**
* Global logger
*/
- public static final Logger logger = LoggerFactory.getLogger(Main.class);
+ public static final Logger logger = LoggerFactory.getLogger("WatameBot Startup");
/**
* Program arguments
diff --git a/src/net/foxgenesis/watame/WatameBot.java b/src/net/foxgenesis/watame/WatameBot.java
index 9228496..5e7ef89 100644
--- a/src/net/foxgenesis/watame/WatameBot.java
+++ b/src/net/foxgenesis/watame/WatameBot.java
@@ -4,13 +4,12 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
+import java.nio.file.Path;
import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
@@ -37,15 +36,20 @@
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.dv8tion.jda.internal.utils.IOUtil;
+import net.foxgenesis.database.DatabaseManager;
+import net.foxgenesis.database.IDatabaseManager;
+import net.foxgenesis.database.providers.MySQLConnectionProvider;
import net.foxgenesis.property.IPropertyProvider;
import net.foxgenesis.util.ProgramArguments;
+import net.foxgenesis.util.ResourceUtils;
+import net.foxgenesis.util.ResourceUtils.ModuleResource;
import net.foxgenesis.watame.plugin.IPlugin;
import net.foxgenesis.watame.plugin.SeverePluginException;
import net.foxgenesis.watame.plugin.UntrustedPluginLoader;
import net.foxgenesis.watame.property.GuildPropertyProvider;
import net.foxgenesis.watame.property.IGuildPropertyMapping;
-import net.foxgenesis.watame.sql.DataManager;
-import net.foxgenesis.watame.sql.IDatabaseManager;
+import net.foxgenesis.watame.sql.IGuildData;
+import net.foxgenesis.watame.sql.WatameBotDatabase;
/**
* Class containing WatameBot implementation
@@ -139,10 +143,12 @@ private static String readToken(String filepath) {
*/
private JDA discord;
+ private final DatabaseManager manager;
+
/**
* Database connection handler
*/
- private final DataManager database;
+ private final WatameBotDatabase database;
/**
* Property provider
@@ -162,7 +168,7 @@ private static String readToken(String filepath) {
/**
* List of all plugins
*/
- private Collection plugins = new ArrayList<>();
+ private ConcurrentHashMap plugins = new ConcurrentHashMap<>();
/**
* Create a new instance with a specified login {@code token}.
@@ -182,8 +188,11 @@ private WatameBot(@Nonnull String token) throws SQLException {
// Load plugins
loader = new UntrustedPluginLoader<>(IPlugin.class);
+ // Create our database manager
+ manager = new DatabaseManager("WatameBot Database Manager");
+
// Connect to our database file
- database = new DataManager();
+ database = new WatameBotDatabase();
// Create connection to discord through our token
builder = createJDA(token);
@@ -194,7 +203,7 @@ private WatameBot(@Nonnull String token) throws SQLException {
void start() {
logger.info(state.marker, "Starting...");
- plugins.addAll(loader.getPlugins());
+ loader.getPlugins().forEach(plugin -> plugins.put(plugin.getName(), plugin));
logger.debug(state.marker, "Found {} plugins", plugins.size());
preInit();
@@ -214,7 +223,7 @@ private void shutdown() {
// Close all plugins
logger.debug(state.marker, "Closing all pugins");
- plugins.forEach(plugin -> IOUtil.silentClose(plugin));
+ plugins.values().forEach(plugin -> IOUtil.silentClose(plugin));
// Await all futures to complete
if (!ForkJoinPool.commonPool().awaitQuiescence(3, TimeUnit.MINUTES))
@@ -252,29 +261,22 @@ private void preInit() {
// Pre-initialize all plugins async
logger.debug(state.marker, "Calling plugin pre-initialization async");
- CompletableFuture pluginPreInit = CompletableFuture.allOf(List.copyOf(plugins).stream()
+ CompletableFuture pluginPreInit = CompletableFuture.allOf(plugins.values().stream()
.map(plugin -> CompletableFuture.runAsync(plugin::preInit).exceptionallyAsync(error -> {
pluginError(plugin, error, state.marker);
return null;
})).toArray(CompletableFuture[]::new));
- // Setup and connect to the database
+ // Setup the database
try {
- logger.debug(state.marker, "Connecting to database");
- database.connect();
- database.retrieveDatabaseData(null);
+ logger.debug(state.marker, "Adding database to database manager");
+ manager.register(database);
} catch (IOException e) {
// Some error occurred while setting up database
ExitCode.DATABASE_SETUP_ERROR.programExit(e);
} catch (IllegalArgumentException e) {
// Resource was null
ExitCode.DATABASE_INVALID_SETUP_FILE.programExit(e);
- } catch (UnsupportedOperationException e) {
- // Unable to connect to database
- ExitCode.DATABASE_NOT_CONNECTED.programExit(e);
- } catch (SQLException e) {
- // Error while accessing database
- ExitCode.DATABASE_ACCESS_ERROR.programExit(e);
}
/*
@@ -303,18 +305,30 @@ private void init() {
// Initialize all plugins
logger.debug(state.marker, "Calling plugin initialization async");
ProtectedJDABuilder pBuilder = new ProtectedJDABuilder(builder);
- CompletableFuture pluginInit = CompletableFuture.allOf(List.copyOf(plugins).stream()
+ CompletableFuture pluginInit = CompletableFuture.allOf(plugins.values().stream()
.map(plugin -> CompletableFuture.runAsync(() -> plugin.init(pBuilder)).exceptionallyAsync(error -> {
pluginError(plugin, error, state.marker);
return null;
})).toArray(CompletableFuture[]::new));
+ // Start databases
+ try {
+ logger.info(state.marker, "Starting database pool");
+ manager.start(
+ new MySQLConnectionProvider(ResourceUtils.getProperties(Path.of("config", "database.properties"),
+ new ModuleResource("watamebot", "defaults/database.properties")))).join();
+ } catch (IOException e) {
+ // Some error occurred while setting up database
+ ExitCode.DATABASE_SETUP_ERROR.programExit(e);
+ }
+
/*
* ====== END INITIALIZATION ======
*/
logger.trace(state.marker, "Waiting for plugin initialization");
pluginInit.join();
+
postInit();
}
@@ -335,18 +349,18 @@ private void postInit() {
// Post-initialize all plugins
logger.debug(state.marker, "Calling plugin post-initialization async");
- CompletableFuture pluginPostInit = CompletableFuture.allOf(List.copyOf(plugins).stream()
+ CompletableFuture pluginPostInit = CompletableFuture.allOf(plugins.values().stream()
.map(plugin -> CompletableFuture.runAsync(() -> plugin.postInit(this)).exceptionallyAsync(error -> {
pluginError(plugin, error, state.marker);
return null;
- })).toArray(CompletableFuture[]::new)).thenRunAsync(() -> {
- // Register commands
- logger.trace(state.marker, "Collecting command data");
- CommandListUpdateAction update = discord.updateCommands();
- List.copyOf(plugins).stream().filter(IPlugin::providesCommands)
- .forEach(plugin -> update.addCommands(plugin.getCommands()));
- update.queue();
- });
+ })).toArray(CompletableFuture[]::new));
+
+ // Register commands
+ logger.trace(state.marker, "Collecting command data");
+ CommandListUpdateAction update = discord.updateCommands();
+ plugins.values().stream().filter(IPlugin::providesCommands)
+ .forEach(plugin -> update.addCommands(plugin.getCommands()));
+ update.queue();
/*
* ====== END POST-INITIALIZATION ======
@@ -373,7 +387,7 @@ private void ready() {
logger.trace("STATE = " + state);
logger.debug("Calling plugin on ready async");
- CompletableFuture.allOf(List.copyOf(plugins).stream()
+ CompletableFuture.allOf(plugins.values().stream()
.map(plugin -> CompletableFuture.runAsync(() -> plugin.onReady(this)).exceptionallyAsync(error -> {
pluginError(plugin, error, state.marker);
return null;
@@ -459,7 +473,7 @@ private JDA buildJDA() {
private void unloadPlugin(IPlugin plugin) {
logger.trace(state.marker, "Unloading {}", plugin.getClass());
IOUtil.silentClose(plugin);
- plugins.remove(plugin);
+ plugins.remove(plugin.getName());
logger.warn(state.marker, plugin.getClass() + " unloaded");
}
@@ -504,7 +518,11 @@ private void pluginError(IPlugin plugin, Throwable error, Marker marker) {
*
* @return
*/
- public IDatabaseManager getDatabase() { return database; }
+ public IDatabaseManager getDatabaseManager() { return manager; }
+
+ public IGuildData getDataForGuild(Guild guild) {
+ return database.getDataForGuild(guild);
+ }
/**
* Get the property provider instance.
diff --git a/src/net/foxgenesis/watame/command/ConfigCommand.java b/src/net/foxgenesis/watame/command/ConfigCommand.java
index e94dfc3..bf1b760 100644
--- a/src/net/foxgenesis/watame/command/ConfigCommand.java
+++ b/src/net/foxgenesis/watame/command/ConfigCommand.java
@@ -91,6 +91,6 @@ public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent
}
private static JSONObjectAdv getConfig(Guild guild) {
- return WatameBot.getInstance().getDatabase().getDataForGuild(guild).getConfig();
+ return WatameBot.getInstance().getDataForGuild(guild).getConfig();
}
}
diff --git a/src/net/foxgenesis/watame/sql/DataManager.java b/src/net/foxgenesis/watame/sql/DataManager.java
index 5fd10ac..9defe07 100644
--- a/src/net/foxgenesis/watame/sql/DataManager.java
+++ b/src/net/foxgenesis/watame/sql/DataManager.java
@@ -92,7 +92,7 @@ public class DataManager implements IDatabaseManager, AutoCloseable {
* @throws IllegalArgumentException if folder exists and is not a directory
* @see #DataManager(File)
*/
- public DataManager() { this(new File("repo")); }
+ private DataManager() { this(new File("repo")); }
/**
* Create a new instance using the specified folder as the repository folder.
@@ -103,7 +103,7 @@ public class DataManager implements IDatabaseManager, AutoCloseable {
* @throws NullPointerException if {@code folder} is null
* @see #DataManager()
*/
- public DataManager(@Nonnull File folder) {
+ private DataManager(@Nonnull File folder) {
// Ensure repository folder is created
createDatabaseFolder(folder);
@@ -145,7 +145,7 @@ public void addGuild(@Nonnull Guild guild) {
logger.debug("Loading guild ({})[{}]", guild.getName(), guild.getIdLong());
- this.data.put(guild.getIdLong(), new GuildData(guild, this));
+ //this.data.put(guild.getIdLong(), new GuildData(guild, this));
// If initial data retrieval has already been completed, retrieve needed data
if (this.allDataReady)
@@ -315,7 +315,7 @@ private void insertGuildInDatabase(@Nonnull Guild guild) {
@Override
public boolean isReady() { return this.allDataReady; }
- @Override
+
public boolean isConnectionValid() {
try {
// Check if our connection is valid with a one second timeout
diff --git a/src/net/foxgenesis/watame/sql/GuildData.java b/src/net/foxgenesis/watame/sql/GuildData.java
index df91c07..dd10284 100644
--- a/src/net/foxgenesis/watame/sql/GuildData.java
+++ b/src/net/foxgenesis/watame/sql/GuildData.java
@@ -1,6 +1,5 @@
package net.foxgenesis.watame.sql;
-import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
@@ -13,6 +12,7 @@
import net.dv8tion.jda.api.entities.Channel;
import net.dv8tion.jda.api.entities.Guild;
import net.foxgenesis.config.fields.JSONObjectAdv;
+import net.foxgenesis.util.function.QuadFunction;
/**
* Class used to contain guild database data.
@@ -26,7 +26,7 @@ public class GuildData implements IGuildData, AutoCloseable {
* Link to parent data manager
*/
@Nonnull
- private final DataManager dataManager;
+ private final QuadFunction consumer;
/**
* {@link Guild} this instance is based on
@@ -54,9 +54,9 @@ public class GuildData implements IGuildData, AutoCloseable {
* @param guild - The {@link Guild} that this instance represents
* @param dataManager - the {@link DataManager} that created this instance
*/
- GuildData(@Nonnull Guild guild, @Nonnull DataManager dataManager) {
- Objects.nonNull(dataManager);
- this.dataManager = dataManager;
+ GuildData(@Nonnull Guild guild, @Nonnull QuadFunction consumer) {
+ Objects.nonNull(consumer);
+ this.consumer = consumer;
Objects.nonNull(guild);
this.guild = guild;
@@ -119,62 +119,13 @@ void setData(@Nonnull ResultSet result) throws SQLException {
}
// Set our current data and pass our update method
- this.data = new JSONObjectAdv(jsonString, this::pushJSONUpdate);
+ this.data = new JSONObjectAdv(jsonString, (key, obj, remove) -> {;
+ consumer.apply(key, obj, guild, remove);
+ });
this.setup = true;
}
- /**
- * Method used to either update or remove JSON data in the database.
- *
- * @param name - JSON name path
- * @param data - data to set or {@code null} if removing
- * @param remove - should the data at {@code name} be removed or updated
- * @throws NullPointerException if {@code name} is {@code null}
- * @throws IllegalArgumentException if {@code remove} is {@code true} and
- * {@code data} is {@code null}
- * @see #setData(ResultSet)
- */
- private void pushJSONUpdate(@Nonnull String name, @Nullable Object data, boolean remove) {
- Objects.nonNull(name);
-
- int result;
-
- if (remove)
- try (PreparedStatement removeStatement = dataManager.getAndAssertStatement("guild_json_remove")) {
- // Set data and execute update
- removeStatement.setString(1, "$." + name);
-
- DataManager.sqlLogger.debug(DataManager.UPDATE_MARKER, "PushUpdate -> " + removeStatement);
-
- result = removeStatement.executeUpdate();
- } catch (SQLException e) {
- DataManager.logger.error(DataManager.UPDATE_MARKER, "Error while removing guild json data", e);
- return;
- }
- else {
- // Ensure we have data passed
- if (data == null)
- throw new IllegalArgumentException("Data must not be null if 'remove' is 'true'!");
-
- try (PreparedStatement updateStatement = dataManager.getAndAssertStatement("guild_json_update")) {
- // Set data and execute update
- updateStatement.setString(1, "$." + name);
- updateStatement.setString(2, data.toString());
- updateStatement.setLong(3, guild.getIdLong());
-
- DataManager.sqlLogger.debug(DataManager.UPDATE_MARKER, "PushUpdate -> " + updateStatement);
-
- result = updateStatement.executeUpdate();
- } catch (SQLException e) {
- DataManager.logger.error(DataManager.UPDATE_MARKER, "Error while updating guild json data", e);
- return;
- }
- }
-
- DataManager.sqlLogger.debug(DataManager.UPDATE_MARKER, "ExecuteUpdate <- " + result);
- }
-
private void checkSetup() {
if (!setup)
throw new UnsupportedOperationException("GuildData has not been setup yet!");
diff --git a/src/net/foxgenesis/watame/sql/IDatabaseManager.java b/src/net/foxgenesis/watame/sql/IDatabaseManager.java
index 0f7c1a7..cbb1e03 100644
--- a/src/net/foxgenesis/watame/sql/IDatabaseManager.java
+++ b/src/net/foxgenesis/watame/sql/IDatabaseManager.java
@@ -5,13 +5,6 @@
import net.dv8tion.jda.api.entities.Guild;
public interface IDatabaseManager {
- /**
- * Check if the database is connected and is ready for operations.
- *
- * @return Returns {@code true} if is connected and ready
- */
- public boolean isConnectionValid();
-
/**
* Check if all guild data has been processed and is ready for use.
*
diff --git a/src/net/foxgenesis/watame/sql/WatameBotDatabase.java b/src/net/foxgenesis/watame/sql/WatameBotDatabase.java
new file mode 100644
index 0000000..d18c0fa
--- /dev/null
+++ b/src/net/foxgenesis/watame/sql/WatameBotDatabase.java
@@ -0,0 +1,206 @@
+package net.foxgenesis.watame.sql;
+
+import java.sql.ResultSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+
+import net.dv8tion.jda.api.entities.Guild;
+import net.foxgenesis.watame.Constants;
+
+public class WatameBotDatabase extends net.foxgenesis.database.AbstractDatabase implements IDatabaseManager {
+ // =============================== STATIC =================================
+ static final Logger logger = LoggerFactory.getLogger("Database");
+ static final Logger sqlLogger = LoggerFactory.getLogger("SQLInfo");
+
+ static final Marker SQL_MARKER = MarkerFactory.getMarker("SQL");
+
+ static final Marker UPDATE_MARKER = MarkerFactory.getMarker("SQL_UPDATE");
+ static final Marker QUERY_MARKER = MarkerFactory.getMarker("SQL_QUERY");
+
+ static {
+ UPDATE_MARKER.add(SQL_MARKER);
+ QUERY_MARKER.add(SQL_MARKER);
+ }
+
+ // =============================== INSTANCE =================================
+
+ /**
+ * Map of guild id to guild data object
+ */
+ private final ConcurrentHashMap data = new ConcurrentHashMap<>();
+
+ public WatameBotDatabase() {
+ super("WatameBot Database", Constants.DATABASE_OPERATIONS_FILE, Constants.DATABASE_SETUP_FILE);
+ }
+
+ /**
+ * Register a guild to be loaded during data retrieval.
+ *
+ * @param guild - {@link Guild} to be loaded
+ * @throws NullPointerException if {@code guild} is {@code null}
+ * @see #removeGuild(Guild)
+ */
+ public void addGuild(@Nonnull Guild guild) {
+ Objects.requireNonNull(guild);
+
+ logger.debug("Loading guild ({})[{}]", guild.getName(), guild.getIdLong());
+
+ this.data.put(guild.getIdLong(), new GuildData(guild, this::pushJSONUpdate));
+
+ // If initial data retrieval has already been completed, retrieve needed data
+ retrieveData(guild);
+
+ logger.trace("Guild loaded ({})[{}]", guild.getName(), guild.getIdLong());
+ }
+
+ /**
+ * Remove a guild from the data manager.
+ *
+ * @param guild - {@link Guild} to be removed
+ * @throws NullPointerException if {@code guild} is {@code null}
+ * @see #addGuild(Guild)
+ */
+ public void removeGuild(@Nonnull Guild guild) {
+ Objects.requireNonNull(guild);
+
+ logger.debug("Removing guild ({})[{}]", guild.getName(), guild.getIdLong());
+
+ try {
+ this.data.remove(guild.getIdLong()).close();
+ logger.trace("Guild REMOVED ({})[{}]", guild.getName(), guild.getIdLong());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Insert a new row into the database for a {@link Guild}.
+ *
+ * @param guild - guild to insert
+ */
+ private void insertGuildInDatabase(@Nonnull Guild guild) {
+ Objects.requireNonNull(guild);
+
+ this.prepareStatement("guild_data_insert", st -> {
+ long guildID = guild.getIdLong();
+ st.setLong(1, guildID);
+
+ sqlLogger.trace(UPDATE_MARKER, st.toString());
+ System.out.println("update: " + st.executeUpdate());
+ }, e -> sqlLogger.error(QUERY_MARKER, "Error while inserting new guild", e));
+ }
+
+ /**
+ * Retrieve guild data for a specific guild.
+ *
+ * @param guild - {@link Guild} to retrieve data for
+ */
+ private void retrieveData(@Nonnull Guild guild) {
+ Objects.requireNonNull(guild);
+
+ this.prepareStatement("guild_data_get_id", s -> {
+ s.setLong(1, guild.getIdLong());
+ sqlLogger.trace(QUERY_MARKER, s.toString());
+
+ try (ResultSet set = s.executeQuery()) {
+ if (set.next()) {
+ long id = set.getLong("GuildID"); //$NON-NLS-1$
+ if (id != 0 && this.data.containsKey(id)) {
+ this.data.get(id).setData(set);
+ return;
+ }
+ }
+ logger.warn("Guild ({})[{}] is missing in database! Attempting to insert and retrieve...",
+ guild.getName(), guild.getIdLong());
+ insertGuildInDatabase(guild);
+ retrieveData(guild);
+ }
+ }, e -> sqlLogger.error(QUERY_MARKER, "Error while reading guild", e));
+ }
+
+ @Override
+ @Nullable
+ public IGuildData getDataForGuild(@Nonnull Guild guild) {
+ // Ensure non null guild
+ Objects.requireNonNull(guild);
+
+ // Check if database processing is finished
+ if (!isReady())
+ throw new UnsupportedOperationException("Data not ready yet");
+
+ // Check and get guild data
+ if (!data.containsKey(guild.getIdLong())) {
+ // Guild data doesn't exist
+ logger.warn("Attempted to get non existant data for guild {}. Attempting to insert and retrieve data",
+ guild.getId());
+
+ insertGuildInDatabase(guild);
+ retrieveData(guild);
+ }
+
+ return data.get(guild.getIdLong());
+ }
+
+ /**
+ * Method used to either update or remove JSON data in the database.
+ *
+ * @param name - JSON name path
+ * @param data - data to set or {@code null} if removing
+ * @param remove - should the data at {@code name} be removed or updated
+ * @return
+ * @throws NullPointerException if {@code name} is {@code null}
+ * @throws IllegalArgumentException if {@code remove} is {@code true} and
+ * {@code data} is {@code null}
+ * @see #setData(ResultSet)
+ */
+ private Integer pushJSONUpdate(@Nonnull String name, @Nullable Object data, @Nonnull Guild guild, boolean remove) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(guild);
+
+ int result;
+ if (remove) {
+ result = this.mapStatement("guild_json_remove", removeStatement -> {
+ // Set data and execute update
+ removeStatement.setString(1, "$." + name);
+ removeStatement.setLong(3, guild.getIdLong());
+
+ sqlLogger.debug(UPDATE_MARKER, "PushUpdate -> " + removeStatement);
+
+ return removeStatement.executeUpdate();
+ }, e -> logger.error(UPDATE_MARKER, "Error while removing guild json data", e));
+
+ } else {
+ // Ensure we have data passed
+ if (data == null)
+ throw new IllegalArgumentException("Data must not be null if 'remove' is 'false'!");
+
+ result = this.mapStatement("guild_json_update", updateStatement -> {
+ // Set data and execute update
+ updateStatement.setString(1, "$." + name);
+ updateStatement.setString(2, data.toString());
+ updateStatement.setLong(3, guild.getIdLong());
+
+ sqlLogger.debug(UPDATE_MARKER, "PushUpdate -> " + updateStatement);
+
+ return updateStatement.executeUpdate();
+ }, e -> logger.error(UPDATE_MARKER, "Error while updating guild json data", e));
+ }
+ sqlLogger.debug(UPDATE_MARKER, "ExecuteUpdate <- " + result);
+
+ return result;
+ }
+
+ @Override
+ public void close() throws Exception {}
+
+ @Override
+ protected void onReady() {}
+}