MZDatabase + Provider & MZLang

This commit is contained in:
Molzonas 2025-08-29 02:56:49 +02:00
commit 44f74edd67
13 changed files with 679 additions and 0 deletions

113
.gitignore vendored Normal file
View File

@ -0,0 +1,113 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# Common working directory
run/

113
pom.xml Normal file
View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fr.molzonas</groupId>
<artifactId>mzcore</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>mzcore</name>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.20.6-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.50.3.0</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.7</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>3.20.6</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen</artifactId>
<version>3.20.6</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-meta</artifactId>
<version>3.20.6</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>7.0.2</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>11.11.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,97 @@
package fr.molzonas.mzcore;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MZLang {
private static final String DIRECTORY = "lang";
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
public static final String DEFAULT_LOCALE_TAG = "en_US";
private static final ConcurrentHashMap<Locale, ResourceBundle> CACHE = new ConcurrentHashMap<>();
@Getter @Setter private Locale currentLocale;
@Getter @Setter private String bundleName;
private final String dataFolder;
private static final LegacyComponentSerializer AMPERSAND =
LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
public MZLang(String bundleName, Locale currentLocale, String dataFolder) {
this.bundleName = bundleName;
this.currentLocale = currentLocale;
this.dataFolder = dataFolder;
}
public String getString(String key, Object... args) {
return this.getString(key, this.currentLocale, args);
}
public String getString(String key, Locale locale, Object... args) {
return MiniMessage.miniMessage().serialize(this.getComponent(key, locale, args));
}
public Component getComponent(String key, Locale locale, Object... args) {
Locale loc = locale != null ? locale : DEFAULT_LOCALE;
ResourceBundle rb = getResourceBundle(loc, dataFolder);
String pattern = rb.containsKey(key) ? rb.getString(key) : ResourceBundle.getBundle(bundleName, loc).getString(key);
MessageFormat mf = new MessageFormat(pattern, loc);
String formatted = mf.format(args == null ? new Object[0] : args);
return colorize(formatted);
}
private ResourceBundle getResourceBundle(Locale locale, String dataFolder) {
return CACHE.computeIfAbsent(locale, y -> ResourceBundle.getBundle(bundleName, y, new Utf8Control(dataFolder)));
}
public static Component colorize(String input) {
if (input == null || input.isEmpty()) return Component.empty();
return AMPERSAND.deserialize(input);
}
private static class Utf8Control extends ResourceBundle.Control {
private final String dataFolder;
public Utf8Control(String dataFolder) {
this.dataFolder = dataFolder;
}
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format,
ClassLoader loader, boolean reload) throws IOException {
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, "properties");
File file = new File(dataFolder, DIRECTORY + File.separator + resourceName);
try (FileInputStream fis = new FileInputStream(file)) {
try (InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
Properties prop = new Properties();
prop.load(isr);
return new PropertiesResourceBundle(prop);
}
}
}
}
private static class PropertiesResourceBundle extends ResourceBundle {
private final Properties props;
PropertiesResourceBundle(Properties props) { this.props = props; }
@Override protected Object handleGetObject(@NotNull String key) { return props.getProperty(key); }
@NotNull
@Override public Enumeration<String> getKeys() {
return Collections.enumeration(props.stringPropertyNames());
}
@Override public boolean containsKey(@NotNull String key) { return props.containsKey(key); }
}
}

View File

@ -0,0 +1,5 @@
package fr.molzonas.mzcore;
public class MZProperties {
// TODO
}

View File

@ -0,0 +1,5 @@
package fr.molzonas.mzcore.database;
public final class MZCodegen {
// TODO
}

View File

