1
0
Fork 0
mirror of https://github.com/xHyroM/lighteco.git synced 2024-12-22 12:31:05 +01:00

feat: sql storage provider

This commit is contained in:
Jozef Steinhübl 2023-08-28 16:53:48 +02:00
parent 0578cdf3f4
commit 39b2211131
16 changed files with 232 additions and 34 deletions

View file

@ -6,7 +6,9 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
public interface StorageProvider {
void init() throws Exception;
@NonNull User loadUser(@NonNull UUID uniqueId) throws Exception;
// todo: look at this
void saveUser(@NonNull User user);
void saveUser(@NonNull User user) throws Exception;
}

View file

@ -10,6 +10,7 @@ import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
@ -31,12 +32,12 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr
@Override
public void onLoad() {
plugin.load();
this.plugin.load();
}
@Override
public void onEnable() {
plugin.enable();
this.plugin.enable();
}
@Override
@ -45,12 +46,12 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr
}
public Server getServer() {
return loader.getServer();
return this.loader.getServer();
}
@Override
public File getDataFolder() {
return loader.getDataFolder();
return this.loader.getDataFolder();
}
@Override
@ -59,4 +60,9 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr
.map(Entity::getUniqueId)
.toList();
}
@Override
public InputStream getResourceStream(String filename) {
return this.loader.getResource(filename);
}
}

View file

@ -11,6 +11,8 @@ dependencies {
}
api("net.kyori:adventure-text-minimessage:4.14.0")
api("org.xerial:sqlite-jdbc:3.40.0.0")
implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.0-beta.5")
implementation("eu.okaeri:okaeri-configs-validator-okaeri:5.0.0-beta.5")

View file

@ -3,6 +3,7 @@ package dev.xhyrom.lighteco.common.plugin.bootstrap;
import dev.xhyrom.lighteco.common.plugin.logger.PluginLogger;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
@ -12,4 +13,5 @@ public interface LightEcoBootstrap {
PluginLogger getLogger();
File getDataFolder();
List<UUID> getOnlinePlayers();
InputStream getResourceStream(String filename);
}

View file

