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> <pattern>org.postgresql</pattern>
<shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern> <shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern>
</relocation> </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> <relocation>
<pattern>com.zaxxer.hikari</pattern> <pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern> <shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>

View File

@ -68,14 +68,6 @@
<pattern>org.postgresql</pattern> <pattern>org.postgresql</pattern>
<shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern> <shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern>
</relocation> </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> <relocation>
<pattern>com.zaxxer.hikari</pattern> <pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern> <shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>

View File

@ -147,20 +147,6 @@
<version>2.7.3</version> <version>2.7.3</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </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 --> <!-- Jedis -->
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>

View File

@ -25,7 +25,6 @@
package me.lucko.luckperms.common.dependencies; package me.lucko.luckperms.common.dependencies;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import me.lucko.luckperms.common.dependencies.relocation.Relocation; import me.lucko.luckperms.common.dependencies.relocation.Relocation;
@ -91,15 +90,17 @@ public enum Dependency {
"com.h2database", "com.h2database",
"h2", "h2",
"1.4.196", "1.4.196",
"CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU=", "CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU="
Relocation.of("h2", "org{}h2") // we don't apply relocations to h2 - it gets loaded via
// an isolated classloader
), ),
SQLITE_DRIVER( SQLITE_DRIVER(
"org.xerial", "org.xerial",
"sqlite-jdbc", "sqlite-jdbc",
"3.21.0", "3.21.0",
"bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y=", "bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y="
Relocation.of("sqlite", "org{}sqlite") // we don't apply relocations to sqlite - it gets loaded via
// an isolated classloader
), ),
HIKARI( HIKARI(
"com{}zaxxer", "com{}zaxxer",
@ -125,20 +126,20 @@ public enum Dependency {
"mongo-java-driver", "mongo-java-driver",
"3.5.0", "3.5.0",
"gxrbKVSI/xM6r+6uL7g7I0DzNV+hlNTtfw4UL13XdK8=", "gxrbKVSI/xM6r+6uL7g7I0DzNV+hlNTtfw4UL13XdK8=",
ImmutableList.<Relocation>builder() Relocation.allOf(
.addAll(Relocation.of("mongodb", "com{}mongodb")) Relocation.of("mongodb", "com{}mongodb"),
.addAll(Relocation.of("bson", "org{}bson")) Relocation.of("bson", "org{}bson")
.build() )
), ),
JEDIS( JEDIS(
"redis.clients", "redis.clients",
"jedis", "jedis",
"2.9.0", "2.9.0",
"HqqWy45QVeTVF0Z/DzsrPLvGKn2dHotqI8YX7GDThvo=", "HqqWy45QVeTVF0Z/DzsrPLvGKn2dHotqI8YX7GDThvo=",
ImmutableList.<Relocation>builder() Relocation.allOf(
.addAll(Relocation.of("jedis", "redis{}clients{}jedis")) Relocation.of("jedis", "redis{}clients{}jedis"),
.addAll(Relocation.of("commonspool2", "org{}apache{}commons{}pool2")) Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
.build() )
), ),
COMMONS_POOL_2( COMMONS_POOL_2(
"org.apache.commons", "org.apache.commons",
@ -173,10 +174,10 @@ public enum Dependency {
"configurate-hocon", "configurate-hocon",
"3.3", "3.3",
"UIy5FVmsBUG6+Z1mpIEE2EXgtOI1ZL0p/eEW+BbtGLU=", "UIy5FVmsBUG6+Z1mpIEE2EXgtOI1ZL0p/eEW+BbtGLU=",
ImmutableList.<Relocation>builder() Relocation.allOf(
.addAll(Relocation.of("configurate", "ninja{}leaping{}configurate")) Relocation.of("configurate", "ninja{}leaping{}configurate"),
.addAll(Relocation.of("hocon", "com{}typesafe{}config")) Relocation.of("hocon", "com{}typesafe{}config")
.build() )
), ),
HOCON_CONFIG( HOCON_CONFIG(
"com{}typesafe", "com{}typesafe",
@ -197,6 +198,10 @@ public enum Dependency {
this(groupId, artifactId, version, checksum, Collections.emptyList()); 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) { Dependency(String groupId, String artifactId, String version, String checksum, List<Relocation> relocations) {
this( this(
String.format(MAVEN_CENTRAL_FORMAT, 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) { Dependency(String url, String version, String checksum, List<Relocation> relocations) {
this.url = url; this.url = url;
this.version = version; this.version = version;

View File

@ -25,8 +25,10 @@
package me.lucko.luckperms.common.dependencies; package me.lucko.luckperms.common.dependencies;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams; 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.Relocation;
import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler; import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; 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.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -41,8 +44,10 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.EnumSet; import java.util.EnumMap;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -52,7 +57,8 @@ public class DependencyManager {
private final LuckPermsPlugin plugin; private final LuckPermsPlugin plugin;
private final MessageDigest digest; private final MessageDigest digest;
private final DependencyRegistry registry; 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; private RelocationHandler relocationHandler = null;
public DependencyManager(LuckPermsPlugin plugin) { public DependencyManager(LuckPermsPlugin plugin) {
@ -72,7 +78,7 @@ public class DependencyManager {
return this.relocationHandler; return this.relocationHandler;
} }
public File getSaveDirectory() { private File getSaveDirectory() {
File saveDirectory = new File(this.plugin.getDataDirectory(), "lib"); File saveDirectory = new File(this.plugin.getDataDirectory(), "lib");
if (!(saveDirectory.exists() || saveDirectory.mkdirs())) { if (!(saveDirectory.exists() || saveDirectory.mkdirs())) {
throw new RuntimeException("Unable to create lib dir - " + saveDirectory.getPath()); throw new RuntimeException("Unable to create lib dir - " + saveDirectory.getPath());
@ -81,12 +87,43 @@ public class DependencyManager {
return saveDirectory; 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) { public void loadStorageDependencies(Set<StorageType> storageTypes) {
loadDependencies(this.registry.resolveStorageDependencies(storageTypes)); loadDependencies(this.registry.resolveStorageDependencies(storageTypes));
} }
public void loadDependencies(Set<Dependency> dependencies) { public void loadDependencies(Set<Dependency> dependencies) {
this.plugin.getLog().info("Identified the following dependencies: " + dependencies.toString());
File saveDirectory = getSaveDirectory(); File saveDirectory = getSaveDirectory();
// create a list of file sources // create a list of file sources
@ -94,7 +131,7 @@ public class DependencyManager {
// obtain a file for each of the dependencies // obtain a file for each of the dependencies
for (Dependency dependency : dependencies) { for (Dependency dependency : dependencies) {
if (!this.alreadyLoaded.add(dependency)) { if (this.loaded.containsKey(dependency)) {
continue; continue;
} }
@ -108,23 +145,23 @@ public class DependencyManager {
} }
// apply any remapping rules to the files // 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) { for (Source source : sources) {
try { try {
// apply remap rules // apply remap rules
List<Relocation> relocations = source.dependency.getRelocations(); List<Relocation> relocations = source.dependency.getRelocations();
if (relocations.isEmpty()) { if (relocations.isEmpty()) {
remappedJars.add(source.file); remappedJars.add(source);
continue; continue;
} }
File input = source.file; 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 the remapped file exists already, just use that.
if (output.exists()) { if (output.exists()) {
remappedJars.add(output); remappedJars.add(new Source(source.dependency, output));
continue; continue;
} }
@ -135,7 +172,7 @@ public class DependencyManager {
this.plugin.getLog().info("Attempting to apply relocations to " + input.getName() + "..."); this.plugin.getLog().info("Attempting to apply relocations to " + input.getName() + "...");
relocationHandler.remap(input, output, relocations); relocationHandler.remap(input, output, relocations);
remappedJars.add(output); remappedJars.add(new Source(source.dependency, output));
} catch (Throwable e) { } catch (Throwable e) {
this.plugin.getLog().severe("Unable to remap the source file '" + source.dependency.name() + "'."); this.plugin.getLog().severe("Unable to remap the source file '" + source.dependency.name() + "'.");
e.printStackTrace(); e.printStackTrace();
@ -143,17 +180,23 @@ public class DependencyManager {
} }
// load each of the jars // 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 { try {
this.plugin.getPluginClassLoader().loadJar(file); this.plugin.getPluginClassLoader().loadJar(source.file);
this.loaded.put(source.dependency, source.file);
} catch (Throwable e) { } 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(); 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"; String fileName = dependency.name().toLowerCase() + "-" + dependency.getVersion() + ".jar";
File file = new File(saveDirectory, fileName); File file = new File(saveDirectory, fileName);

View File

@ -87,7 +87,7 @@ public class DependencyRegistry {
return dependencies; return dependencies;
} }
public static boolean classExists(String className) { private static boolean classExists(String className) {
try { try {
Class.forName(className); Class.forName(className);
return true; return true;
@ -100,17 +100,19 @@ public class DependencyRegistry {
return classExists("org.slf4j.Logger") && classExists("org.slf4j.LoggerFactory"); 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 shouldAutoLoad(Dependency dependency) {
switch (dependency) {
public static boolean asmPresent() { // all used within 'isolated' classloaders, and are therefore not
return classExists("org.objectweb.asm.ClassReader") && // relocated.
classExists("org.objectweb.asm.ClassVisitor") && case ASM:
classExists("org.objectweb.asm.ClassWriter"); case ASM_COMMONS:
case JAR_RELOCATOR:
case H2_DRIVER:
case SQLITE_DRIVER:
return false;
default:
return true;
} }
public static boolean asmCommonsPresent() {
return classExists("org.objectweb.asm.commons.ClassRemapper") &&
classExists("org.objectweb.asm.commons.Remapper");
} }
} }

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; package me.lucko.luckperms.common.dependencies.relocation;
import java.util.ArrayList; import java.util.Arrays;
import java.util.List; import java.util.List;
public final class Relocation { public final class Relocation {
public static List<Relocation> of(String name, String... packages) { public static Relocation of(String id, String pattern) {
List<Relocation> ret = new ArrayList<>(); return new Relocation(pattern.replace("{}", "."), "me.lucko.luckperms.lib." + id);
for (String p : packages) {
ret.add(new Relocation(p.replace("{}", "."), "me.lucko.luckperms.lib." + name));
} }
return ret;
public static List<Relocation> allOf(Relocation... relocations) {
return Arrays.asList(relocations);
} }
private final String pattern; private final String pattern;
private final String relocatedPattern; private final String relocatedPattern;
public Relocation(String pattern, String relocatedPattern) { private Relocation(String pattern, String relocatedPattern) {
this.pattern = pattern; this.pattern = pattern;
this.relocatedPattern = relocatedPattern; 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.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager; import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL; import java.util.EnumSet;
import java.net.URLClassLoader;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* Handles class runtime relocation of packages in downloaded dependencies * Handles class runtime relocation of packages in downloaded dependencies
*/ */
public class RelocationHandler implements AutoCloseable { public class RelocationHandler {
private final DependencyManager dependencyManager; private static final Set<Dependency> DEPENDENCIES = EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR);
private URLClassLoader classLoader; private final Constructor<?> jarRelocatorConstructor;
private Constructor<?> relocatorConstructor; private final Method jarRelocatorRunMethod;
private Method relocatorRunMethod;
public RelocationHandler(DependencyManager dependencyManager) { public RelocationHandler(DependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
try { 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) { } catch (Exception e) {
throw new RuntimeException(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 { 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<>(); Map<String, String> mappings = new HashMap<>();
for (Relocation relocation : relocations) { for (Relocation relocation : relocations) {
mappings.put(relocation.getPattern(), relocation.getRelocatedPattern()); mappings.put(relocation.getPattern(), relocation.getRelocatedPattern());
} }
// create and invoke a new relocator // create and invoke a new relocator
Object relocator = this.relocatorConstructor.newInstance(input, output, mappings); Object relocator = this.jarRelocatorConstructor.newInstance(input, output, mappings);
this.relocatorRunMethod.invoke(relocator); this.jarRelocatorRunMethod.invoke(relocator);
}
@Override
public void close() throws IOException {
if (this.classLoader == null) {
return;
}
this.classLoader.close();
this.classLoader = null;
} }
} }

View File

@ -122,28 +122,33 @@ public class StorageFactory {
private AbstractDao makeDao(StorageType method) { private AbstractDao makeDao(StorageType method) {
switch (method) { switch (method) {
case MARIADB: case MARIADB:
return new SqlDao(this.plugin, new MariaDbConnectionFactory( return new SqlDao(
this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), this.plugin,
new MariaDbConnectionFactory(this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
); );
case MYSQL: case MYSQL:
return new SqlDao(this.plugin, new MySqlConnectionFactory( return new SqlDao(
this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), this.plugin,
new MySqlConnectionFactory(this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
); );
case SQLITE: case SQLITE:
return new SqlDao(this.plugin, new SQLiteConnectionFactory( return new SqlDao(
new File(this.plugin.getDataDirectory(), "luckperms-sqlite.db")), this.plugin,
new SQLiteConnectionFactory(this.plugin, new File(this.plugin.getDataDirectory(), "luckperms-sqlite.db")),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
); );
case H2: case H2:
return new SqlDao(this.plugin, new H2ConnectionFactory( return new SqlDao(
new File(this.plugin.getDataDirectory(), "luckperms-h2")), this.plugin,
new H2ConnectionFactory(this.plugin, new File(this.plugin.getDataDirectory(), "luckperms-h2")),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
); );
case POSTGRESQL: case POSTGRESQL:
return new SqlDao(this.plugin, new PostgreConnectionFactory( return new SqlDao(
this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)), this.plugin,
new PostgreConnectionFactory(this.plugin.getConfiguration().get(ConfigKeys.DATABASE_VALUES)),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX) this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
); );
case MONGODB: case MONGODB:

View File

@ -25,19 +25,27 @@
package me.lucko.luckperms.common.storage.dao.sql.connection.file; 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.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.sql.Connection; import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;
public class H2ConnectionFactory extends FlatfileConnectionFactory { 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; private NonClosableConnection connection;
public H2ConnectionFactory(File file) { public H2ConnectionFactory(LuckPermsPlugin plugin, File file) {
super("H2", file); super("H2", file);
// backwards compat // backwards compat
@ -45,21 +53,26 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
if (data.exists()) { if (data.exists()) {
data.renameTo(new File(file.getParent(), file.getName() + ".mv.db")); 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 @Override
public Connection getConnection() throws SQLException { public synchronized Connection getConnection() throws SQLException {
this.lock.lock();
try {
if (this.connection == null || this.connection.isClosed()) { if (this.connection == null || this.connection.isClosed()) {
Connection connection = Driver.load().connect("jdbc:h2:" + this.file.getAbsolutePath(), new Properties()); Connection connection = this.driver.connect("jdbc:h2:" + this.file.getAbsolutePath(), new Properties());
if (connection != null) { if (connection != null) {
this.connection = new NonClosableConnection(connection); this.connection = new NonClosableConnection(connection);
} }
} }
} finally {
this.lock.unlock();
}
if (this.connection == null) { if (this.connection == null) {
throw new SQLException("Unable to get a connection."); throw new SQLException("Unable to get a connection.");

View File

@ -25,19 +25,26 @@
package me.lucko.luckperms.common.storage.dao.sql.connection.file; 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.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;
public class SQLiteConnectionFactory extends FlatfileConnectionFactory { 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; private NonClosableConnection connection;
public SQLiteConnectionFactory(File file) { public SQLiteConnectionFactory(LuckPermsPlugin plugin, File file) {
super("SQLite", file); super("SQLite", file);
// backwards compat // backwards compat
@ -45,23 +52,39 @@ public class SQLiteConnectionFactory extends FlatfileConnectionFactory {
if (data.exists()) { if (data.exists()) {
data.renameTo(file); 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 @Override
public Connection getConnection() throws SQLException { public synchronized Connection getConnection() throws SQLException {
this.lock.lock();
try {
if (this.connection == null || this.connection.isClosed()) { if (this.connection == null || this.connection.isClosed()) {
Connection connection = JDBC.createConnection("jdbc:sqlite:" + this.file.getAbsolutePath(), new Properties()); Connection connection = createConnection("jdbc:sqlite:" + this.file.getAbsolutePath());
if (connection != null) { if (connection != null) {
this.connection = new NonClosableConnection(connection); this.connection = new NonClosableConnection(connection);
} }
} }
} finally {
this.lock.unlock();
}
if (this.connection == null) { if (this.connection == null) {
throw new SQLException("Unable to get a connection."); throw new SQLException("Unable to get a connection.");
} }

View File

@ -64,14 +64,6 @@
<pattern>org.postgresql</pattern> <pattern>org.postgresql</pattern>
<shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern> <shadedPattern>me.lucko.luckperms.lib.postgresql</shadedPattern>
</relocation> </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> <relocation>
<pattern>com.zaxxer.hikari</pattern> <pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern> <shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>