@ -0,0 +1,128 @@
package fr.molzonas.mzcore.database;
import lombok.Getter;
public class MZDatabaseConfiguration {
@Getter
private String type = "mariadb";
@Getter
private String host = "127.0.0.1";
@Getter
private int port = 3306;
@Getter
private String database = "myplugin";
@Getter
private String username = "username";
@Getter
private String password = "password";
@Getter
private String file = "plugins/MyPlugin/myplugin.db";
@Getter
private int maximumPoolSize = 10;
@Getter
private int minimumIdle = 2;
@Getter
private int maximumLifetime = 1800000;
@Getter
private int connectionTimeout = 10000;
@Getter
private int idleTimeout = 60000;
@Getter
private boolean sslEnabled = false;
@Getter
private String sslMode = "";
private MZDatabaseConfiguration() {}
public static Builder builder() {
return new Builder();
}
public boolean isSQLite() {
return "sqlite".equalsIgnoreCase(type);
}
public boolean isMariaDB() {
return "mariadb".equalsIgnoreCase(type);
}
public boolean isPostgreSQL() {
return "postgresql".equalsIgnoreCase(type);
}
public static class Builder {
final MZDatabaseConfiguration mzDatabaseConfiguration;
public Builder() {
this.mzDatabaseConfiguration = new MZDatabaseConfiguration();
}
public Builder setType(String type) {
this.mzDatabaseConfiguration.type = type;
return this;
}
public Builder setHost(String host) {
this.mzDatabaseConfiguration.host = host;
return this;
}
public Builder setPort(int port) {
this.mzDatabaseConfiguration.port = port;
return this;
}
public Builder setDatabase(String database) {
this.mzDatabaseConfiguration.database = database;
return this;
}
public Builder setUsername(String username) {
this.mzDatabaseConfiguration.username = username;
return this;
}
public Builder setPassword(String password) {
this.mzDatabaseConfiguration.password = password;
return this;
}
public Builder setFile(String file) {
this.mzDatabaseConfiguration.file = file;
return this;
}
public Builder setMaximumPoolSize(int maximumPoolSize) {
this.mzDatabaseConfiguration.maximumPoolSize = maximumPoolSize;
return this;
}
public Builder setMinimumIdle(int minimumIdle) {
this.mzDatabaseConfiguration.minimumIdle = minimumIdle;
return this;
}
public Builder setMaximumLifetime(int maximumLifetime) {
this.mzDatabaseConfiguration.maximumLifetime = maximumLifetime;
return this;
}
public Builder setConnectionTimeout(int connectionTimeout) {
this.mzDatabaseConfiguration.connectionTimeout = connectionTimeout;
return this;
}
public Builder setIdleTimeout(int idleTimeout) {
this.mzDatabaseConfiguration.idleTimeout = idleTimeout;
return this;
}
public Builder setSslEnabled(boolean sslEnabled) {
this.mzDatabaseConfiguration.sslEnabled = sslEnabled;
return this;
}
public Builder setSslMode(String sslMode) {
this.mzDatabaseConfiguration.sslMode = sslMode;
return this;
}
public MZDatabaseConfiguration build() {
return this.mzDatabaseConfiguration;
}
}
}

View File

@ -0,0 +1,35 @@
package fr.molzonas.mzcore.database;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class MZDatabaseExec {
private final Plugin plugin;
private final Executor executor;
public MZDatabaseExec(Plugin plugin, Executor executor) {
this.plugin = plugin;
this.executor = executor;
}
public MZDatabaseExec(Plugin plugin) {
this.plugin = plugin;
this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> new Thread(r, "MZCore-DB-"));
}
public <T>CompletableFuture<T> execute(Supplier<T> supplier) {
return CompletableFuture.supplyAsync(supplier, executor);
}
public void async(Runnable runnable) {
CompletableFuture.runAsync(runnable);
}
public <T> void backToMain(CompletableFuture<T> completableFuture, Consumer<T> then) {
completableFuture.thenAccept(res -> Bukkit.getScheduler().runTask(plugin, () -> then.accept(res)));
}
}

View File

@ -0,0 +1,66 @@
package fr.molzonas.mzcore.database;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import fr.molzonas.mzcore.database.provider.MZDatabaseProvider;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfoService;
import org.jooq.DSLContext;
import org.jooq.conf.ParamType;
import org.jooq.conf.Settings;
import org.jooq.impl.DSL;
import javax.sql.DataSource;
import static org.bukkit.Bukkit.getLogger;
public class MZDatabaseManager {
private static final int LEAK_DETECTION_THRESHOLD = 15000;
private final HikariDataSource dataSource;
private final DSLContext dsl;
public MZDatabaseManager(MZDatabaseConfiguration config, MZDatabaseProvider provider) {
HikariConfig hc = new HikariConfig();
hc.setJdbcUrl(provider.jdbcUrl(config));
if (!config.isSQLite()) {
hc.setUsername(config.getUsername());
hc.setPassword(config.getPassword());
}
hc.setMaximumPoolSize(config.getMaximumPoolSize());
hc.setMinimumIdle(config.getMinimumIdle());
hc.setConnectionTimeout(config.getConnectionTimeout());
hc.setIdleTimeout(config.getIdleTimeout());
hc.setMaxLifetime(config.getMaximumLifetime());
provider.tune(hc, config);
hc.setLeakDetectionThreshold(LEAK_DETECTION_THRESHOLD);
hc.setPoolName("MZCore-" + config.getType());
hc.setConnectionInitSql("SET NAMES utf8mb4");
this.dataSource = new HikariDataSource(hc);
Flyway flyway = Flyway.configure()
.dataSource(dataSource)
.locations(provider.migrationLocation())
.baselineOnMigrate(true)
.load();
flyway.migrate();
MigrationInfoService info = flyway.info();
// TODO change logger method
getLogger().info("DB schema at " + info.current().getVersion() + " - " + info.current().getDescription());
Settings st = new Settings()
.withParamType(ParamType.INDEXED)
.withExecuteLogging(false);
this.dsl = DSL.using(dataSource, provider.dialect(), st);
}
public DSLContext dsl() {
return this.dsl;
}
public void shutdown() {
this.dataSource.close();
}
}

