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;