diff --git a/bukkit/pom.xml b/bukkit/pom.xml
index 8dd5d34a2..c8e27c3f5 100644
--- a/bukkit/pom.xml
+++ b/bukkit/pom.xml
@@ -45,10 +45,65 @@
false
false
+
net.kyori.text
me.lucko.luckperms.lib.text
+
+ me.lucko.jarreloator
+ me.lucko.luckperms.lib.jarreloator
+
+
+
+
+ com.github.benmanes.caffeine
+ me.lucko.luckperms.lib.caffeine
+
+
+ org.mariadb.jdbc
+ me.lucko.luckperms.lib.mariadb
+
+
+ com.mysql
+ me.lucko.luckperms.lib.mysql
+
+
+ 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
+
+
+ com.mongodb
+ me.lucko.luckperms.lib.mongodb
+
+
+ org.bson
+ me.lucko.luckperms.lib.bson
+
+
+ redis.clients.jedis.shaded
+ me.lucko.luckperms.lib.jedis
+
+
+ ninja.leaping.configurate
+ me.lucko.luckperms.lib.configurate
+
+
+ com.typesafe.config
+ me.lucko.luckperms.lib.hocon
+
diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java
index bd5686749..79d248aed 100644
--- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java
+++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java
@@ -57,6 +57,8 @@ import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.LuckPermsCalculator;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
+import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
+import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -124,6 +126,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private DefaultsProvider defaultsProvider;
private ChildPermissionProvider childPermissionProvider;
private LocaleManager localeManager;
+ private PluginClassLoader pluginClassLoader;
private DependencyManager dependencyManager;
private CachedStateManager cachedStateManager;
private ContextManager contextManager;
@@ -151,6 +154,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
this.senderFactory = new BukkitSenderFactory(this);
this.log = new SenderLogger(this, getConsoleSender());
+ this.pluginClassLoader = new ReflectionClassLoader(this);
this.dependencyManager = new DependencyManager(this);
this.dependencyManager.loadDependencies(Collections.singleton(Dependency.CAFFEINE));
}
@@ -633,6 +637,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
return this.localeManager;
}
+ @Override
+ public PluginClassLoader getPluginClassLoader() {
+ return this.pluginClassLoader;
+ }
+
@Override
public DependencyManager getDependencyManager() {
return this.dependencyManager;
diff --git a/bungee/pom.xml b/bungee/pom.xml
index 1e2b1508d..9a13c36fc 100644
--- a/bungee/pom.xml
+++ b/bungee/pom.xml
@@ -45,10 +45,65 @@
false
false
+
net.kyori.text
me.lucko.luckperms.lib.text
+
+ me.lucko.jarreloator
+ me.lucko.luckperms.lib.jarreloator
+
+
+
+
+ com.github.benmanes.caffeine
+ me.lucko.luckperms.lib.caffeine
+
+
+ org.mariadb.jdbc
+ me.lucko.luckperms.lib.mariadb
+
+
+ com.mysql
+ me.lucko.luckperms.lib.mysql
+
+
+ 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
+
+
+ com.mongodb
+ me.lucko.luckperms.lib.mongodb
+
+
+ org.bson
+ me.lucko.luckperms.lib.bson
+
+
+ redis.clients.jedis.shaded
+ me.lucko.luckperms.lib.jedis
+
+
+ ninja.leaping.configurate
+ me.lucko.luckperms.lib.configurate
+
+
+ com.typesafe.config
+ me.lucko.luckperms.lib.hocon
+
diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java
index 6db8cd13b..118ac3eb9 100644
--- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java
+++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java
@@ -51,6 +51,8 @@ import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.LuckPermsCalculator;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
+import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
+import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -109,6 +111,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private EventFactory eventFactory;
private Logger log;
private LocaleManager localeManager;
+ private PluginClassLoader pluginClassLoader;
private DependencyManager dependencyManager;
private CachedStateManager cachedStateManager;
private ContextManager contextManager;
@@ -128,6 +131,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
this.senderFactory = new BungeeSenderFactory(this);
this.log = new SenderLogger(this, getConsoleSender());
+ this.pluginClassLoader = new ReflectionClassLoader(this);
this.dependencyManager = new DependencyManager(this);
this.dependencyManager.loadDependencies(Collections.singleton(Dependency.CAFFEINE));
}
@@ -435,6 +439,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
return this.localeManager;
}
+ @Override
+ public PluginClassLoader getPluginClassLoader() {
+ return this.pluginClassLoader;
+ }
+
@Override
public DependencyManager getDependencyManager() {
return this.dependencyManager;
diff --git a/common/pom.xml b/common/pom.xml
index 30379d12c..903be4885 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -44,6 +44,24 @@
provided
+
+
+ me.lucko
+ jar-relocator
+ 1.1
+ compile
+
+
+ org.ow2.asm
+ asm
+
+
+ org.ow2.asm
+ asm-commons
+
+
+
+
net.kyori
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 02420e342..6eba82e0a 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,57 +25,83 @@
package me.lucko.luckperms.common.dependencies;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
+import me.lucko.jarreloator.Relocation;
+import me.lucko.luckperms.common.dependencies.relocation.Relocations;
+
import java.io.InputStream;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
public enum Dependency {
+ ASM(
+ "org.ow2.asm",
+ "asm",
+ "5.2",
+ "Pl6g19osUVXvT0cNkJLULeNOP1PbZYnHwH1nIa30uj4="
+ ),
+ ASM_COMMONS(
+ "org.ow2.asm",
+ "asm-commons",
+ "5.2",
+ "zBMYiX4sdxy3l6aNX06mQcI6UfBDfKUXq+z5ZN2yZAs="
+ ),
+
CAFFEINE(
- "com.github.ben-manes.caffeine",
+ "com{}github{}ben-manes{}caffeine",
"caffeine",
"2.6.0",
- "JmT16VQnCnVBAjRJCQkkkjmSVx2jajpzeBuKwpbzDA8="
+ "JmT16VQnCnVBAjRJCQkkkjmSVx2jajpzeBuKwpbzDA8=",
+ Relocations.of("caffeine", "com{}github{}benmanes{}caffeine")
),
MARIADB_DRIVER(
- "org.mariadb.jdbc",
+ "org{}mariadb{}jdbc",
"mariadb-java-client",
"2.2.0",
- "/q0LPGHrp3L9rvKr7TuA6urbtXBqvXis92mP4KhxzUw="
+ "/q0LPGHrp3L9rvKr7TuA6urbtXBqvXis92mP4KhxzUw=",
+ Relocations.of("mariadb", "org{}mariadb{}jdbc")
),
MYSQL_DRIVER(
"mysql",
"mysql-connector-java",
"5.1.44",
- "d4RZVzTeWpoHBPB/tQP3mSafNy7L9MDUSOt4Ku9LGCc="
+ "d4RZVzTeWpoHBPB/tQP3mSafNy7L9MDUSOt4Ku9LGCc=",
+ Relocations.of("mysql", "com{}mysql")
),
POSTGRESQL_DRIVER(
- "org.postgresql",
+ "org{}postgresql",
"postgresql",
"9.4.1212",
- "DLKhWL4xrPIY4KThjI89usaKO8NIBkaHc/xECUsMNl0="
+ "DLKhWL4xrPIY4KThjI89usaKO8NIBkaHc/xECUsMNl0=",
+ Relocations.of("postgresql", "org{}postgresql")
),
H2_DRIVER(
"com.h2database",
"h2",
"1.4.196",
- "CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU="
+ "CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU=",
+ Relocations.of("h2", "org{}h2")
),
SQLITE_DRIVER(
"org.xerial",
"sqlite-jdbc",
"3.21.0",
- "bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y="
+ "bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y=",
+ Relocations.of("sqlite", "org{}sqlite")
),
HIKARI(
- "com.zaxxer",
+ "com{}zaxxer",
"HikariCP",
"2.7.4",
- "y9JE6/VmbydCqlV1z468+oqdkBswBk6aw+ECT178AT4="
+ "y9JE6/VmbydCqlV1z468+oqdkBswBk6aw+ECT178AT4=",
+ Relocations.of("hikari", "com{}zaxxer{}hikari")
),
SLF4J_SIMPLE(
"org.slf4j",
@@ -93,58 +119,90 @@ public enum Dependency {
"org.mongodb",
"mongo-java-driver",
"3.5.0",
- "gxrbKVSI/xM6r+6uL7g7I0DzNV+hlNTtfw4UL13XdK8="
+ "gxrbKVSI/xM6r+6uL7g7I0DzNV+hlNTtfw4UL13XdK8=",
+ ImmutableList.builder()
+ .addAll(Relocations.of("mongodb", "com{}mongodb"))
+ .addAll(Relocations.of("bson", "org{}bson"))
+ .build()
),
JEDIS(
"https://github.com/lucko/jedis/releases/download/jedis-2.9.1-shaded/jedis-2.9.1-shaded.jar",
"2.9.1-shaded",
- "mM19X6LyD97KP4RSbcCR5BTRAwQ0x9y02voX7ePOSjE="
+ "mM19X6LyD97KP4RSbcCR5BTRAwQ0x9y02voX7ePOSjE=",
+ Relocations.of("jedis", "redis{}clients{}jedis{}shaded")
),
CONFIGURATE_CORE(
- "ninja.leaping.configurate",
+ "ninja{}leaping{}configurate",
"configurate-core",
"3.3",
- "4leBJEqj1kVszaifZeKNl4hgHxG5M+Nk5TJKkPW2s4Y="
+ "4leBJEqj1kVszaifZeKNl4hgHxG5M+Nk5TJKkPW2s4Y=",
+ Relocations.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_GSON(
- "ninja.leaping.configurate",
+ "ninja{}leaping{}configurate",
"configurate-gson",
"3.3",
- "4HxrW3/ZKdn095x/W4gylQMNskdmteXYVxVv0UKGJA4="
+ "4HxrW3/ZKdn095x/W4gylQMNskdmteXYVxVv0UKGJA4=",
+ Relocations.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_YAML(
- "ninja.leaping.configurate",
+ "ninja{}leaping{}configurate",
"configurate-yaml",
"3.3",
- "hgADp3g+xHHPD34bAuxMWtB+OQ718Tlw69jVp2KPJNk="
+ "hgADp3g+xHHPD34bAuxMWtB+OQ718Tlw69jVp2KPJNk=",
+ Relocations.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_HOCON(
- "ninja.leaping.configurate",
+ "ninja{}leaping{}configurate",
"configurate-hocon",
"3.3",
- "UIy5FVmsBUG6+Z1mpIEE2EXgtOI1ZL0p/eEW+BbtGLU="
+ "UIy5FVmsBUG6+Z1mpIEE2EXgtOI1ZL0p/eEW+BbtGLU=",
+ ImmutableList.builder()
+ .addAll(Relocations.of("configurate", "ninja{}leaping{}configurate"))
+ .addAll(Relocations.of("hocon", "com{}typesafe{}config"))
+ .build()
),
HOCON_CONFIG(
- "com.typesafe",
+ "com{}typesafe",
"config",
"1.3.1",
- "5vrfxhCCINOmuGqn5OFsnnu4V7pYlViGMIuxOXImSvA="
+ "5vrfxhCCINOmuGqn5OFsnnu4V7pYlViGMIuxOXImSvA=",
+ Relocations.of("hocon", "com{}typesafe{}config")
);
private final String url;
private final String version;
private final byte[] checksum;
+ private final List relocations;
private static final String MAVEN_CENTRAL_FORMAT = "https://repo1.maven.org/maven2/%s/%s/%s/%s-%s.jar";
Dependency(String groupId, String artifactId, String version, String checksum) {
- this(String.format(MAVEN_CENTRAL_FORMAT, groupId.replace(".", "/"), artifactId, version, artifactId, version), version, checksum);
+ this(groupId, artifactId, version, checksum, Collections.emptyList());
}
- Dependency(String url, String version, String checksum) {
+ Dependency(String groupId, String artifactId, String version, String checksum, List relocations) {
+ this(
+ String.format(MAVEN_CENTRAL_FORMAT,
+ rewriteEscaping(groupId).replace(".", "/"),
+ rewriteEscaping(artifactId),
+ version,
+ rewriteEscaping(artifactId),
+ version
+ ),
+ version, checksum, relocations
+ );
+ }
+
+ Dependency(String url, String version, String checksum, List relocations) {
this.url = url;
this.version = version;
this.checksum = Base64.getDecoder().decode(checksum);
+ this.relocations = relocations;
+ }
+
+ private static String rewriteEscaping(String s) {
+ return s.replace("{}", ".");
}
public static void main(String[] args) throws Exception {
@@ -180,4 +238,8 @@ public enum Dependency {
public byte[] getChecksum() {
return this.checksum;
}
+
+ public List getRelocations() {
+ return this.relocations;
+ }
}
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 071ae8a6a..0cac64fa1 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,64 +25,34 @@
package me.lucko.luckperms.common.dependencies;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
-import me.lucko.luckperms.api.platform.PlatformType;
-import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.jarreloator.JarRelocator;
+import me.lucko.jarreloator.Relocation;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageType;
import java.io.File;
import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.MalformedURLException;
import java.net.URL;
-import java.net.URLClassLoader;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
-import java.util.LinkedHashSet;
+import java.util.EnumSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
* Responsible for loading runtime dependencies.
*/
public class DependencyManager {
- private static final Method ADD_URL_METHOD;
-
- static {
- Method addUrlMethod;
- try {
- addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
- addUrlMethod.setAccessible(true);
- } catch (NoSuchMethodException e) {
- throw new ExceptionInInitializerError(e);
- }
- ADD_URL_METHOD = addUrlMethod;
- }
-
- private static final Map> STORAGE_DEPENDENCIES = ImmutableMap.>builder()
- .put(StorageType.JSON, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_GSON))
- .put(StorageType.YAML, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_YAML))
- .put(StorageType.HOCON, ImmutableList.of(Dependency.HOCON_CONFIG, Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_HOCON))
- .put(StorageType.MONGODB, ImmutableList.of(Dependency.MONGODB_DRIVER))
- .put(StorageType.MARIADB, ImmutableList.of(Dependency.MARIADB_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
- .put(StorageType.MYSQL, ImmutableList.of(Dependency.MYSQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
- .put(StorageType.POSTGRESQL, ImmutableList.of(Dependency.POSTGRESQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
- .put(StorageType.SQLITE, ImmutableList.of(Dependency.SQLITE_DRIVER))
- .put(StorageType.H2, ImmutableList.of(Dependency.H2_DRIVER))
- .build();
-
private final LuckPermsPlugin plugin;
private final MessageDigest digest;
+ private final DependencyRegistry registry;
+ private final EnumSet alreadyLoaded = EnumSet.noneOf(Dependency.class);
public DependencyManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
@@ -91,94 +61,142 @@ public class DependencyManager {
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
+ this.registry = new DependencyRegistry(plugin);
}
public void loadStorageDependencies(Set storageTypes) {
- Set dependencies = new LinkedHashSet<>();
- for (StorageType storageType : storageTypes) {
- dependencies.addAll(STORAGE_DEPENDENCIES.get(storageType));
- }
-
- if (this.plugin.getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
- dependencies.add(Dependency.JEDIS);
- }
-
- // don't load slf4j if it's already present
- if (classExists("org.slf4j.Logger") && classExists("org.slf4j.LoggerFactory")) {
- dependencies.remove(Dependency.SLF4J_API);
- dependencies.remove(Dependency.SLF4J_SIMPLE);
- }
-
- // don't load configurate dependencies on sponge
- if (this.plugin.getServerType() == PlatformType.SPONGE) {
- dependencies.remove(Dependency.CONFIGURATE_CORE);
- dependencies.remove(Dependency.CONFIGURATE_GSON);
- dependencies.remove(Dependency.CONFIGURATE_YAML);
- dependencies.remove(Dependency.CONFIGURATE_HOCON);
- dependencies.remove(Dependency.HOCON_CONFIG);
- }
-
- loadDependencies(dependencies);
+ loadDependencies(this.registry.resolveStorageDependencies(storageTypes));
}
public void loadDependencies(Set dependencies) {
- this.plugin.getLog().info("Identified the following dependencies: " + dependencies.toString());
+ loadDependencies(dependencies, true);
+ }
- File libDir = new File(this.plugin.getDataDirectory(), "lib");
- if (!(libDir.exists() || libDir.mkdirs())) {
- throw new RuntimeException("Unable to create lib dir - " + libDir.getPath());
+ public void loadDependencies(Set dependencies, boolean applyRemapping) {
+ if (applyRemapping) {
+ this.plugin.getLog().info("Identified the following dependencies: " + dependencies.toString());
}
- // Download files.
- List filesToLoad = new ArrayList<>();
+ File saveDirectory = new File(this.plugin.getDataDirectory(), "lib");
+ if (!(saveDirectory.exists() || saveDirectory.mkdirs())) {
+ throw new RuntimeException("Unable to create lib dir - " + saveDirectory.getPath());
+ }
+
+ // create a list of file sources
+ List sources = new ArrayList<>();
+
+ // obtain a file for each of the dependencies
for (Dependency dependency : dependencies) {
+ if (!this.alreadyLoaded.add(dependency)) {
+ continue;
+ }
+
try {
- filesToLoad.add(downloadDependency(libDir, dependency));
+ File file = downloadDependency(saveDirectory, dependency);
+ sources.add(new Source(dependency, file));
} catch (Throwable e) {
this.plugin.getLog().severe("Exception whilst downloading dependency " + dependency.name());
e.printStackTrace();
}
}
- // Load classes.
- for (File file : filesToLoad) {
+ // apply any remapping rules to the files
+ List remappedJars = new ArrayList<>(sources.size());
+ if (applyRemapping) {
+ for (Source source : sources) {
+ try {
+ // apply remap rules
+ List relocations = source.dependency.getRelocations();
+
+ if (relocations.isEmpty()) {
+ remappedJars.add(source.file);
+ continue;
+ }
+
+ File input = source.file;
+ File output = new File(input.getParentFile(), "remap-" + input.getName());
+
+ // if the remapped file exists already, just use that.
+ if (output.exists()) {
+ remappedJars.add(output);
+ continue;
+ }
+
+ // make sure ASM is loaded
+ Set asmDepends = EnumSet.noneOf(Dependency.class);
+ if (!DependencyRegistry.asmPresent()) {
+ asmDepends.add(Dependency.ASM);
+ }
+ if (!DependencyRegistry.asmCommonsPresent()) {
+ asmDepends.add(Dependency.ASM_COMMONS);
+ }
+ if (!asmDepends.isEmpty()) {
+ // load asm before calling the jar relocator
+ loadDependencies(asmDepends, false);
+ }
+
+ // attempt to remap the jar.
+ this.plugin.getLog().info("Attempting to remap " + input.getName() + "...");
+ JarRelocator relocator = new JarRelocator(input, output, relocations);
+ relocator.run();
+
+ remappedJars.add(output);
+ } catch (Throwable e) {
+ this.plugin.getLog().severe("Unable to remap the source file '" + source.dependency.name() + "'.");
+ e.printStackTrace();
+ }
+ }
+ } else {
+ for (Source source : sources) {
+ remappedJars.add(source.file);
+ }
+ }
+
+ // load each of the jars
+ for (File file : remappedJars) {
try {
- loadJar(file);
- } catch (Throwable t) {
- this.plugin.getLog().severe("Failed to load dependency jar " + file.getName());
- t.printStackTrace();
+ this.plugin.getPluginClassLoader().loadJar(file);
+ } catch (Throwable e) {
+ this.plugin.getLog().severe("Failed to load dependency jar '" + file.getName() + "'.");
+ e.printStackTrace();
}
}
}
- private File downloadDependency(File libDir, 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);
- File file = new File(libDir, fileName);
+ // if the file already exists, don't attempt to re-download it.
if (file.exists()) {
return file;
}
URL url = new URL(dependency.getUrl());
-
- this.plugin.getLog().info("Dependency '" + fileName + "' could not be found. Attempting to download.");
try (InputStream in = url.openStream()) {
+
+ // download the jar content
byte[] bytes = ByteStreams.toByteArray(in);
if (bytes.length == 0) {
throw new RuntimeException("Empty stream");
}
+
+ // compute a hash for the downloaded file
byte[] hash = this.digest.digest(bytes);
this.plugin.getLog().info("Successfully downloaded '" + fileName + "' with checksum: " + Base64.getEncoder().encodeToString(hash));
+ // ensure the hash matches the expected checksum
if (!Arrays.equals(hash, dependency.getChecksum())) {
throw new RuntimeException("Downloaded file had an invalid hash. Expected: " + Base64.getEncoder().encodeToString(dependency.getChecksum()));
}
+ // if the checksum matches, save the content to disk
Files.write(file.toPath(), bytes);
}
+ // ensure the file saved correctly
if (!file.exists()) {
throw new IllegalStateException("File not present. - " + file.toString());
} else {
@@ -186,27 +204,13 @@ public class DependencyManager {
}
}
- private void loadJar(File file) {
- // get the classloader to load into
- ClassLoader classLoader = this.plugin.getClass().getClassLoader();
+ private static final class Source {
+ private final Dependency dependency;
+ private final File file;
- if (classLoader instanceof URLClassLoader) {
- try {
- ADD_URL_METHOD.invoke(classLoader, file.toURI().toURL());
- } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) {
- throw new RuntimeException("Unable to invoke URLClassLoader#addURL", e);
- }
- } else {
- throw new RuntimeException("Unknown classloader type: " + classLoader.getClass());
- }
- }
-
- private static boolean classExists(String className) {
- try {
- Class.forName(className);
- return true;
- } catch (ClassNotFoundException e) {
- return false;
+ private Source(Dependency dependency, File file) {
+ this.dependency = dependency;
+ this.file = file;
}
}
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
new file mode 100644
index 000000000..4a2724438
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java
@@ -0,0 +1,115 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import me.lucko.luckperms.api.platform.PlatformType;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+import me.lucko.luckperms.common.storage.StorageType;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DependencyRegistry {
+
+ private static final Map> STORAGE_DEPENDENCIES = ImmutableMap.>builder()
+ .put(StorageType.JSON, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_GSON))
+ .put(StorageType.YAML, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_YAML))
+ .put(StorageType.HOCON, ImmutableList.of(Dependency.HOCON_CONFIG, Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_HOCON))
+ .put(StorageType.MONGODB, ImmutableList.of(Dependency.MONGODB_DRIVER))
+ .put(StorageType.MARIADB, ImmutableList.of(Dependency.MARIADB_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
+ .put(StorageType.MYSQL, ImmutableList.of(Dependency.MYSQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
+ .put(StorageType.POSTGRESQL, ImmutableList.of(Dependency.POSTGRESQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
+ .put(StorageType.SQLITE, ImmutableList.of(Dependency.SQLITE_DRIVER))
+ .put(StorageType.H2, ImmutableList.of(Dependency.H2_DRIVER))
+ .build();
+
+ private final LuckPermsPlugin plugin;
+
+ public DependencyRegistry(LuckPermsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public Set resolveStorageDependencies(Set storageTypes) {
+ Set dependencies = new LinkedHashSet<>();
+ for (StorageType storageType : storageTypes) {
+ dependencies.addAll(STORAGE_DEPENDENCIES.get(storageType));
+ }
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
+ dependencies.add(Dependency.JEDIS);
+ }
+
+ // don't load slf4j if it's already present
+ if (slf4jPresent()) {
+ dependencies.remove(Dependency.SLF4J_API);
+ dependencies.remove(Dependency.SLF4J_SIMPLE);
+ }
+
+ // don't load configurate dependencies on sponge
+ if (this.plugin.getServerType() == PlatformType.SPONGE) {
+ dependencies.remove(Dependency.CONFIGURATE_CORE);
+ dependencies.remove(Dependency.CONFIGURATE_GSON);
+ dependencies.remove(Dependency.CONFIGURATE_YAML);
+ dependencies.remove(Dependency.CONFIGURATE_HOCON);
+ dependencies.remove(Dependency.HOCON_CONFIG);
+ }
+
+ return dependencies;
+ }
+
+ public static boolean classExists(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ private static boolean slf4jPresent() {
+ 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");
+ }
+
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/PluginClassLoader.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/PluginClassLoader.java
new file mode 100644
index 000000000..e592169bd
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/PluginClassLoader.java
@@ -0,0 +1,40 @@
+/*
+ * 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.io.File;
+import java.net.URL;
+
+/**
+ * Represents the plugins classloader
+ */
+public interface PluginClassLoader {
+
+ void loadJar(URL url);
+
+ void loadJar(File file);
+
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java
new file mode 100644
index 000000000..44263de45
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java
@@ -0,0 +1,77 @@
+/*
+ * 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.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class ReflectionClassLoader implements PluginClassLoader {
+ private static final Method ADD_URL_METHOD;
+
+ static {
+ Method addUrlMethod;
+ try {
+ addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+ addUrlMethod.setAccessible(true);
+ } catch (NoSuchMethodException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ ADD_URL_METHOD = addUrlMethod;
+ }
+
+ private final URLClassLoader classLoader;
+
+ public ReflectionClassLoader(Object plugin) throws IllegalStateException {
+ ClassLoader classLoader = plugin.getClass().getClassLoader();
+ if (classLoader instanceof URLClassLoader) {
+ this.classLoader = (URLClassLoader) classLoader;
+ } else {
+ throw new IllegalStateException("ClassLoader is not instance of URLClassLoader");
+ }
+ }
+
+ @Override
+ public void loadJar(URL url) {
+ try {
+ ADD_URL_METHOD.invoke(this.classLoader, url);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void loadJar(File file) {
+ try {
+ loadJar(file.toURI().toURL());
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocations.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocations.java
new file mode 100644
index 000000000..e7f813e5a
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/relocation/Relocations.java
@@ -0,0 +1,44 @@
+/*
+ * 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.relocation;
+
+import me.lucko.jarreloator.Relocation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class Relocations {
+
+ public static List of(String name, String... packages) {
+ List relocations = new ArrayList<>();
+ for (String p : packages) {
+ relocations.add(new Relocation(p.replace("{}", "."), "me.lucko.luckperms.lib." + name));
+ }
+ return relocations;
+ }
+
+ private Relocations() {}
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java
index df8e44ff8..603c3a718 100644
--- a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java
+++ b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java
@@ -39,6 +39,7 @@ import me.lucko.luckperms.common.commands.utils.CommandUtils;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.dependencies.DependencyManager;
+import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.logging.Logger;
@@ -154,6 +155,13 @@ public interface LuckPermsPlugin {
*/
LocaleManager getLocaleManager();
+ /**
+ * Gets the classloader wrapper for adding dependencies to the classpath
+ *
+ * @return the plugin classloader
+ */
+ PluginClassLoader getPluginClassLoader();
+
/**
* Gets the dependency manager for the plugin
*
diff --git a/sponge/pom.xml b/sponge/pom.xml
index ad2ef43d8..bffe9c45b 100644
--- a/sponge/pom.xml
+++ b/sponge/pom.xml
@@ -45,10 +45,53 @@
false
false
+
net.kyori.text
me.lucko.luckperms.lib.text
+
+ me.lucko.jarreloator
+ me.lucko.luckperms.lib.jarreloator
+
+
+
+
+ org.mariadb.jdbc
+ me.lucko.luckperms.lib.mariadb
+
+
+ com.mysql
+ me.lucko.luckperms.lib.mysql
+
+
+ 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
+
+
+ com.mongodb
+ me.lucko.luckperms.lib.mongodb
+
+
+ org.bson
+ me.lucko.luckperms.lib.bson
+
+
+ redis.clients.jedis.shaded
+ me.lucko.luckperms.lib.jedis
+
diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java
index a387820b3..6903afa0a 100644
--- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java
+++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java
@@ -47,6 +47,8 @@ import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.LuckPermsCalculator;
import me.lucko.luckperms.common.dependencies.DependencyManager;
+import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
+import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -177,6 +179,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
private me.lucko.luckperms.common.logging.Logger log;
private LuckPermsService service;
private LocaleManager localeManager;
+ private PluginClassLoader pluginClassLoader;
private DependencyManager dependencyManager;
private CachedStateManager cachedStateManager;
private ContextManager contextManager;
@@ -195,6 +198,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
this.localeManager = new NoopLocaleManager();
this.senderFactory = new SpongeSenderFactory(this);
this.log = new SenderLogger(this, getConsoleSender());
+ this.pluginClassLoader = new ReflectionClassLoader(this);
this.dependencyManager = new DependencyManager(this);
sendStartupBanner(getConsoleSender());
@@ -598,6 +602,11 @@ public class LPSpongePlugin implements LuckPermsPlugin {
return this.localeManager;
}
+ @Override
+ public PluginClassLoader getPluginClassLoader() {
+ return this.pluginClassLoader;
+ }
+
@Override
public DependencyManager getDependencyManager() {
return this.dependencyManager;