Initial rework of the BendingPlayer back-end

This commit is contained in:
jayoevans 2019-10-22 11:49:47 +10:00
parent a8a8daa6d0
commit 299cec824e
15 changed files with 711 additions and 1 deletions

14
pom.xml
View file

@ -74,6 +74,18 @@
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>3.8.1</version> <version>3.8.1</version>
</dependency> </dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.1</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- Factions --> <!-- Factions -->
<dependency> <dependency>
<groupId>me.markeh</groupId> <groupId>me.markeh</groupId>
@ -244,4 +256,4 @@
<dir>${project.build.directory}</dir> <dir>${project.build.directory}</dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
</project> </project>

View file

@ -8,6 +8,7 @@ import com.bekvon.bukkit.residence.protection.FlagPermissions;
import co.aikar.timings.lib.MCTiming; import co.aikar.timings.lib.MCTiming;
import co.aikar.timings.lib.TimingManager; import co.aikar.timings.lib.TimingManager;
import com.projectkorra.projectkorra.module.ModuleManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Statistic; import org.bukkit.Statistic;
@ -77,6 +78,7 @@ public class ProjectKorra extends JavaPlugin {
} }
Manager.startup(); Manager.startup();
ModuleManager.startup();
this.getServer().getPluginManager().registerEvents(new PKListener(this), this); this.getServer().getPluginManager().registerEvents(new PKListener(this), this);
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, new BendingManager(), 0, 1); this.getServer().getScheduler().scheduleSyncRepeatingTask(this, new BendingManager(), 0, 1);

View file

@ -0,0 +1,14 @@
package com.projectkorra.projectkorra.database;
public class DatabaseConfig
{
public final DatabaseManager.Engine Engine = DatabaseManager.Engine.SQLITE;
public final String SQLite_File = "projectkorra.sql";
public final String MySQL_IP = "localhost";
public final String MySQL_Port = "3306";
public final String MySQL_DatabaseName = "projectkorra";
public final String MySQL_Username = "root";
public final String MySQL_Password = "password";
}

View file

@ -0,0 +1,57 @@
package com.projectkorra.projectkorra.database;
import com.projectkorra.projectkorra.database.engine.MySQLDatabase;
import com.projectkorra.projectkorra.database.engine.SQLDatabase;
import com.projectkorra.projectkorra.database.engine.SQLiteDatabase;
import com.projectkorra.projectkorra.module.Module;
import java.util.logging.Level;
public class DatabaseManager extends Module
{
private final DatabaseConfig _config;
private final SQLDatabase _database;
private DatabaseManager()
{
super("Database");
// TODO Pull from new ConfigManager
_config = new DatabaseConfig();
switch (_config.Engine)
{
case MYSQL:
_database = new MySQLDatabase(_config);
break;
case SQLITE:
_database = new SQLiteDatabase(this, _config);
break;
default:
log(Level.SEVERE, "Unknown database engine.");
_database = null;
break;
}
}
public DatabaseConfig getConfig()
{
return _config;
}
public SQLDatabase getDatabase()
{
return _database;
}
@Override
public void onDisable()
{
_database.close();
}
public enum Engine
{
MYSQL, SQLITE;
}
}

View file

@ -0,0 +1,63 @@
package com.projectkorra.projectkorra.database;
import com.projectkorra.projectkorra.module.ModuleManager;
public class DatabaseQuery
{
private final String _mysql;
private final String _sqlite;
private DatabaseQuery(String mysql, String sqlite)
{
_mysql = mysql;
_sqlite = sqlite;
}
public String getQuery()
{
switch (ModuleManager.getModule(DatabaseManager.class).getConfig().Engine)
{
case MYSQL:
return _mysql;
case SQLITE:
return _sqlite;
}
return null;
}
public static Builder newBuilder()
{
return new Builder();
}
public static final class Builder
{
private String _mysql;
private String _sqlite;
public Builder mysql(String mysql)
{
_mysql = mysql;
return this;
}
public Builder sqlite(String sqlite)
{
_sqlite = sqlite;
return this;
}
public Builder query(String query)
{
_mysql = query;
_sqlite = query;
return this;
}
public DatabaseQuery build()
{
return new DatabaseQuery(_mysql, _sqlite);
}
}
}