View File

@ -0,0 +1,15 @@
package fr.molzonas.mzcore.database.provider;
import fr.molzonas.mzcore.database.MZDatabaseConfiguration;
import org.jooq.SQLDialect;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.util.Properties;
public interface MZDatabaseProvider {
SQLDialect dialect();
String jdbcUrl(MZDatabaseConfiguration mzDatabaseConfiguration);
default void tune(HikariConfig hikariConfig, MZDatabaseConfiguration mzDatabaseConfiguration) {}
String migrationLocation();
}

View File

@ -0,0 +1,37 @@
package fr.molzonas.mzcore.database.provider;
import com.zaxxer.hikari.HikariConfig;
import fr.molzonas.mzcore.database.MZDatabaseConfiguration;
import org.jooq.SQLDialect;
public class MZDatabaseProviderMariaDB implements MZDatabaseProvider {
@Override
public SQLDialect dialect() {
return SQLDialect.MARIADB;
}
@Override
public String jdbcUrl(MZDatabaseConfiguration c) {
StringBuilder ssl = new StringBuilder("jdbc:mariadb://").append(c.getHost())
.append(":").append(c.getPort()).append("/").append(c.getDatabase())
.append("?useUnicode=true&characterEncoding=utf8&sessionVariables=sql_mode='STRICT_ALL_TABLES'");
if (c.isSslEnabled()) {
ssl.append("&useSSL=true");
if (c.getSslMode().equals("VERIFY_CA")) ssl.append("&verifyServerCertificate=true");
else ssl.append("&requireSSL=true");
}
return ssl.toString();
}
@Override
public void tune(HikariConfig hc, MZDatabaseConfiguration mc) {
hc.addDataSourceProperty("cachePrepStmts","true");
hc.addDataSourceProperty("prepStmtCacheSize","250");
hc.addDataSourceProperty("prepStmtCacheSqlLimit","2048");
}
@Override
public String migrationLocation() {
return "classpath:database/migration/mariadb";
}
}

View File

@ -0,0 +1,29 @@
package fr.molzonas.mzcore.database.provider;
import com.zaxxer.hikari.HikariConfig;
import fr.molzonas.mzcore.database.MZDatabaseConfiguration;
import org.jooq.SQLDialect;
public class MZDatabaseProviderPostgreSQL implements MZDatabaseProvider {
@Override
public SQLDialect dialect() {
return SQLDialect.POSTGRES;
}
@Override
public String jdbcUrl(MZDatabaseConfiguration c) {
String sslMode = c.getSslMode() == null || c.getSslMode().isBlank() ? "require" : c.getSslMode();
String mode = c.isSslEnabled() ? sslMode : "disable";
return "jdbc:postgresql://" + c.getHost() + ":" + c.getPort() + "/" + c.getDatabase() + "?sslmode=" + mode;
}
@Override
public void tune(HikariConfig hc, MZDatabaseConfiguration mc) {
hc.addDataSourceProperty("reWriteBatchedInserts", "true");
}
@Override
public String migrationLocation() {
return "classpath:database/migration/postgres";
}
}

View File

@ -0,0 +1,27 @@
package fr.molzonas.mzcore.database.provider;
import com.zaxxer.hikari.HikariConfig;
import fr.molzonas.mzcore.database.MZDatabaseConfiguration;
import org.jooq.SQLDialect;
public class MZDatabaseProviderSQLite implements MZDatabaseProvider {
@Override
public SQLDialect dialect() {
return SQLDialect.SQLITE;
}
@Override
public String jdbcUrl(MZDatabaseConfiguration c) {
return "jdbc:sqlite:" + c.getFile() + "?journal_mode=WAL&busy_timeout=5000";
}
@Override public void tune(HikariConfig hc, MZDatabaseConfiguration mc) {
hc.setMaximumPoolSize(1);
hc.setMinimumIdle(1);
}
@Override
public String migrationLocation() {
return "classpath:database/migration/sqlite";
}
}

View File

@ -0,0 +1,9 @@
name: MZCore
version: '1.0'
main: fr.molzonas.mzcore.MZCore
api-version: '1.20'
prefix: MZCore
load: STARTUP
authors: [ Molzonas ]
description: Start everything, an API for my dev.
website: https://www.molzonas.fr/