From 4858e59b7060986b35b56377f0640bab3e8eb016 Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 22 Jan 2018 21:32:31 +0000 Subject: [PATCH] Don't relocate H2 or SQLite depends in favour of loading into isolated classloaders (fixes #704) --- bukkit/pom.xml | 8 -- bungee/pom.xml | 8 -- common/pom.xml | 14 ---- .../common/dependencies/Dependency.java | 47 +++++++---- .../dependencies/DependencyManager.java | 71 +++++++++++++---- .../dependencies/DependencyRegistry.java | 26 ++++--- .../classloader/IsolatedClassLoader.java | 43 +++++++++++ .../dependencies/relocation/Relocation.java | 16 ++-- .../relocation/RelocationHandler.java | 77 ++++++------------- .../common/storage/StorageFactory.java | 25 +++--- .../connection/file/H2ConnectionFactory.java | 41 ++++++---- .../file/SQLiteConnectionFactory.java | 53 +++++++++---- sponge/pom.xml | 8 -- 13 files changed, 255 insertions(+), 182 deletions(-) create mode 100644 common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/IsolatedClassLoader.java diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 400642c5..791c641b 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -68,14 +68,6 @@ org.postgresql me.lucko.luckperms.lib.postgresql - - org.h2 - me.lucko.luckperms.lib.h2 - - - org.sqlite - me.lucko.luckperms.lib.sqlite - com.zaxxer.hikari me.lucko.luckperms.lib.hikari diff --git a/bungee/pom.xml b/bungee/pom.xml index 197e8e82..11b2e48f 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -68,14 +68,6 @@ org.postgresql me.lucko.luckperms.lib.postgresql - - org.h2 - me.lucko.luckperms.lib.h2 - - - org.sqlite - me.lucko.luckperms.lib.sqlite - com.zaxxer.hikari me.lucko.luckperms.lib.hikari diff --git a/common/pom.xml b/common/pom.xml index 88d762d3..40ea3b2e 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -147,20 +147,6 @@ 2.7.3 provided - - - com.h2database - h2 - 1.4.196 - provided - - - - org.xerial - sqlite-jdbc - 3.21.0 - provided - redis.clients diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java index 3705963d..83d566c6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java @@ -25,7 +25,6 @@ package me.lucko.luckperms.common.dependencies; -import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import me.lucko.luckperms.common.dependencies.relocation.Relocation; @@ -91,15 +90,17 @@ public enum Dependency { "com.h2database", "h2", "1.4.196", - "CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU=", - Relocation.of("h2", "org{}h2") + "CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU=" + // we don't apply relocations to h2 - it gets loaded via + // an isolated classloader ), SQLITE_DRIVER( "org.xerial", "sqlite-jdbc", "3.21.0", - "bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y=", - Relocation.of("sqlite", "org{}sqlite") + "bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y=" + // we don't apply relocations to sqlite - it gets loaded via + // an isolated classloader ), HIKARI( "com{}zaxxer", @@ -125,20 +126,20 @@ public enum Dependency { "mongo-java-driver", "3.5.0", "gxrbKVSI/xM6r+6uL7g7I0DzNV+hlNTtfw4UL13XdK8=", - ImmutableList.builder() - .addAll(Relocation.of("mongodb", "com{}mongodb")) - .addAll(Relocation.of("bson", "org{}bson")) - .build() + Relocation.allOf( + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ) ), JEDIS( "redis.clients", "jedis", "2.9.0", "HqqWy45QVeTVF0Z/DzsrPLvGKn2dHotqI8YX7GDThvo=", - ImmutableList.builder() - .addAll(Relocation.of("jedis", "redis{}clients{}jedis")) - .addAll(Relocation.of("commonspool2", "org{}apache{}commons{}pool2")) - .build() + Relocation.allOf( + Relocation.of("jedis", "redis{}clients{}jedis"), + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ) ), COMMONS_POOL_2( "org.apache.commons", @@ -173,10 +174,10 @@ public enum Dependency { "configurate-hocon", "3.3", "UIy5FVmsBUG6+Z1mpIEE2EXgtOI1ZL0p/eEW+BbtGLU=", - ImmutableList.builder() - .addAll(Relocation.of("configurate", "ninja{}leaping{}configurate")) - .addAll(Relocation.of("hocon", "com{}typesafe{}config")) - .build() + Relocation.allOf( + Relocation.of("configurate", "ninja{}leaping{}configurate"), + Relocation.of("hocon", "com{}typesafe{}config") + ) ), HOCON_CONFIG( "com{}typesafe", @@ -197,6 +198,10 @@ public enum Dependency { this(groupId, artifactId, version, checksum, Collections.emptyList()); } + Dependency(String groupId, String artifactId, String version, String checksum, Relocation relocation) { + this(groupId, artifactId, version, checksum, Collections.singletonList(relocation)); + } + Dependency(String groupId, String artifactId, String version, String checksum, List relocations) { this( String.format(MAVEN_CENTRAL_FORMAT, @@ -210,6 +215,14 @@ public enum Dependency { ); } + Dependency(String url, String version, String checksum) { + this(url, version, checksum, Collections.emptyList()); + } + + Dependency(String url, String version, String checksum, Relocation relocation) { + this(url, version, checksum, Collections.singletonList(relocation)); + } + Dependency(String url, String version, String checksum, List relocations) { this.url = url; this.version = version; diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java index ed9cf7b6..e76d2a6e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java @@ -25,8 +25,10 @@ package me.lucko.luckperms.common.dependencies; +import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteStreams; +import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader; import me.lucko.luckperms.common.dependencies.relocation.Relocation; import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; @@ -34,6 +36,7 @@ import me.lucko.luckperms.common.storage.StorageType; import java.io.File; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Files; import java.security.MessageDigest; @@ -41,8 +44,10 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; -import java.util.EnumSet; +import java.util.EnumMap; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -52,7 +57,8 @@ public class DependencyManager { private final LuckPermsPlugin plugin; private final MessageDigest digest; private final DependencyRegistry registry; - private final EnumSet alreadyLoaded = EnumSet.noneOf(Dependency.class); + private final EnumMap loaded = new EnumMap<>(Dependency.class); + private final Map, IsolatedClassLoader> loaders = new HashMap<>(); private RelocationHandler relocationHandler = null; public DependencyManager(LuckPermsPlugin plugin) { @@ -72,7 +78,7 @@ public class DependencyManager { return this.relocationHandler; } - public File getSaveDirectory() { + private File getSaveDirectory() { File saveDirectory = new File(this.plugin.getDataDirectory(), "lib"); if (!(saveDirectory.exists() || saveDirectory.mkdirs())) { throw new RuntimeException("Unable to create lib dir - " + saveDirectory.getPath()); @@ -81,12 +87,43 @@ public class DependencyManager { return saveDirectory; } + public IsolatedClassLoader obtainClassLoaderWith(Set dependencies) { + ImmutableSet set = ImmutableSet.copyOf(dependencies); + + for (Dependency dependency : dependencies) { + if (!this.loaded.containsKey(dependency)) { + throw new IllegalStateException("Dependency " + dependency + " is not loaded."); + } + } + + synchronized (this.loaders) { + IsolatedClassLoader classLoader = this.loaders.get(set); + if (classLoader != null) { + return classLoader; + } + + URL[] urls = set.stream() + .map(this.loaded::get) + .map(file -> { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }) + .toArray(URL[]::new); + + classLoader = new IsolatedClassLoader(urls); + this.loaders.put(set, classLoader); + return classLoader; + } + } + public void loadStorageDependencies(Set storageTypes) { loadDependencies(this.registry.resolveStorageDependencies(storageTypes)); } public void loadDependencies(Set dependencies) { - this.plugin.getLog().info("Identified the following dependencies: " + dependencies.toString()); File saveDirectory = getSaveDirectory(); // create a list of file sources @@ -94,7 +131,7 @@ public class DependencyManager { // obtain a file for each of the dependencies for (Dependency dependency : dependencies) { - if (!this.alreadyLoaded.add(dependency)) { + if (this.loaded.containsKey(dependency)) { continue; } @@ -108,23 +145,23 @@ public class DependencyManager { } // apply any remapping rules to the files - List remappedJars = new ArrayList<>(sources.size()); + List remappedJars = new ArrayList<>(sources.size()); for (Source source : sources) { try { // apply remap rules List relocations = source.dependency.getRelocations(); if (relocations.isEmpty()) { - remappedJars.add(source.file); + remappedJars.add(source); continue; } File input = source.file; - File output = new File(input.getParentFile(), "remap-" + input.getName()); + File output = new File(input.getParentFile(), "remapped-" + input.getName()); // if the remapped file exists already, just use that. if (output.exists()) { - remappedJars.add(output); + remappedJars.add(new Source(source.dependency, output)); continue; } @@ -135,7 +172,7 @@ public class DependencyManager { this.plugin.getLog().info("Attempting to apply relocations to " + input.getName() + "..."); relocationHandler.remap(input, output, relocations); - remappedJars.add(output); + remappedJars.add(new Source(source.dependency, output)); } catch (Throwable e) { this.plugin.getLog().severe("Unable to remap the source file '" + source.dependency.name() + "'."); e.printStackTrace(); @@ -143,17 +180,23 @@ public class DependencyManager { } // load each of the jars - for (File file : remappedJars) { + for (Source source : remappedJars) { + if (!DependencyRegistry.shouldAutoLoad(source.dependency)) { + this.loaded.put(source.dependency, source.file); + continue; + } + try { - this.plugin.getPluginClassLoader().loadJar(file); + this.plugin.getPluginClassLoader().loadJar(source.file); + this.loaded.put(source.dependency, source.file); } catch (Throwable e) { - this.plugin.getLog().severe("Failed to load dependency jar '" + file.getName() + "'."); + this.plugin.getLog().severe("Failed to load dependency jar '" + source.file.getName() + "'."); e.printStackTrace(); } } } - public File downloadDependency(File saveDirectory, Dependency dependency) throws Exception { + private File downloadDependency(File saveDirectory, Dependency dependency) throws Exception { String fileName = dependency.name().toLowerCase() + "-" + dependency.getVersion() + ".jar"; File file = new File(saveDirectory, fileName); diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java index fb56b6d3..4ed2c584 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java @@ -87,7 +87,7 @@ public class DependencyRegistry { return dependencies; } - public static boolean classExists(String className) { + private static boolean classExists(String className) { try { Class.forName(className); return true; @@ -100,17 +100,19 @@ public class DependencyRegistry { return classExists("org.slf4j.Logger") && classExists("org.slf4j.LoggerFactory"); } - // used to remap dependencies - we check to see if they're present to avoid loading unnecessarily. - - public static boolean asmPresent() { - return classExists("org.objectweb.asm.ClassReader") && - classExists("org.objectweb.asm.ClassVisitor") && - classExists("org.objectweb.asm.ClassWriter"); - } - - public static boolean asmCommonsPresent() { - return classExists("org.objectweb.asm.commons.ClassRemapper") && - classExists("org.objectweb.asm.commons.Remapper"); + public static boolean shouldAutoLoad(Dependency dependency) { + switch (dependency) { + // all used within 'isolated' classloaders, and are therefore not + // relocated. + case ASM: + case ASM_COMMONS: + case JAR_RELOCATOR: + case H2_DRIVER: + case SQLITE_DRIVER: + return false; + default: + return true; + } } } diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/IsolatedClassLoader.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/IsolatedClassLoader.java new file mode 100644 index 00000000..4cd934e7 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/IsolatedClassLoader.java @@ -0,0 +1,43 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.dependencies.classloader; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * A classloader "isolated" from the rest of the Minecraft server. + * + *