View file

@ -0,0 +1,19 @@
package com.projectkorra.projectkorra.database;
import com.projectkorra.projectkorra.database.engine.SQLDatabase;
import com.projectkorra.projectkorra.module.ModuleManager;
public abstract class DatabaseRepository
{
private final DatabaseManager databaseManager;
public DatabaseRepository()
{
this.databaseManager = ModuleManager.getModule(DatabaseManager.class);
}
protected SQLDatabase getDatabase()
{
return databaseManager.getDatabase();
}
}

View file

@ -0,0 +1,48 @@
package com.projectkorra.projectkorra.database.engine;
import com.projectkorra.projectkorra.database.DatabaseConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class MySQLDatabase implements SQLDatabase
{
private final HikariDataSource _hikari;
public MySQLDatabase(DatabaseConfig databaseConfig)
{
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl("jdbc:mysql://" + databaseConfig.MySQL_IP + ":" + databaseConfig.MySQL_Port + "/" + databaseConfig.MySQL_DatabaseName);
hikariConfig.setDriverClassName("com.mysql.jdbc.Driver");
hikariConfig.setUsername(databaseConfig.MySQL_Username);
hikariConfig.setPassword(databaseConfig.MySQL_Password);
hikariConfig.setMinimumIdle(1);
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setConnectionTimeout(10000);
_hikari = new HikariDataSource(hikariConfig);
}
@Override
public Connection getConnection()
{
try (Connection connection = _hikari.getConnection())
{
return connection;
}
catch (SQLException e)
{
e.printStackTrace();
return null;
}
}
@Override
public void close()
{
_hikari.close();
}
}

View file

@ -0,0 +1,10 @@
package com.projectkorra.projectkorra.database.engine;
import java.sql.Connection;
public interface SQLDatabase
{
Connection getConnection();
void close();
}

View file

