diff --git a/src/main/java/world/bentobox/bentobox/database/DatabaseSetup.java b/src/main/java/world/bentobox/bentobox/database/DatabaseSetup.java index e24f2d80f..724f40136 100644 --- a/src/main/java/world/bentobox/bentobox/database/DatabaseSetup.java +++ b/src/main/java/world/bentobox/bentobox/database/DatabaseSetup.java @@ -2,6 +2,7 @@ package world.bentobox.bentobox.database; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.database.json.JSONDatabase; +import world.bentobox.bentobox.database.mariadb.MariaDBDatabase; import world.bentobox.bentobox.database.mongodb.MongoDBDatabase; import world.bentobox.bentobox.database.mysql.MySQLDatabase; import world.bentobox.bentobox.database.yaml.YamlDatabase; @@ -10,7 +11,7 @@ public interface DatabaseSetup { /** * Gets the type of database being used. - * Currently supported options are YAML, JSON, MYSQL and MONGODB. + * Currently supported options are YAML, JSON, MYSQL, MARIADB and MONGODB. * Default is YAML. * @return Database type */ @@ -28,8 +29,8 @@ public interface DatabaseSetup { YAML(new YamlDatabase()), JSON(new JSONDatabase()), MYSQL(new MySQLDatabase()), + MARIADB(new MariaDBDatabase()), MONGODB(new MongoDBDatabase()); - DatabaseSetup database; DatabaseType(DatabaseSetup database){ diff --git a/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabase.java b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabase.java new file mode 100644 index 000000000..3fad0cbb5 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabase.java @@ -0,0 +1,26 @@ +package world.bentobox.bentobox.database.mariadb; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.AbstractDatabaseHandler; +import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl; +import world.bentobox.bentobox.database.DatabaseSetup; + +public class MariaDBDatabase implements DatabaseSetup { + + + /* (non-Javadoc) + * @see world.bentobox.bentobox.database.BSBDbSetup#getHandler(java.lang.Class) + */ + @Override + public AbstractDatabaseHandler getHandler(Class type) { + BentoBox plugin = BentoBox.getInstance(); + return new MariaDBDatabaseHandler<>(plugin, type, new MariaDBDatabaseConnector(new DatabaseConnectionSettingsImpl( + plugin.getSettings().getDatabaseHost(), + plugin.getSettings().getDatabasePort(), + plugin.getSettings().getDatabaseName(), + plugin.getSettings().getDatabaseUsername(), + plugin.getSettings().getDatabasePassword() + ))); + } + +} diff --git a/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseConnector.java b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseConnector.java new file mode 100644 index 000000000..4f6a37d68 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseConnector.java @@ -0,0 +1,64 @@ +package world.bentobox.bentobox.database.mariadb; + +import org.bukkit.Bukkit; +import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl; +import world.bentobox.bentobox.database.DatabaseConnector; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class MariaDBDatabaseConnector implements DatabaseConnector { + + private String connectionUrl; + private DatabaseConnectionSettingsImpl dbSettings; + private Connection connection = null; + + /** + * Class for MariaDB database connections using the settings provided + * @param dbSettings - database settings + */ + MariaDBDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) { + this.dbSettings = dbSettings; + connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName() + + "?autoReconnect=true&useSSL=false&allowMultiQueries=true"; + } + + @Override + public Connection createConnection() { + try { + connection = DriverManager.getConnection(connectionUrl, dbSettings.getUsername(), dbSettings.getPassword()); + } catch (SQLException e) { + Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage()); + } + return connection; + } + + @Override + public String getConnectionUrl() { + return connectionUrl; + } + + @Override + public String getUniqueId(String tableName) { + // Not used + return ""; + } + + @Override + public boolean uniqueIdExists(String tableName, String key) { + // Not used + return false; + } + + @Override + public void closeConnection() { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + Bukkit.getLogger().severe("Could not close MariaDB database connection"); + } + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java new file mode 100644 index 000000000..fa09f6046 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java @@ -0,0 +1,186 @@ +package world.bentobox.bentobox.database.mariadb; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.DatabaseConnector; +import world.bentobox.bentobox.database.json.AbstractJSONDatabaseHandler; +import world.bentobox.bentobox.database.objects.DataObject; + +import java.lang.reflect.Method; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +/** + * + * Class that inserts a into the corresponding database-table. + * + * @author tastybento + * + * @param + */ +public class MariaDBDatabaseHandler extends AbstractJSONDatabaseHandler { + + /** + * Connection to the database + */ + private Connection connection; + + /** + * Handles the connection to the database and creation of the initial database schema (tables) for + * the class that will be stored. + * @param plugin - plugin object + * @param type - the type of class to be stored in the database. Must inherit DataObject + * @param dbConnecter - authentication details for the database + */ + MariaDBDatabaseHandler(BentoBox plugin, Class type, DatabaseConnector dbConnecter) { + super(plugin, type, dbConnecter); + connection = (Connection)dbConnecter.createConnection(); + // Check if the table exists in the database and if not, create it + createSchema(); + } + + /** + * Creates the table in the database if it doesn't exist already + */ + private void createSchema() { + String sql = "CREATE TABLE IF NOT EXISTS `" + + dataObject.getCanonicalName() + + "` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))"; + // Prepare and execute the database statements + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.executeUpdate(); + } catch (SQLException e) { + plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage()); + } + } + + @Override + public List loadObjects() { + List list = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + sb.append("SELECT `json` FROM `"); + sb.append(dataObject.getCanonicalName()); + sb.append("`"); + try (Statement preparedStatement = connection.createStatement()) { + try (ResultSet resultSet = preparedStatement.executeQuery(sb.toString())) { + // Load all the results + Gson gson = getGson(); + while (resultSet.next()) { + String json = resultSet.getString("json"); + if (json != null) { + try { + T gsonResult = gson.fromJson(json, dataObject); + if (gsonResult != null) { + list.add(gsonResult); + } + } catch (JsonSyntaxException ex) { + plugin.logError("Could not load object " + ex.getMessage()); + } + } + } + } + } catch (SQLException e) { + plugin.logError("Could not load objects " + e.getMessage()); + } + return list; + } + + @Override + public T loadObject(String uniqueId) { + String sb = "SELECT `json` FROM `" + + dataObject.getCanonicalName() + + "` WHERE uniqueId = ? LIMIT 1"; + try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) { + // UniqueId needs to be placed in quotes + preparedStatement.setString(1, "\"" + uniqueId + "\""); + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + // If there is a result, we only want/need the first one + Gson gson = getGson(); + return gson.fromJson(resultSet.getString("json"), dataObject); + } + } + } catch (SQLException e) { + plugin.logError("Could not load object " + uniqueId + " " + e.getMessage()); + } + return null; + } + + @Override + public void saveObject(T instance) { + if (!(instance instanceof DataObject)) { + plugin.logError("This class is not a DataObject: " + instance.getClass().getName()); + return; + } + String sb = "INSERT INTO " + + "`" + + dataObject.getCanonicalName() + + "` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?"; + // Replace into is used so that any data in the table will be replaced with updated data + // The table name is the canonical name, so that add-ons can be sure of a unique table in the database + try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) { + Gson gson = getGson(); + String toStore = gson.toJson(instance); + preparedStatement.setString(1, toStore); + preparedStatement.setString(2, toStore); + preparedStatement.execute(); + } catch (SQLException e) { + plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage()); + } + } + + @Override + public void deleteObject(T instance) { + if (!(instance instanceof DataObject)) { + plugin.logError("This class is not a DataObject: " + instance.getClass().getName()); + return; + } + String sb = "DELETE FROM `" + + dataObject.getCanonicalName() + + "` WHERE uniqueId = ?"; + try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) { + Method getUniqueId = dataObject.getMethod("getUniqueId"); + String uniqueId = (String) getUniqueId.invoke(instance); + // UniqueId needs to be placed in quotes + preparedStatement.setString(1, "\"" + uniqueId + "\""); + preparedStatement.execute(); + } catch (Exception e) { + plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage()); + } + } + + @Override + public boolean objectExists(String uniqueId) { + // Create the query to see if this key exists + String query = "SELECT IF ( EXISTS( SELECT * FROM `" + + dataObject.getCanonicalName() + + "` WHERE `uniqueId` = ?), 1, 0)"; + + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + // UniqueId needs to be placed in quotes + preparedStatement.setString(1, "\"" + uniqueId + "\""); + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getBoolean(1); + } + } + } catch (SQLException e) { + plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage()); + } + return false; + } + + @Override + public void close() { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + plugin.logError("Could not close database for some reason"); + } + } + } + +}