Used to load specific LuckPerms dependencies without causing conflicts + * with other plugins, or libraries provided by the server implementation.

+ */ +public class IsolatedClassLoader extends URLClassLoader { + + public IsolatedClassLoader(URL[] urls) { + super(urls, null); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocation.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocation.java index e9ec5abb..64366c87 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocation.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocation.java @@ -25,23 +25,23 @@ package me.lucko.luckperms.common.dependencies.relocation; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public final class Relocation { - public static List of(String name, String... packages) { - List ret = new ArrayList<>(); - for (String p : packages) { - ret.add(new Relocation(p.replace("{}", "."), "me.lucko.luckperms.lib." + name)); - } - return ret; + public static Relocation of(String id, String pattern) { + return new Relocation(pattern.replace("{}", "."), "me.lucko.luckperms.lib." + id); + } + + public static List allOf(Relocation... relocations) { + return Arrays.asList(relocations); } private final String pattern; private final String relocatedPattern; - public Relocation(String pattern, String relocatedPattern) { + private Relocation(String pattern, String relocatedPattern) { this.pattern = pattern; this.relocatedPattern = relocatedPattern; } diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/RelocationHandler.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/RelocationHandler.java index 26b21b0a..ea304e02 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/RelocationHandler.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/RelocationHandler.java @@ -27,86 +27,55 @@ package me.lucko.luckperms.common.dependencies.relocation; import me.lucko.luckperms.common.dependencies.Dependency; import me.lucko.luckperms.common.dependencies.DependencyManager; +import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader; import java.io.File; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Handles class runtime relocation of packages in downloaded dependencies */ -public class RelocationHandler implements AutoCloseable { - private final DependencyManager dependencyManager; +public class RelocationHandler { + private static final Set DEPENDENCIES = EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR); - private URLClassLoader classLoader; - private Constructor relocatorConstructor; - private Method relocatorRunMethod; + private final Constructor jarRelocatorConstructor; + private final Method jarRelocatorRunMethod; public RelocationHandler(DependencyManager dependencyManager) { - this.dependencyManager = dependencyManager; try { - setup(); + // download the required dependencies for remapping + dependencyManager.loadDependencies(DEPENDENCIES); + // get a classloader containing the required dependencies as sources + IsolatedClassLoader classLoader = dependencyManager.obtainClassLoaderWith(DEPENDENCIES); + + // load the relocator class + Class jarRelocatorClass = classLoader.loadClass("me.lucko.jarrelocator.JarRelocator"); + + // prepare the the reflected constructor & method instances + this.jarRelocatorConstructor = jarRelocatorClass.getDeclaredConstructor(File.class, File.class, Map.class); + this.jarRelocatorConstructor.setAccessible(true); + + this.jarRelocatorRunMethod = jarRelocatorClass.getDeclaredMethod("run"); + this.jarRelocatorRunMethod.setAccessible(true); } catch (Exception e) { throw new RuntimeException(e); } } - private void setup() throws Exception { - File saveDirectory = this.dependencyManager.getSaveDirectory(); - - // download the required dependencies for the remapping. - File asm = this.dependencyManager.downloadDependency(saveDirectory, Dependency.ASM); - File asmCommons = this.dependencyManager.downloadDependency(saveDirectory, Dependency.ASM_COMMONS); - File jarRelocator = this.dependencyManager.downloadDependency(saveDirectory, Dependency.JAR_RELOCATOR); - - URL[] urls = new URL[]{ - asm.toURI().toURL(), - asmCommons.toURI().toURL(), - jarRelocator.toURI().toURL() - }; - - // construct an isolated classloader instance containing the dependencies needed - this.classLoader = new URLClassLoader(urls, null); - - // load the relocator class - Class relocatorClass = this.classLoader.loadClass("me.lucko.jarrelocator.JarRelocator"); - - // prepare the the reflected constructor & method instances - this.relocatorConstructor = relocatorClass.getDeclaredConstructor(File.class, File.class, Map.class); - this.relocatorConstructor.setAccessible(true); - - this.relocatorRunMethod = relocatorClass.getDeclaredMethod("run"); - this.relocatorRunMethod.setAccessible(true); - } - public void remap(File input, File output, List relocations) throws Exception { - if (this.classLoader == null) { - throw new IllegalStateException("ClassLoader is closed"); - } - Map mappings = new HashMap<>(); for (Relocation relocation : relocations) { mappings.put(relocation.getPattern(), relocation.getRelocatedPattern()); } // create and invoke a new relocator - Object relocator = this.relocatorConstructor.newInstance(input, output, mappings); - this.relocatorRunMethod.invoke(relocator); - } - - @Override - public void close() throws IOException { - if (this.classLoader == null) { - return; - } - - this.classLoader.close(); - this.classLoader = null; + Object relocator = this.jarRelocatorConstructor.newInstance(input, output, mappings); + this.jarRelocatorRunMethod.invoke(relocator); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java index 69b7f7e4..b5f5cab2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/StorageFactory.java @@ -122,28 +122,33 @@ public class StorageFactory { private AbstractDao makeDao(StorageType method) { switch (method) { case MARIADB: - return new SqlDao(this.plugin, new MariaDbConnectionFactory( - this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), + return new SqlDao( + this.plugin, + new MariaDbConnectionFactory(this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) ); case MYSQL: - return new SqlDao(this.plugin, new MySqlConnectionFactory( - this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), + return new SqlDao( + this.plugin, + new MySqlConnectionFactory(this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) ); case SQLITE: - return new SqlDao(this.plugin, new SQLiteConnectionFactory( - new File(this.plugin.getDataDirectory(), "luckperms-sqlite.db")), + return new SqlDao( + this.plugin, + new SQLiteConnectionFactory(this.plugin, new File(this.plugin.getDataDirectory(), "luckperms-sqlite.db")), this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) ); case H2: - return new SqlDao(this.plugin, new H2ConnectionFactory( - new File(this.plugin.getDataDirectory(), "luckperms-h2")), + return new SqlDao( + this.plugin, + new H2ConnectionFactory(this.plugin, new File(this.plugin.getDataDirectory(), "luckperms-h2")), this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) ); case POSTGRESQL: - return new SqlDao(this.plugin, new PostgreConnectionFactory( - this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), + return new SqlDao( + this.plugin, + new PostgreConnectionFactory(this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) ); case MONGODB: diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/H2ConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/H2ConnectionFactory.java index c13f5ce3..7fe4a6a7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/H2ConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/H2ConnectionFactory.java @@ -25,19 +25,27 @@ package me.lucko.luckperms.common.storage.dao.sql.connection.file; -import org.h2.Driver; +import me.lucko.luckperms.common.dependencies.Dependency; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URLClassLoader; import java.sql.Connection; +import java.sql.Driver; import java.sql.SQLException; +import java.util.EnumSet; import java.util.Properties; -import java.util.concurrent.locks.ReentrantLock; public class H2ConnectionFactory extends FlatfileConnectionFactory { - private final ReentrantLock lock = new ReentrantLock(); + + // the driver used to obtain connections + private final Driver driver; + // the active connection private NonClosableConnection connection; - public H2ConnectionFactory(File file) { + public H2ConnectionFactory(LuckPermsPlugin plugin, File file) { super("H2", file); // backwards compat @@ -45,20 +53,25 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory { if (data.exists()) { data.renameTo(new File(file.getParent(), file.getName() + ".mv.db")); } + + // setup the classloader + URLClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER)); + try { + Class driverClass = classLoader.loadClass("org.h2.Driver"); + Method loadMethod = driverClass.getMethod("load"); + this.driver = (Driver) loadMethod.invoke(null); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } } @Override - public Connection getConnection() throws SQLException { - this.lock.lock(); - try { - if (this.connection == null || this.connection.isClosed()) { - Connection connection = Driver.load().connect("jdbc:h2:" + this.file.getAbsolutePath(), new Properties()); - if (connection != null) { - this.connection = new NonClosableConnection(connection); - } + public synchronized Connection getConnection() throws SQLException { + if (this.connection == null || this.connection.isClosed()) { + Connection connection = this.driver.connect("jdbc:h2:" + this.file.getAbsolutePath(), new Properties()); + if (connection != null) { + this.connection = new NonClosableConnection(connection); } - } finally { - this.lock.unlock(); } if (this.connection == null) { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/SQLiteConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/SQLiteConnectionFactory.java index 57224df3..7cbf4980 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/SQLiteConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/connection/file/SQLiteConnectionFactory.java @@ -25,19 +25,26 @@ package me.lucko.luckperms.common.storage.dao.sql.connection.file; -import org.sqlite.JDBC; +import me.lucko.luckperms.common.dependencies.Dependency; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URLClassLoader; import java.sql.Connection; import java.sql.SQLException; +import java.util.EnumSet; import java.util.Properties; -import java.util.concurrent.locks.ReentrantLock; public class SQLiteConnectionFactory extends FlatfileConnectionFactory { - private final ReentrantLock lock = new ReentrantLock(); + + // the method invoked to obtain new connection instances + private final Method createConnectionMethod; + // the active connection private NonClosableConnection connection; - public SQLiteConnectionFactory(File file) { + public SQLiteConnectionFactory(LuckPermsPlugin plugin, File file) { super("SQLite", file); // backwards compat @@ -45,21 +52,37 @@ public class SQLiteConnectionFactory extends FlatfileConnectionFactory { if (data.exists()) { data.renameTo(file); } + + // setup the classloader + URLClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.SQLITE_DRIVER)); + try { + Class jdcbClass = classLoader.loadClass("org.sqlite.JDBC"); + this.createConnectionMethod = jdcbClass.getMethod("createConnection", String.class, Properties.class); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private Connection createConnection(String url) throws SQLException { + try { + return (Connection) this.createConnectionMethod.invoke(null, url, new Properties()); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof SQLException) { + throw ((SQLException) e.getCause()); + } + throw new RuntimeException(e); + } } @Override - public Connection getConnection() throws SQLException { - this.lock.lock(); - try { - if (this.connection == null || this.connection.isClosed()) { - Connection connection = JDBC.createConnection("jdbc:sqlite:" + this.file.getAbsolutePath(), new Properties()); - if (connection != null) { - this.connection = new NonClosableConnection(connection); - } + public synchronized Connection getConnection() throws SQLException { + if (this.connection == null || this.connection.isClosed()) { + Connection connection = createConnection("jdbc:sqlite:" + this.file.getAbsolutePath()); + if (connection != null) { + this.connection = new NonClosableConnection(connection); } - - } finally { - this.lock.unlock(); } if (this.connection == null) { diff --git a/sponge/pom.xml b/sponge/pom.xml index bae01921..f6a76659 100644 --- a/sponge/pom.xml +++ b/sponge/pom.xml @@ -64,14 +64,6 @@ org.postgresql me.lucko.luckperms.lib.postgresql - - org.h2 - me.lucko.luckperms.lib.h2 - - - org.sqlite - me.lucko.luckperms.lib.sqlite - com.zaxxer.hikari me.lucko.luckperms.lib.hikari