@ -0,0 +1,83 @@
package com.projectkorra.projectkorra.database.engine;
import com.projectkorra.projectkorra.database.DatabaseConfig;
import com.projectkorra.projectkorra.database.DatabaseManager;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SQLiteDatabase implements SQLDatabase
{
private final File _databaseFile;
private Connection _connection;
public SQLiteDatabase(DatabaseManager databaseManager, DatabaseConfig databaseConfig)
{
_databaseFile = new File(databaseManager.getPlugin().getDataFolder(), databaseConfig.SQLite_File);
if (!_databaseFile.getParentFile().exists())
{
_databaseFile.getParentFile().mkdirs();
}
if (!_databaseFile.exists())
{
try
{
_databaseFile.createNewFile();
}
catch (IOException e)
{
e.printStackTrace();
}
}
open();
}
public void open()
{
try
{
_connection = DriverManager.getConnection("jdbc:sqlite:" + _databaseFile.getAbsolutePath());
}
catch (SQLException e)
{
e.printStackTrace();
}
}
@Override
public Connection getConnection()
{
try
{
if (_connection == null || _connection.isClosed())
{
open();
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return _connection;
}
@Override
public void close()
{
try
{
_connection.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,20 @@
package com.projectkorra.projectkorra.module;
import com.projectkorra.projectkorra.database.DatabaseRepository;
public abstract class DatabaseModule<T extends DatabaseRepository> extends Module
{
private final T repository;
protected DatabaseModule(String name, T repository)
{
super(name);
this.repository = repository;
}
protected T getRepository()
{
return this.repository;
}
}

View file

@ -0,0 +1,92 @@
package com.projectkorra.projectkorra.module;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.projectkorra.projectkorra.ProjectKorra;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Level;
public abstract class Module implements Listener
{
private static final String LOG_FORMAT = "(%s) %s";
private final String name;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
protected Module(String name)
{
this.name = name;
}
protected final void enable()
{
long startTime = System.currentTimeMillis();
log("Enabling...");
getPlugin().getServer().getPluginManager().registerEvents(this, getPlugin());
onEnable();
long finishTime = System.currentTimeMillis();
log(String.format("Enabled! [%sms]", finishTime - startTime));
}
public void onEnable()
{
}
protected final void disable()
{
long startTime = System.currentTimeMillis();
log("Disabling...");
HandlerList.unregisterAll(this);
onDisable();
long finishTime = System.currentTimeMillis();
log(String.format("Disabled! [%sms]", finishTime - startTime));
}
public void onDisable()
{
}
protected final void runSync(Runnable runnable)
{
getPlugin().getServer().getScheduler().runTask(getPlugin(), runnable);
}
protected final void runAsync(Runnable runnable)
{
getPlugin().getServer().getScheduler().runTaskAsynchronously(getPlugin(), runnable);
}
public String getName()
{
return this.name;
}
protected Gson getGson()
{
return this.gson;
}
public final void log(String message)
{
log(Level.INFO, message);
}
public final void log(Level level, String message)
{
getPlugin().getLogger().log(level, String.format(LOG_FORMAT, getName(), message));
}
public ProjectKorra getPlugin()
{
return JavaPlugin.getPlugin(ProjectKorra.class);
}
}

View file

@ -0,0 +1,74 @@
package com.projectkorra.projectkorra.module;
import com.google.common.base.Preconditions;
import com.projectkorra.projectkorra.database.DatabaseManager;
import com.projectkorra.projectkorra.player.BendingPlayerManager;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class ModuleManager {
private static final Map<Class<? extends Module>, Module> MODULES = new HashMap<>();
/**
* Registers a new {@link Module} instance.
*
* @param moduleClass {@link Class} of the {@link Module} to be registered
* @throws NullPointerException if moduleClass is null
* @throws IllegalArgumentException if moduleClass has already been registered
*/
public static void registerModule(Class<? extends Module> moduleClass) {
Preconditions.checkNotNull(moduleClass, "moduleClass cannot be null");
Preconditions.checkArgument(!MODULES.containsKey(moduleClass), "moduleClass has already been registered");
try {
Constructor<? extends Module> constructor = moduleClass.getDeclaredConstructor();
boolean accessible = constructor.isAccessible();
constructor.setAccessible(true);
Module module = constructor.newInstance();
MODULES.put(moduleClass, module);
module.enable();
constructor.setAccessible(accessible);
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* Returns a registered {@link Module} by its {@link Class}.
*
* @param moduleClass {@link Class} of the registered {@link Module}
* @return instance of the {@link Module} class
* @throws NullPointerException if moduleClass is null
* @throws IllegalArgumentException if moduleClass has not been registered
*/
public static <T extends Module> T getModule(Class<T> moduleClass) {
Preconditions.checkNotNull(moduleClass, "moduleClass cannot be null");
Preconditions.checkArgument(MODULES.containsKey(moduleClass), "moduleClass has not been registered");
return moduleClass.cast(MODULES.get(moduleClass));
}
/**
* Register all our core {@link Module}s onEnable.
*/
public static void startup() {
registerModule(DatabaseManager.class);
registerModule(BendingPlayerManager.class);
}
/**
* Disable all our core {@link Module}s onDisable.
*/
public static void shutdown() {
getModule(BendingPlayerManager.class).disable();
getModule(DatabaseManager.class).disable();
}
}

View file

@ -0,0 +1,29 @@
package com.projectkorra.projectkorra.player;
import java.util.UUID;
public class BendingPlayer
{
private final int _playerId;
private final UUID _uuid;
private final String _playerName;
private long _firstLogin;
public BendingPlayer(int playerId, UUID uuid, String playerName, long firstLogin)
{
_playerId = playerId;
_uuid = uuid;
_playerName = playerName;
_firstLogin = firstLogin;
}
public int getId()
{
return _playerId;
}
public long getFirstLogin()
{
return _firstLogin;
}
}

View file

@ -0,0 +1,52 @@
package com.projectkorra.projectkorra.player;
import com.projectkorra.projectkorra.module.DatabaseModule;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerLoginEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class BendingPlayerManager extends DatabaseModule<BendingPlayerRepository>
{
private final Map<UUID, BendingPlayer> _players = new HashMap<>();
private BendingPlayerManager()
{
super("Bending Player", new BendingPlayerRepository());
runAsync(() ->
{
getRepository().createTable();
runSync(() ->
{
log("Created database table.");
});
});
}
@EventHandler(priority = EventPriority.LOWEST)
public void onLogin(PlayerLoginEvent event)
{
Player player = event.getPlayer();
runAsync(() ->
{
BendingPlayer bendingPlayer = getRepository().selectPlayer(player);
runSync(() ->
{
_players.put(player.getUniqueId(), bendingPlayer);
});
});
}
public BendingPlayer getBendingPlayer(Player player)
{
return _players.get(player.getUniqueId());
}
}

View file

@ -0,0 +1,135 @@
package com.projectkorra.projectkorra.player;
import com.projectkorra.projectkorra.database.DatabaseQuery;
import com.projectkorra.projectkorra.database.DatabaseRepository;
import org.bukkit.entity.Player;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
public class BendingPlayerRepository extends DatabaseRepository
{
private static final DatabaseQuery CREATE_TABLE_BENDING_PLAYERS = DatabaseQuery.newBuilder()
.mysql("CREATE TABLE IF NOT EXISTS pk_bending_players (player_id INTEGER PRIMARY KEY AUTO_INCREMENT, uuid BINARY(16) NOT NULL, player_name VARCHAR(16) NOT NULL, first_login BIGINT NOT NULL, INDEX uuid_index (uuid));")
.sqlite("CREATE TABLE IF NOT EXISTS pk_bending_players (player_id INTEGER PRIMARY KEY AUTOINCREMENT, uuid BINARY(16) NOT NULL, player_name VARCHAR(16) NOT NULL, first_login BIGINT NOT NULL); CREATE INDEX uuid_index ON pk_bending_players (uuid);")
.build();
private static final DatabaseQuery SELECT_BENDING_PLAYER = DatabaseQuery.newBuilder()
.query("SELECT player_id, player_name, first_login FROM pk_bending_players WHERE uuid = ?;")
.build();
private static final DatabaseQuery INSERT_BENDING_PLAYER = DatabaseQuery.newBuilder()
.query("INSERT INTO pk_bending_players (uuid, player_name, first_login) VALUES (?, ?, ?);")
.build();
private static final DatabaseQuery UPDATE_BENDING_PLAYER = DatabaseQuery.newBuilder()
.query("UPDATE pk_bending_players SET player_name = ? WHERE player_id = ?;")
.build();
protected void createTable()
{
Connection connection = getDatabase().getConnection();
try (PreparedStatement statement = connection.prepareStatement(CREATE_TABLE_BENDING_PLAYERS.getQuery()))
{
statement.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
protected BendingPlayer selectPlayer(Player player)
{
UUID uuid = player.getUniqueId();
byte[] binaryUUID = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
Connection connection = getDatabase().getConnection();
try (PreparedStatement statement = connection.prepareStatement(SELECT_BENDING_PLAYER.getQuery()))
{
statement.setBytes(1, binaryUUID);
try (ResultSet rs = statement.executeQuery())
{
if (!rs.next())
{
return insertPlayer(player.getUniqueId(), player.getName());
}
int playerId = rs.getInt("player_id");
String playerName = rs.getString("player_name");
long firstLogin = rs.getLong("first_login");
if (!player.getName().equals(playerName))
{
updatePlayer(playerId, player.getName());
}
return new BendingPlayer(playerId, uuid, playerName, firstLogin);
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return null;
}
private BendingPlayer insertPlayer(UUID uuid, String playerName)
{
byte[] binaryUUID = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
Connection connection = getDatabase().getConnection();
long firstLogin = System.currentTimeMillis();
try (PreparedStatement statement = connection.prepareStatement(INSERT_BENDING_PLAYER.getQuery(), Statement.RETURN_GENERATED_KEYS))
{
statement.setBytes(1, binaryUUID);
statement.setString(2, playerName);
statement.setLong(3, firstLogin);
statement.executeUpdate();
try (ResultSet rs = statement.getGeneratedKeys())
{
if (rs.next())
{
int playerId = rs.getInt(1);
return new BendingPlayer(playerId, uuid, playerName, firstLogin);
}
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return null;
}
protected void updatePlayer(int playerId, String playerName)
{
Connection connection = getDatabase().getConnection();
try (PreparedStatement statement = connection.prepareStatement(UPDATE_BENDING_PLAYER.getQuery()))
{
statement.setInt(1, playerId);
statement.setString(2, playerName);
statement.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}