@ -3,6 +3,7 @@ package dev.xhyrom.lighteco.common.storage;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.api.storage.StorageProvider;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import dev.xhyrom.lighteco.common.util.ThrowableRunnable;
import java.util.UUID;
import java.util.concurrent.Callable;
@ -32,8 +33,26 @@ public class Storage {
});
}
private CompletableFuture<Void> future(Runnable runnable) {
return CompletableFuture.runAsync(runnable);
private CompletableFuture<Void> future(ThrowableRunnable runnable) {
return CompletableFuture.runAsync(() -> {
try {
runnable.run();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
});
}
public void init() {
try {
this.provider.init();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize storage provider", e);
}
}
public CompletableFuture<User> loadUser(UUID uniqueId) {

View file

@ -3,6 +3,8 @@ package dev.xhyrom.lighteco.common.storage;
import dev.xhyrom.lighteco.api.storage.StorageProvider;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import dev.xhyrom.lighteco.common.storage.provider.memory.MemoryStorageProvider;
import dev.xhyrom.lighteco.common.storage.provider.sql.SqlStorageProvider;
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.file.SqliteConnectionFactory;
public class StorageFactory {
private final LightEcoPlugin plugin;
@ -14,14 +16,22 @@ public class StorageFactory {
public Storage get() {
// todo: use config
String provider = this.plugin.getConfig().storage.provider;
Storage storage = new Storage(this.plugin, createProvider(provider));
return new Storage(this.plugin, createProvider(provider));
storage.init();
return storage;
}
private StorageProvider createProvider(String provider) {
switch (provider.toLowerCase()) {
case "memory":
return new MemoryStorageProvider(this.plugin);
case "sqlite":
return new SqlStorageProvider(
this.plugin,
new SqliteConnectionFactory(this.plugin.getBootstrap().getDataFolder().toPath().resolve("data.db"))
);
default:
throw new IllegalArgumentException("Unknown storage provider: " + provider);
}

View file

@ -16,6 +16,9 @@ public class MemoryStorageProvider implements StorageProvider {
this.plugin = plugin;
}
@Override
public void init() throws Exception {}
@Override
public @NonNull User loadUser(@NonNull UUID uniqueId) {
this.simulateSlowDatabaseQuery();

View file

@ -0,0 +1,51 @@
// Implementation from LuckPerms
// https://github.com/LuckPerms/LuckPerms/blob/master/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SchemaReader.java
// Copyright (c) lucko (Luck) <lucko@lucko.me>
// Copyright (c) contributors
// Under MIT License
package dev.xhyrom.lighteco.common.storage.provider.sql;
import lombok.experimental.UtilityClass;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
@UtilityClass
public final class SchemaReader {
public static List<String> getStatements(InputStream is) throws IOException {
List<String> queries = new LinkedList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("--") || line.startsWith("#")) {
continue;
}
sb.append(line);
// check for end of declaration
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = sb.toString().trim();
if (!result.isEmpty()) {
queries.add(result);
}
// reset
sb = new StringBuilder();
}
}
}
return queries;
}
}

View file

@ -7,16 +7,20 @@ import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
public class SqlStorageProvider implements StorageProvider {
private static final String SAVE_USER_LOCAL_CURRENCY = "";
private static final String SAVE_USER_GLOBAL_CURRENCY = "";
private static final String SAVE_USER_LOCAL_CURRENCY_MYSQL = "INSERT INTO {prefix}_{context}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON DUPLICATE KEY UPDATE balance=?3;";
private static final String SAVE_USER_GLOBAL_CURRENCY_MYSQL = "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON DUPLICATE KEY UPDATE balance=?3;";
private static final String SAVE_USER_LOCAL_CURRENCY = "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?3;";
private static final String SAVE_USER_GLOBAL_CURRENCY = "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?3;";
private static final String LOAD_WHOLE_USER = """
SELECT currency_identifier, balance
@ -46,15 +50,40 @@ public class SqlStorageProvider implements StorageProvider {
);
}
@Override
public void init() throws Exception {
this.connectionFactory.init(this.plugin);
List<String> statements;
String schemaFileName = "schema/" + this.connectionFactory.getImplementationName().toLowerCase() + ".sql";
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
if (is == null)
throw new IOException("Failed to load schema file: " + schemaFileName);
statements = SchemaReader.getStatements(is).stream()
.map(this.statementProcessor)
.toList();
}
try (Connection c = this.connectionFactory.getConnection()) {
try (Statement s = c.createStatement()) {
for (String statement : statements) {
s.addBatch(statement);
}
s.executeBatch();
}
}
}
@Override
public @NonNull User loadUser(@NonNull UUID uniqueId) throws Exception {
String uniqueIdString = uniqueId.toString();
dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(LOAD_WHOLE_USER)) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(LOAD_WHOLE_USER))) {
ps.setString(1, uniqueIdString);
ps.setString(2, uniqueIdString);
ResultSet rs = ps.executeQuery();
@ -73,7 +102,53 @@ public class SqlStorageProvider implements StorageProvider {
}
@Override
public void saveUser(@NonNull User user) {
public void saveUser(@NonNull User user) throws Exception {
String uniqueIdString = user.getUniqueId().toString();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement psGlobal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY));
PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY))) {
for (Currency currency : this.plugin.getCurrencyManager().getRegisteredCurrencies()) {
BigDecimal balance = user.getBalance(currency.getProxy());
if (balance.compareTo(BigDecimal.ZERO) == 0) continue;
switch (currency.getType()) {
case GLOBAL -> {
psGlobal.setString(1, uniqueIdString);
psGlobal.setString(2, currency.getIdentifier());
psGlobal.setBigDecimal(3, balance);
psGlobal.addBatch();
}
case LOCAL -> {
psLocal.setString(1, uniqueIdString);
psLocal.setString(2, currency.getIdentifier());
psLocal.setBigDecimal(3, balance);
psLocal.addBatch();
}
}
}
System.out.println(psGlobal.toString());
System.out.println(psLocal.toString());
psGlobal.executeBatch();
psLocal.executeBatch();
}
}
}
private static boolean doesTableExists(Connection c, String table) throws SQLException {
try (ResultSet rs = c.getMetaData().getTables(c.getCatalog(), null, "%s", null)) {
while (rs.next()) {
if (rs.getString(3).equalsIgnoreCase(table)) {
return true;
}
}
}
return false;
}
}

View file

@ -6,6 +6,8 @@ import java.sql.Connection;
import java.util.function.Function;
public interface ConnectionFactory {
String getImplementationName();
void init(LightEcoPlugin plugin);
void shutdown() throws Exception;

View file

@ -3,19 +3,20 @@ package dev.xhyrom.lighteco.common.storage.provider.sql.connection.file;
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory;
import java.io.File;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Function;
abstract class FileConnectionFactory implements ConnectionFactory {
private Connection connection;
private final File file;
private final Path file;
public FileConnectionFactory(File file) {
public FileConnectionFactory(Path file) {
this.file = file;
}
protected abstract Connection createConnection(File file) throws SQLException;
protected abstract Connection createConnection(Path file) throws SQLException;
@Override
public void shutdown() throws Exception {

View file

@ -4,6 +4,7 @@ import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import java.io.File;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
@ -12,10 +13,15 @@ import java.util.function.Function;
public class H2ConnectionFactory extends FileConnectionFactory {
private Constructor<?> connectionConstructor;
public H2ConnectionFactory(File file) {
public H2ConnectionFactory(Path file) {
super(file);
}
@Override
public String getImplementationName() {
return "h2";
}
@Override
public void init(LightEcoPlugin plugin) {
// TODO: implement
@ -23,10 +29,10 @@ public class H2ConnectionFactory extends FileConnectionFactory {
}
@Override
protected Connection createConnection(File file) throws SQLException {
protected Connection createConnection(Path file) throws SQLException {
try {
return (Connection) this.connectionConstructor.newInstance(
"jdbc:h2:" + file.getAbsolutePath(),
"jdbc:h2:" + file,
new Properties(),
null, null, false
);

View file

@ -2,33 +2,45 @@ package dev.xhyrom.lighteco.common.storage.provider.sql.connection.file;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import java.io.File;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.function.Function;
public class SqliteConnectionFactory extends FileConnectionFactory {
private Constructor<?> connectionConstructor;
public SqliteConnectionFactory(File file) {
public SqliteConnectionFactory(Path file) {
super(file);
}
@Override
public void init(LightEcoPlugin plugin) {
// TODO: implement
public String getImplementationName() {
return "sqlite";
}
@Override
protected Connection createConnection(File file) throws SQLException {
public void init(LightEcoPlugin plugin) {
/*ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
return (Connection) this.connectionConstructor.newInstance(
"jdbc:sqlite:" + file.getAbsolutePath(),
Class<?> connectionClass = classLoader.loadClass("org.sqlite.jdbc4.JDBC4Connection");
this.connectionConstructor = connectionClass.getConstructor(String.class, String.class, Properties.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}*/
}
@Override
protected Connection createConnection(Path file) throws SQLException {
try {
/*return (Connection) this.connectionConstructor.newInstance(
"jdbc:sqlite:" + file,
new Properties(),
null, null, false
);
);*/
return DriverManager.getConnection("jdbc:sqlite:" + file);
} catch (Exception e) {
if (e.getCause() instanceof SQLException) {
throw (SQLException) e.getCause();

View file

@ -0,0 +1,7 @@
package dev.xhyrom.lighteco.common.util;
@FunctionalInterface
public interface ThrowableRunnable {
void run() throws Exception;
}

View file

@ -3,11 +3,11 @@ CREATE TABLE IF NOT EXISTS `{prefix}_users` (
`currency_identifier` VARCHAR(255) NOT NULL,
`balance` DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (`uuid`, `currency_identifier`)
) DEFAULT CHARSET = utf8mb4;
);
CREATE TABLE IF NOT EXISTS `{prefix}_{context}_users` (
`uuid` VARCHAR(36) NOT NULL,
`currency_identifier` VARCHAR(255) NOT NULL,
`balance` DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (`uuid`, `currency_identifier`)
) DEFAULT CHARSET = utf8mb4;
);