Don't relocate H2 or SQLite depends in favour of loading into isolated classloaders (fixes #704)

This commit is contained in:
Luck 2018-01-22 21:32:31 +00:00
parent 1f70ad978f
commit 4858e59b70
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
13 changed files with 255 additions and 182 deletions

View File

@ -68,14 +68,6 @@
<pattern>org.postgresql</pattern>
<shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>me.lucko.luckperms.lib.h2</shadedPattern>
</relocation>
<relocation>
<pattern>org.sqlite</pattern>
<shadedPattern>me.lucko.luckperms.lib.sqlite</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>

View File

@ -68,14 +68,6 @@
<pattern>org.postgresql</pattern>
<shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>me.lucko.luckperms.lib.h2</shadedPattern>
</relocation>
<relocation>
<pattern>org.sqlite</pattern>
<shadedPattern>me.lucko.luckperms.lib.sqlite</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>

View File

@ -147,20 +147,6 @@
<version>2.7.3</version>
<scope>provided</scope>
</dependency>
<!-- h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<scope>provided</scope>
</dependency>
<!-- sqlite -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.21.0</version>
<scope>provided</scope>
</dependency>
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>

View File

@ -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.<Relocation>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.<Relocation>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.<Relocation>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<Relocation> 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<Relocation> relocations) {
this.url = url;
this.version = version;

View File

@ -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<Dependency> alreadyLoaded = EnumSet.noneOf(Dependency.class);
private final EnumMap<Dependency, File> loaded = new EnumMap<>(Dependency.class);
private final Map<ImmutableSet<Dependency>, 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<Dependency> dependencies) {
ImmutableSet<Dependency> 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<StorageType> storageTypes) {
loadDependencies(this.registry.resolveStorageDependencies(storageTypes));
}
public void loadDependencies(Set<Dependency> 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<File> remappedJars = new ArrayList<>(sources.size());
List<Source> remappedJars = new ArrayList<>(sources.size());
for (Source source : sources) {
try {
// apply remap rules
List<Relocation> 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);

View File

@ -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;
}
}
}

View File

@ -0,0 +1,43 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.
*
* <p>Used to load specific LuckPerms dependencies without causing conflicts
* with other plugins, or libraries provided by the server implementation.</p>
*/
public class IsolatedClassLoader extends URLClassLoader {
public IsolatedClassLoader(URL[] urls) {
super(urls, null);
}
}

View File

@ -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<Relocation> of(String name, String... packages) {
List<Relocation> 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<Relocation> 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;
}

View File

@ -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<Dependency> 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<Relocation> relocations) throws Exception {
if (this.classLoader == null) {
throw new IllegalStateException("ClassLoader is closed");
}
Map<String, String> 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);
}
}

View File

@ -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:

View File

@ -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) {

View File

@ -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) {

View File

@ -64,14 +64,6 @@
<pattern>org.postgresql</pattern>
<shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>me.lucko.luckperms.lib.h2</shadedPattern>
</relocation>
<relocation>
<pattern>org.sqlite</pattern>
<shadedPattern>me.lucko.luckperms.lib.sqlite</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>