From 06f2a95f5d8c4eb6cd13656cd6dbb0322c4f3cdc Mon Sep 17 00:00:00 2001 From: Petr Ilin Date: Sat, 24 Dec 2022 20:19:03 +0300 Subject: H2 v1->v2 migration, external database libraries --- build.gradle | 7 - config/spotbugs/suppressions.xml | 3 + .../java/net/elytrium/limboauth/LimboAuth.java | 49 ++----- src/main/java/net/elytrium/limboauth/Settings.java | 2 +- .../limboauth/dependencies/BaseLibrary.java | 94 +++++++++++++ .../limboauth/dependencies/DatabaseLibrary.java | 149 +++++++++++++++++++++ .../dependencies/IsolatedClassLoader.java | 32 +++++ 7 files changed, 291 insertions(+), 45 deletions(-) create mode 100644 src/main/java/net/elytrium/limboauth/dependencies/BaseLibrary.java create mode 100644 src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java create mode 100644 src/main/java/net/elytrium/limboauth/dependencies/IsolatedClassLoader.java diff --git a/build.gradle b/build.gradle index f63c6da..d2f7bf8 100644 --- a/build.gradle +++ b/build.gradle @@ -51,10 +51,6 @@ dependencies { implementation("com.j256.ormlite:ormlite-jdbc:6.1") - implementation("com.h2database:h2:1.4.200") - implementation('mysql:mysql-connector-java:8.0.31') - implementation('org.postgresql:postgresql:42.5.1') - implementation("org.bstats:bstats-velocity:3.0.0") implementation("de.mkammerer:argon2-jvm-nolibs:2.11") @@ -100,7 +96,6 @@ shadowJar { relocate("at.favre.lib", "net.elytrium.limboauth.thirdparty.at.favre.lib") relocate("com.j256.ormlite", "net.elytrium.limboauth.thirdparty.com.j256.ormlite") - relocate("com.mysql", "net.elytrium.limboauth.thirdparty.com.mysql") relocate("com.sun.jna", "net.elytrium.limboauth.thirdparty.com.sun.jna") { exclude("com.sun.jna.Native") // For compatibility with native methods. } @@ -108,8 +103,6 @@ shadowJar { relocate("dev.samstevens.totp", "net.elytrium.limboauth.thirdparty.dev.samstevens.totp") relocate("org.apache.commons.codec", "net.elytrium.limboauth.thirdparty.org.apache.commons.codec") relocate("org.bstats", "net.elytrium.limboauth.thirdparty.org.bstats") - relocate("org.h2", "net.elytrium.limboauth.thirdparty.org.h2") - relocate("org.postgresql", "net.elytrium.limboauth.thirdparty.org.postgresql") } license { diff --git a/config/spotbugs/suppressions.xml b/config/spotbugs/suppressions.xml index 833396e..9e62805 100644 --- a/config/spotbugs/suppressions.xml +++ b/config/spotbugs/suppressions.xml @@ -14,4 +14,7 @@ + + + diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java index a42322a..46b0864 100644 --- a/src/main/java/net/elytrium/limboauth/LimboAuth.java +++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java @@ -26,8 +26,8 @@ import com.j256.ormlite.dao.DaoManager; import com.j256.ormlite.dao.GenericRawResults; import com.j256.ormlite.db.DatabaseType; import com.j256.ormlite.field.FieldType; -import com.j256.ormlite.jdbc.JdbcPooledConnectionSource; import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.table.TableInfo; import com.j256.ormlite.table.TableUtils; import com.velocitypowered.api.command.CommandManager; @@ -90,6 +90,7 @@ import net.elytrium.limboauth.command.LimboAuthCommand; import net.elytrium.limboauth.command.PremiumCommand; import net.elytrium.limboauth.command.TotpCommand; import net.elytrium.limboauth.command.UnregisterCommand; +import net.elytrium.limboauth.dependencies.DatabaseLibrary; import net.elytrium.limboauth.event.AuthPluginReloadEvent; import net.elytrium.limboauth.event.PreAuthorizationEvent; import net.elytrium.limboauth.event.PreEvent; @@ -158,7 +159,7 @@ public class LimboAuth { private ScheduledTask purgeCacheTask; private ScheduledTask purgePremiumCacheTask; - private JdbcPooledConnectionSource connectionSource; + private ConnectionSource connectionSource; private Dao playerDao; private Pattern nicknameValidationPattern; private Limbo authServer; @@ -267,29 +268,14 @@ public class LimboAuth { this.cachedAuthChecks.clear(); Settings.DATABASE dbConfig = Settings.IMP.DATABASE; - switch (dbConfig.STORAGE_TYPE.toLowerCase(Locale.ROOT)) { - case "h2": { - this.connectionSource = new JdbcPooledConnectionSource("jdbc:h2:" + this.dataDirectoryFile.getAbsoluteFile() + "/limboauth"); - break; - } - case "mysql": { - this.connectionSource = new JdbcPooledConnectionSource( - "jdbc:mysql://" + dbConfig.HOSTNAME + "/" + dbConfig.DATABASE + dbConfig.CONNECTION_PARAMETERS, dbConfig.USER, dbConfig.PASSWORD - ); - break; - } - case "postgresql": { - this.connectionSource = new JdbcPooledConnectionSource( - "jdbc:postgresql://" + dbConfig.HOSTNAME + "/" + dbConfig.DATABASE + dbConfig.CONNECTION_PARAMETERS, dbConfig.USER, dbConfig.PASSWORD - ); - break; - } - default: { - LOGGER.error("Wrong database type."); - this.server.shutdown(); - return; - } - } + DatabaseLibrary databaseLibrary = DatabaseLibrary.valueOf(dbConfig.STORAGE_TYPE.toUpperCase(Locale.ROOT)); + this.connectionSource = databaseLibrary.connectToORM( + this.dataDirectoryFile.toPath().toAbsolutePath(), + dbConfig.HOSTNAME, + dbConfig.DATABASE + dbConfig.CONNECTION_PARAMETERS, + dbConfig.USER, + dbConfig.PASSWORD + ); this.nicknameValidationPattern = Pattern.compile(Settings.IMP.MAIN.ALLOWED_NICKNAME_REGEX); @@ -789,7 +775,7 @@ public class LimboAuth { return this.server; } - public JdbcPooledConnectionSource getConnectionSource() { + public ConnectionSource getConnectionSource() { return this.connectionSource; } @@ -797,17 +783,6 @@ public class LimboAuth { return this.playerDao; } - static { - // requireNonNull prevents the shade plugin from excluding the drivers in minimized jar. - Objects.requireNonNull(com.mysql.cj.jdbc.Driver.class); - Objects.requireNonNull(com.mysql.cj.conf.url.SingleConnectionUrl.class); - - Objects.requireNonNull(org.h2.Driver.class); - Objects.requireNonNull(org.h2.engine.Engine.class); - - Objects.requireNonNull(org.postgresql.Driver.class); - } - private static void setLogger(Logger logger) { LOGGER = logger; } diff --git a/src/main/java/net/elytrium/limboauth/Settings.java b/src/main/java/net/elytrium/limboauth/Settings.java index f8733ad..e258da7 100644 --- a/src/main/java/net/elytrium/limboauth/Settings.java +++ b/src/main/java/net/elytrium/limboauth/Settings.java @@ -375,7 +375,7 @@ public class Settings extends YamlConfig { @Comment("Database settings") public static class DATABASE { - @Comment("Database type: mysql, postgresql or h2.") + @Comment("Database type: mysql, postgresql, sqlite or h2.") public String STORAGE_TYPE = "h2"; @Comment("Settings for Network-based database (like MySQL, PostgreSQL): ") diff --git a/src/main/java/net/elytrium/limboauth/dependencies/BaseLibrary.java b/src/main/java/net/elytrium/limboauth/dependencies/BaseLibrary.java new file mode 100644 index 0000000..e8c34b5 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/dependencies/BaseLibrary.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 - 2022 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.dependencies; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +public enum BaseLibrary { + H2_V1( + "com.h2database", + "h2", + "1.4.200" + ), + H2_V2( + "com.h2database", + "h2", + "2.1.214" + ), + MYSQL( + "com.mysql", + "mysql-connector-j", + "8.0.31" + ), + POSTGRESQL( + "org.postgresql", + "postgresql", + "42.5.1" + ), + SQLITE( + "org.xerial", + "sqlite-jdbc", + "3.40.0.0" + ); + + private final Path filenamePath; + private final URL mavenRepoURL; + + BaseLibrary(String groupId, String artifactId, String version) { + String mavenPath = String.format("%s/%s/%s/%s-%s.jar", + groupId.replace(".", "/"), + artifactId, + version, + artifactId, + version + ); + + this.filenamePath = Path.of("libraries/" + mavenPath); + + URL mavenRepoURL = null; + try { + mavenRepoURL = new URL("https://repo1.maven.org/maven2/" + mavenPath); + } catch (Exception e) { + e.printStackTrace(); + } + + this.mavenRepoURL = mavenRepoURL; + } + + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") + public URL getClassLoaderURL() throws MalformedURLException { + if (!Files.exists(this.filenamePath)) { + try { + try (InputStream in = this.mavenRepoURL.openStream()) { + Files.createDirectories(this.filenamePath.getParent()); + Files.copy(in, Files.createFile(this.filenamePath), StandardCopyOption.REPLACE_EXISTING); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return this.filenamePath.toUri().toURL(); + } +} diff --git a/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java b/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java new file mode 100644 index 0000000..3a8a070 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2021 - 2022 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.dependencies; + +import com.j256.ormlite.jdbc.JdbcSingleConnectionSource; +import com.j256.ormlite.support.ConnectionSource; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.Statement; +import java.util.Properties; + +public enum DatabaseLibrary { + H2_LEGACY_V1( + BaseLibrary.H2_V1, + (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.h2.Driver"), jdbc, null, null, false), + (dir, hostname, database) -> "jdbc:h2:" + dir + "/limboauth" + ), + H2( + BaseLibrary.H2_V2, + (classLoader, dir, jdbc, user, password) -> { + Connection modernConnection = fromDriver(classLoader.loadClass("org.h2.Driver"), jdbc, null, null, true); + + Path legacyDatabase = dir.resolve("limboauth.mv.db"); + if (Files.exists(legacyDatabase)) { + Path dumpFile = dir.resolve("limboauth.dump.sql"); + try (Connection legacyConnection = H2_LEGACY_V1.connect(dir, null, null, user, password)) { + try (Statement migrateStatement = legacyConnection.createStatement()) { + migrateStatement.execute("SCRIPT TO '" + dumpFile + "'"); + } + } + + try (Statement migrateStatement = modernConnection.createStatement()) { + migrateStatement.execute("RUNSCRIPT FROM '" + dumpFile + "'"); + } + + Files.delete(dumpFile); + Files.move(legacyDatabase, dir.resolve("limboauth-v1-backup.mv.db")); + } + + return modernConnection; + }, + (dir, hostname, database) -> "jdbc:h2:" + dir + "/limboauth-v2" + ), + MYSQL( + BaseLibrary.MYSQL, + (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("com.mysql.cj.jdbc.Driver"), jdbc, user, password, true), + (dir, hostname, database) -> + "jdbc:mysql://" + hostname + "/" + database + ), + POSTGRESQL( + BaseLibrary.POSTGRESQL, + (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.postgresql.Driver"), jdbc, user, password, true), + (dir, hostname, database) -> "jdbc:postgresql://" + hostname + "/" + database + ), + SQLITE( + BaseLibrary.SQLITE, + (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.sqlite.JDBC"), jdbc, user, password, true), + (dir, hostname, database) -> "jdbc:sqlite:" + dir + "/limboauth.db" + ); + + private final BaseLibrary baseLibrary; + private final DatabaseConnector connector; + private final DatabaseStringGetter stringGetter; + + DatabaseLibrary(BaseLibrary baseLibrary, DatabaseConnector connector, DatabaseStringGetter stringGetter) { + this.baseLibrary = baseLibrary; + this.connector = connector; + this.stringGetter = stringGetter; + } + + public Connection connect(ClassLoader classLoader, Path dir, String hostname, String database, String user, String password) throws Exception { + return this.connect(classLoader, dir, this.stringGetter.getJdbcString(dir, hostname, database), user, password); + } + + public Connection connect(Path dir, String hostname, String database, String user, String password) throws Exception { + return this.connect(dir, this.stringGetter.getJdbcString(dir, hostname, database), user, password); + } + + public Connection connect(ClassLoader classLoader, Path dir, String jdbc, String user, String password) throws Exception { + return this.connector.connect(classLoader, dir, jdbc, user, password); + } + + public Connection connect(Path dir, String jdbc, String user, String password) throws Exception { + return this.connector.connect(new IsolatedClassLoader(new URL[]{this.baseLibrary.getClassLoaderURL()}), dir, jdbc, user, password); + } + + public ConnectionSource connectToORM(Path dir, String hostname, String database, String user, String password) throws Exception { + String jdbc = this.stringGetter.getJdbcString(dir, hostname, database); + URL baseLibraryURL = this.baseLibrary.getClassLoaderURL(); + ClassLoader currentClassLoader = DatabaseLibrary.class.getClassLoader(); + Method addPath = currentClassLoader.getClass().getDeclaredMethod("addPath", Path.class); + addPath.setAccessible(true); + addPath.invoke(currentClassLoader, Path.of(baseLibraryURL.toURI())); + + return new JdbcSingleConnectionSource(jdbc, this.connect(currentClassLoader, dir, jdbc, hostname, user, password)); + } + + private static Connection fromDriver(Class connectionClass, String jdbc, String user, String password, boolean register) throws Exception { + Constructor legacyConstructor = connectionClass.getConstructor(); + + Properties info = new Properties(); + if (user != null) { + info.put("user", user); + } + + if (password != null) { + info.put("password", password); + } + + Object driver = legacyConstructor.newInstance(); + + if (!register) { + DriverManager.deregisterDriver((Driver) driver); + } + + Method connect = connectionClass.getDeclaredMethod("connect", String.class, Properties.class); + connect.setAccessible(true); + return (Connection) connect.invoke(driver, jdbc, info); + } + + public interface DatabaseConnector { + Connection connect(ClassLoader classLoader, Path dir, String jdbc, String user, String password) throws Exception; + } + + public interface DatabaseStringGetter { + String getJdbcString(Path dir, String hostname, String database); + } +} diff --git a/src/main/java/net/elytrium/limboauth/dependencies/IsolatedClassLoader.java b/src/main/java/net/elytrium/limboauth/dependencies/IsolatedClassLoader.java new file mode 100644 index 0000000..ee53ddd --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/dependencies/IsolatedClassLoader.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 - 2022 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.dependencies; + +import java.net.URL; +import java.net.URLClassLoader; + +public class IsolatedClassLoader extends URLClassLoader { + + public IsolatedClassLoader(URL[] urls) { + super(urls, ClassLoader.getSystemClassLoader().getParent()); + } + + static { + ClassLoader.registerAsParallelCapable(); + } +} -- cgit