From c3e81e5934b6c216e3278a94c68df388785bc1de Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sun, 10 Nov 2024 22:35:49 +0100 Subject: [PATCH 01/19] Implement prototype, which is able to load legacy plugins --- LavalinkServer/build.gradle.kts | 1 + .../src/main/java/lavalink/server/Launcher.kt | 9 +- .../server/bootstrap/PluginClassWrapper.kt | 11 ++ .../bootstrap/PluginCompomentClassLoader.kt | 138 +++++++++++++++ .../lavalink/server/bootstrap/PluginLoader.kt | 62 +++++++ .../server/bootstrap/PluginManager.kt | 166 ++++-------------- .../server/bootstrap/PluginManifest.kt | 77 +++++++- .../lavalink/server/info/InfoRestHandler.kt | 5 +- 8 files changed, 328 insertions(+), 141 deletions(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt create mode 100644 LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt create mode 100644 LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt diff --git a/LavalinkServer/build.gradle.kts b/LavalinkServer/build.gradle.kts index ddb62b35a..3e4be4824 100644 --- a/LavalinkServer/build.gradle.kts +++ b/LavalinkServer/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.pluginApi) { exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") } + implementation("org.pf4j", "pf4j", "3.12.1") implementation(libs.bundles.metrics) implementation(libs.bundles.spring) { diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt index 8252b9a47..df03cb603 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt @@ -23,6 +23,8 @@ package lavalink.server import com.sedmelluq.discord.lavaplayer.tools.PlayerLibrary +import lavalink.server.bootstrap.LavalinkPluginDescriptor +import lavalink.server.bootstrap.PluginComponentClassLoader import lavalink.server.bootstrap.PluginManager import lavalink.server.info.AppInfo import lavalink.server.info.GitRepoState @@ -126,7 +128,7 @@ object Launcher { launchMain(parent, args) } - private fun launchPluginBootstrap() = SpringApplication(PluginManager::class.java).run { + private fun launchPluginBootstrap() = SpringApplication(AppInfo::class.java, PluginManager::class.java).run { setBannerMode(Banner.Mode.OFF) webApplicationType = WebApplicationType.NONE run() @@ -135,7 +137,8 @@ object Launcher { private fun launchMain(parent: ConfigurableApplicationContext, args: Array) { val pluginManager = parent.getBean(PluginManager::class.java) val properties = Properties() - properties["componentScan"] = pluginManager.pluginManifests.map { it.path } + properties["componentScan"] = pluginManager.loader.plugins + .map { (it.descriptor as LavalinkPluginDescriptor).path } .toMutableList().apply { add("lavalink.server") } SpringApplicationBuilder() @@ -143,7 +146,7 @@ object Launcher { .properties(properties) .web(WebApplicationType.SERVLET) .bannerMode(Banner.Mode.OFF) - .resourceLoader(DefaultResourceLoader(pluginManager.classLoader)) + .resourceLoader(DefaultResourceLoader(PluginComponentClassLoader(pluginManager.loader))) .listeners( ApplicationListener { event: Any -> when (event) { diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt new file mode 100644 index 000000000..7510de970 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt @@ -0,0 +1,11 @@ +package lavalink.server.bootstrap + +import org.pf4j.Plugin +import org.pf4j.PluginFactory +import org.pf4j.PluginWrapper + +class PluginClassWrapper(@get:JvmName("getLavalinkContext") val wrapper: PluginWrapper) : Plugin() + +object LavalinkPluginFactory : PluginFactory { + override fun create(pluginWrapper: PluginWrapper): Plugin = PluginClassWrapper(pluginWrapper) +} diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt new file mode 100644 index 000000000..0651fbe64 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt @@ -0,0 +1,138 @@ +package lavalink.server.bootstrap + +import org.pf4j.PluginWrapper +import org.pf4j.util.FileUtils +import org.slf4j.LoggerFactory +import java.io.InputStream +import java.net.URL +import java.nio.file.Path +import java.util.Enumeration +import java.util.stream.Stream +import kotlin.io.path.isDirectory +import kotlin.io.path.listDirectoryEntries + +private val LOG = LoggerFactory.getLogger(PluginComponentClassLoader::class.java) + +/** + * Implementation of [ClassLoader] which is aware of separate plugin class loaders. + */ +class PluginComponentClassLoader(pluginLoader: PluginLoader) : ClassLoader() { + + // This list does not necessarily need to be up to date + // it just acts as a fast path to resolve these names more quickly + private val systemPackages = listOf( + "java", + "javax", + "lavalink", + "kotlin", + "koltinx", + "jarkarte", + "git", + "org/springframework", + "org/jetbrains/kotlin", + "org/pf4j", + "com/sedmelluq", + "dev/arbjerg", + "io/prometheus", + "club/minnced", + "ch/qos/logback", + "io/sentry", + "com/github/oshi", + "freemarker", + "com/samskivert", + "groovy", + "org/thymeleaf/spring6", + "org/apache/jasper", + "org/aspectj", + "com/fasterxml/jackson/", + "javax", + "com/hazelcast", + "com/couchbase", + "org/infinispan/spring", + "org/cache2k", + "com/github/benmanes/caffeine/cache", + "com/fasterxml/jackson/dataformat/xml", + "io/r2dbc/spi", + "reactor", + "org/eclipse/jetty", + "org/apache/catalina", + "org/apache/coyote", + "freemarker/template", + "com/samskivert/mustache", + "org/thymeleaf/spring6", + "org/apache/jasper/compiler", + "com/google/gson", + "META-INF" + ) + + private val cache = pluginLoader.plugins + .associateBy { (it.descriptor as LavalinkPluginDescriptor).path.replace('.', '/') } + .toMutableMap() + + // Due to the nature of the legacy class loading some plugins might produce packages outside their defined paths + // For this reason we need to build a map of those packages + private val legacyCache = pluginLoader.plugins + .asSequence() + .filter { (it.descriptor as LavalinkPluginDescriptor).manifestVersion == PluginDescriptor.Version.V1 } + .map { it to findClassesProvidedByPlugin(it.pluginPath) } + .toList() + + fun findClassesProvidedByPlugin(pluginPath: Path, vararg path: String): List { + val fixPath = FileUtils.getPath(pluginPath, "", *path) + val childPaths = fixPath.listDirectoryEntries() + .filter { it.isDirectory() } + return (childPaths + childPaths + .flatMap { + findClassesProvidedByPlugin(pluginPath, *path, it.fileName.toString()) + }) + // Filter out top-level directories + .filter { it.toString().contains('/') } + } + + + private fun findLegacyPackage(name: String): PluginWrapper? { + return legacyCache.firstOrNull { (_, classes) -> + classes.any { name.startsWith(it.toString()) } + }?.first ?: run { + val newPath = name.substringBeforeLast('/') + if (newPath == name) return null + findLegacyPackage(newPath) + } + } + + private fun findPackage(pack: String): PluginWrapper? { + return cache.toList().find { (key, _) -> + pack.startsWith(key) + }?.second ?: run { + val newPath = pack.substringBeforeLast('/') + if (newPath == pack) return null + findPackage(newPath) + } + } + + private fun findClassLoader(name: String?): ClassLoader? { + if (name == null) return null + // Sometimes .class files are requested, as paths + // other times the class name gets requested + val pack = if (name.endsWith(".class")) { + name.substringBeforeLast('/') + } else { + name.substringBeforeLast('.').replace('.', '/') + } + if (systemPackages.any { pack.startsWith(it) }) return javaClass.classLoader + + LOG.debug("Found package of {} to be {}", name, pack) + val plugin = cache.getOrPut(pack) { findPackage(pack) } ?: findLegacyPackage(name) + ?: return javaClass.classLoader + + LOG.debug("Found class {} to be provided by plugin {}", name, plugin.pluginId) + + return plugin.pluginClassLoader + } + + override fun loadClass(name: String?): Class<*>? = findClassLoader(name)?.loadClass(name) + override fun getResource(name: String?): URL? = findClassLoader(name)?.getResource(name) + override fun getResources(name: String?): Enumeration? = findClassLoader(name)?.getResources(name) + override fun getResourceAsStream(name: String?): InputStream? = findClassLoader(name)?.getResourceAsStream(name) + override fun resources(name: String?): Stream? = findClassLoader(name)?.resources(name) +} \ No newline at end of file diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt new file mode 100644 index 000000000..601e88e76 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -0,0 +1,62 @@ +package lavalink.server.bootstrap + +import lavalink.server.info.AppInfo +import org.pf4j.ClassLoadingStrategy +import org.pf4j.CompoundPluginDescriptorFinder +import org.pf4j.CompoundPluginLoader +import org.pf4j.DefaultPluginLoader +import org.pf4j.DefaultPluginManager +import org.pf4j.DevelopmentPluginLoader +import org.pf4j.PluginLoader as BasePluginLoader +import org.pf4j.PluginClassLoader +import org.pf4j.PluginDescriptor +import org.pf4j.PluginDescriptorFinder +import org.pf4j.PluginFactory +import org.springframework.stereotype.Component +import java.nio.file.Path +import java.util.jar.Manifest +import kotlin.io.path.Path +import kotlin.io.path.extension + +@Component +class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : DefaultPluginManager(Path(pluginsConfig.pluginsDir)) { + + override fun getSystemVersion(): String = appInfo.versionBuild + override fun createPluginFactory(): PluginFactory = LavalinkPluginFactory + override fun createPluginLoader(): BasePluginLoader = CompoundPluginLoader() + .add(DevelopmentPluginLoader(this), this::isDevelopment) + .add(DefaultPluginLoader(this)) + .add(LegacyPluginLoader()) + + override fun createPluginDescriptorFinder(): PluginDescriptorFinder? { + return CompoundPluginDescriptorFinder().apply { + add(LegacyLavalinkDescriptorFinder) + add(LavalinkDescriptorFinder) + } + } + + private inner class LegacyPluginLoader : BasePluginLoader { + override fun isApplicable(pluginPath: Path): Boolean = pluginPath.extension == "jar" + override fun loadPlugin(pluginPath: Path, pluginDescriptor: PluginDescriptor): ClassLoader? { + val pluginClassLoader = PluginClassLoader( + this@PluginLoader, pluginDescriptor, javaClass.getClassLoader(), + ClassLoadingStrategy.APD + ) + pluginClassLoader.addFile(pluginPath.toFile()) + + return pluginClassLoader + } + + } + + private inner class LavalinkPluginClassLoader(descriptor: lavalink.server.bootstrap.PluginDescriptor) : + PluginClassLoader( + this@PluginLoader, descriptor, javaClass.classLoader, + if (descriptor.manifestVersion == lavalink.server.bootstrap.PluginDescriptor.Version.V1) ClassLoadingStrategy.APD else ClassLoadingStrategy.PDA + ) { + + init { + definePackage(descriptor.path, Manifest(), null) + } + } +} diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt index c3ed4cb73..6d14db633 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt @@ -3,49 +3,36 @@ package lavalink.server.bootstrap import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.core.io.support.PathMatchingResourcePatternResolver -import java.io.File -import java.io.FileOutputStream -import java.io.InputStream -import java.net.URL -import java.net.URLClassLoader -import java.nio.channels.Channels -import java.util.* -import java.util.jar.JarFile +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.div @SpringBootApplication -class PluginManager(val config: PluginsConfig) { +class PluginManager(val config: PluginsConfig, val loader: PluginLoader) { + val httpClient = HttpClient.newHttpClient() + companion object { private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java) } - final val pluginManifests: MutableList = mutableListOf() - - var classLoader = javaClass.classLoader - init { + loader.loadPlugins() manageDownloads() - - pluginManifests.apply { - addAll(readClasspathManifests()) - addAll(loadJars()) - } + loader.startPlugins() } + @OptIn(ExperimentalPathApi::class) private fun manageDownloads() { if (config.plugins.isEmpty()) return - val directory = File(config.pluginsDir) - directory.mkdir() - - val pluginJars = directory.listFiles()?.filter { it.extension == "jar" } - ?.flatMap { file -> - JarFile(file).use { jar -> - loadPluginManifests(jar).map { manifest -> PluginJar(manifest, file) } - } - } - ?.onEach { log.info("Found plugin '${it.manifest.name}' version ${it.manifest.version}") } - ?: return + val directory = Path(config.pluginsDir) + directory.createDirectories() val declarations = config.plugins.map { declaration -> if (declaration.dependency == null) throw RuntimeException("Illegal dependency declaration: null") @@ -59,121 +46,38 @@ class PluginManager(val config: PluginsConfig) { Declaration(fragments[0], fragments[1], fragments[2], "${repository.removeSuffix("/")}/") }.distinctBy { "${it.group}:${it.name}" } - for (declaration in declarations) { - val jars = pluginJars.filter { it.manifest.name == declaration.name }.takeIf { it.isNotEmpty() } - ?: pluginJars.filter { matchName(it, declaration.name) } + val pluginManifests = loader.plugins.map { it.descriptor as LavalinkPluginDescriptor } - var hasCurrentVersion = false + for (declaration in declarations) { + val manifest = pluginManifests.firstOrNull { it.pluginId == declaration.name } - for (jar in jars) { - if (jar.manifest.version == declaration.version) { - hasCurrentVersion = true - // Don't clean up the jar if it's a current version. - continue + if (manifest?.version != declaration.version) { + if (manifest != null) { + loader.deletePlugin(manifest.pluginId) } - // Delete versions of the plugin that aren't the same as declared version. - if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}") - log.info("Deleted ${jar.file.path} (new version: ${declaration.version})") - - } - - if (!hasCurrentVersion) { val url = declaration.url - val file = File(directory, declaration.canonicalJarName) - downloadJar(file, url) + val file = directory / declaration.canonicalJarName + if (downloadJar(file, url)) { + loader.loadPlugin(file) + } } } } - private fun downloadJar(output: File, url: String) { - log.info("Downloading $url") - - Channels.newChannel(URL(url).openStream()).use { - FileOutputStream(output).channel.transferFrom(it, 0, Long.MAX_VALUE) - } - } - - private fun readClasspathManifests(): List { - return PathMatchingResourcePatternResolver() - .getResources("classpath*:lavalink-plugins/*.properties") - .map { parsePluginManifest(it.inputStream) } - .onEach { log.info("Found plugin '${it.name}' version ${it.version}") } - } - - private fun loadJars(): List { - val directory = File(config.pluginsDir).takeIf { it.isDirectory } - ?: return emptyList() + private fun downloadJar(output: Path, url: String): Boolean { + log.info("Downloading {}", url) - val jarsToLoad = directory.listFiles()?.filter { it.isFile && it.extension == "jar" } - ?.takeIf { it.isNotEmpty() } - ?: return emptyList() + val request = HttpRequest.newBuilder(URI(url)).build() - classLoader = URLClassLoader.newInstance( - jarsToLoad.map { URL("jar:file:${it.absolutePath}!/") }.toTypedArray(), - javaClass.classLoader - ) - - return jarsToLoad.flatMap { loadJar(it, classLoader) } - } - - private fun loadJar(file: File, cl: ClassLoader): List { - val jar = JarFile(file) - val manifests = loadPluginManifests(jar) - var classCount = 0 - - jar.use { - if (manifests.isEmpty()) { - throw RuntimeException("No plugin manifest found in ${file.path}") - } - - val allowedPaths = manifests.map { manifest -> manifest.path.replace(".", "/") } - - for (entry in it.entries()) { - if (entry.isDirectory || - !entry.name.endsWith(".class") || - allowedPaths.none(entry.name::startsWith)) continue - - cl.loadClass(entry.name.dropLast(6).replace("/", ".")) - classCount++ - } + val response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(output)) + if (response.statusCode() != 200) { + log.warn("Could not download {}, got unexpected status code {}", url, response.statusCode()) + return false } - - log.info("Loaded ${file.name} ($classCount classes)") - return manifests - } - - private fun loadPluginManifests(jar: JarFile): List { - return jar.entries().asSequence() - .filter { !it.isDirectory && it.name.startsWith("lavalink-plugins/") && it.name.endsWith(".properties") } - .map { parsePluginManifest(jar.getInputStream(it)) } - .toList() - } - - private fun parsePluginManifest(stream: InputStream): PluginManifest { - val props = stream.use { - Properties().apply { load(it) } - } - - val name = props.getProperty("name") ?: throw RuntimeException("Manifest is missing 'name'") - val path = props.getProperty("path") ?: throw RuntimeException("Manifest is missing 'path'") - val version = props.getProperty("version") ?: throw RuntimeException("Manifest is missing 'version'") - return PluginManifest(name, path, version) - } - - private fun matchName(jar: PluginJar, name: String): Boolean { - // removeSuffix removes names ending with "-v", such as -v1.0.0 - // and then the subsequent removeSuffix call removes trailing "-", which - // usually precedes a version number, such as my-plugin-1.0.0. - // We strip these to produce the name of the jar's file. - val jarName = jar.file.nameWithoutExtension.takeWhile { !it.isDigit() } - .removeSuffix("-v") - .removeSuffix("-") - - return name == jarName + return response.statusCode() == 200 } - private data class PluginJar(val manifest: PluginManifest, val file: File) private data class Declaration(val group: String, val name: String, val version: String, val repository: String) { val canonicalJarName = "$name-$version.jar" val url = "$repository${group.replace(".", "/")}/$name/$version/$name-$version.jar" diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt index 3f86e7fd2..a5012203b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt @@ -1,7 +1,74 @@ package lavalink.server.bootstrap -data class PluginManifest( - val name: String, - val path: String, - val version: String -) \ No newline at end of file +import org.pf4j.DefaultPluginDescriptor +import org.pf4j.PropertiesPluginDescriptorFinder +import java.nio.file.Path +import java.util.Properties +import kotlin.io.path.inputStream +import kotlin.io.path.useDirectoryEntries +import org.pf4j.PluginDescriptor as BasePluginDescriptor + +interface PluginDescriptor : BasePluginDescriptor { + val path: String + val manifestVersion: Version + override fun getVersion(): String + override fun getPluginId(): String + override fun getPluginClass(): Nothing? = null + + enum class Version { + /** + * Legacy version. + */ + V1, + + /** + * Current up-to-date version. + */ + V2 + } +} + +class LavalinkPluginDescriptor(override val manifestVersion: PluginDescriptor.Version) : DefaultPluginDescriptor(), + PluginDescriptor { + override lateinit var path: String + override fun getPluginClass(): Nothing? = super.getPluginClass() + override fun setPluginClass(pluginClassName: String?): BasePluginDescriptor = this + public override fun setPluginVersion(version: String): DefaultPluginDescriptor = super.setPluginVersion(version) +} + +object LavalinkDescriptorFinder : PropertiesPluginDescriptorFinder() { + override fun createPluginDescriptorInstance(): LavalinkPluginDescriptor = + LavalinkPluginDescriptor(PluginDescriptor.Version.V2) + + override fun createPluginDescriptor(properties: Properties): BasePluginDescriptor { + return (super.createPluginDescriptor(properties) as LavalinkPluginDescriptor).apply { + val path = properties.getProperty("path") ?: error("'path' is not specified in plugin properties") + this.path = path + } + } +} + +object LegacyLavalinkDescriptorFinder : PropertiesPluginDescriptorFinder() { + override fun readProperties(pluginPath: Path): Properties { + val descriptorDirectory = getPropertiesPath(pluginPath, "lavalink-plugins") + val descriptor = descriptorDirectory.useDirectoryEntries("*.properties") { it.singleOrNull() } + ?: error("Found more than one descriptor in $descriptorDirectory") + + val properties = Properties() + descriptor.inputStream().use(properties::load) + + return properties + } + + override fun createPluginDescriptorInstance(): LavalinkPluginDescriptor = + LavalinkPluginDescriptor(PluginDescriptor.Version.V1) + + override fun createPluginDescriptor(properties: Properties): BasePluginDescriptor = + createPluginDescriptorInstance().apply { + path = properties.getProperty("path") ?: error("'path' is not specified in plugin properties") + pluginId = properties.getProperty("name") ?: error("'name' is not specified in plugin properties") + setPluginVersion( + properties.getProperty("version") ?: error("'version' is not specified in plugin properties") + ) + } +} diff --git a/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt index 987af865c..67ef7bd6f 100644 --- a/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt @@ -45,8 +45,9 @@ class InfoRestHandler( PlayerLibrary.VERSION, audioPlayerManager.sourceManagers.map { it.sourceName }, enabledFilers, - Plugins(pluginManager.pluginManifests.map { - Plugin(it.name, it.version) + Plugins(pluginManager.loader.plugins.map { + val descriptor = it.descriptor + Plugin(descriptor.pluginId, descriptor.version) }) ) private val version = appInfo.versionBuild From a007e2af711617ec44fd0b1c151b4d3b08834ccb Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sun, 10 Nov 2024 22:42:14 +0100 Subject: [PATCH 02/19] Add pf4j to version catalog --- LavalinkServer/build.gradle.kts | 2 +- settings.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/LavalinkServer/build.gradle.kts b/LavalinkServer/build.gradle.kts index 3e4be4824..54ee5c136 100644 --- a/LavalinkServer/build.gradle.kts +++ b/LavalinkServer/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { implementation(projects.pluginApi) { exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") } - implementation("org.pf4j", "pf4j", "3.12.1") + implementation(libs.pf4j) implementation(libs.bundles.metrics) implementation(libs.bundles.spring) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 14cbf4aa8..3e0926df6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -83,4 +83,5 @@ fun VersionCatalogBuilder.other() { plugin("maven-publish", "com.vanniktech.maven.publish").versionRef(mavenPublishPlugin) plugin("maven-publish-base", "com.vanniktech.maven.publish.base").versionRef(mavenPublishPlugin) + library("pf4j", "org.pf4j", "pf4j").version("3.12.1") } From 964cea3ecc8fe9bde806b81ffa30dfbab8972b2b Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 03:15:38 +0100 Subject: [PATCH 03/19] Update LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt Co-authored-by: devoxin <15076404+devoxin@users.noreply.github.com> --- .../lavalink/server/bootstrap/PluginCompomentClassLoader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt index 0651fbe64..d62cee84e 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt @@ -25,7 +25,7 @@ class PluginComponentClassLoader(pluginLoader: PluginLoader) : ClassLoader() { "javax", "lavalink", "kotlin", - "koltinx", + "kotlinx", "jarkarte", "git", "org/springframework", From 4739ab6766805c718ca4d1587d5b3e9f8ede9a0b Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sun, 10 Nov 2024 23:05:52 +0100 Subject: [PATCH 04/19] Make plugin manager accessible from API --- LavalinkServer/build.gradle.kts | 2 +- .../{PluginManager.kt => PluginSystemImpl.kt} | 16 +++++++++------- plugin-api/build.gradle.kts | 1 + .../dev/arbjerg/lavalink/api/PluginSystem.kt | 12 ++++++++++++ settings.gradle.kts | 1 + 5 files changed, 24 insertions(+), 8 deletions(-) rename LavalinkServer/src/main/java/lavalink/server/bootstrap/{PluginManager.kt => PluginSystemImpl.kt} (85%) create mode 100644 plugin-api/src/main/java/dev/arbjerg/lavalink/api/PluginSystem.kt diff --git a/LavalinkServer/build.gradle.kts b/LavalinkServer/build.gradle.kts index 54ee5c136..a7fa70a37 100644 --- a/LavalinkServer/build.gradle.kts +++ b/LavalinkServer/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { implementation(projects.pluginApi) { exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") } - implementation(libs.pf4j) + implementation(libs.pf4j.spring) implementation(libs.bundles.metrics) implementation(libs.bundles.spring) { diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt similarity index 85% rename from LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt rename to LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt index 6d14db633..71736730a 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt @@ -1,5 +1,7 @@ package lavalink.server.bootstrap +import dev.arbjerg.lavalink.api.PluginSystem +import org.pf4j.PluginManager import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication @@ -14,17 +16,17 @@ import kotlin.io.path.createDirectories import kotlin.io.path.div @SpringBootApplication -class PluginManager(val config: PluginsConfig, val loader: PluginLoader) { +class PluginSystemImpl(val config: PluginsConfig, override val manager: PluginManager) : PluginSystem { val httpClient = HttpClient.newHttpClient() companion object { - private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java) + private val log: Logger = LoggerFactory.getLogger(PluginSystemImpl::class.java) } init { - loader.loadPlugins() + manager.loadPlugins() manageDownloads() - loader.startPlugins() + manager.startPlugins() } @OptIn(ExperimentalPathApi::class) @@ -46,20 +48,20 @@ class PluginManager(val config: PluginsConfig, val loader: PluginLoader) { Declaration(fragments[0], fragments[1], fragments[2], "${repository.removeSuffix("/")}/") }.distinctBy { "${it.group}:${it.name}" } - val pluginManifests = loader.plugins.map { it.descriptor as LavalinkPluginDescriptor } + val pluginManifests = manager.plugins.map { it.descriptor as LavalinkPluginDescriptor } for (declaration in declarations) { val manifest = pluginManifests.firstOrNull { it.pluginId == declaration.name } if (manifest?.version != declaration.version) { if (manifest != null) { - loader.deletePlugin(manifest.pluginId) + manager.deletePlugin(manifest.pluginId) } val url = declaration.url val file = directory / declaration.canonicalJarName if (downloadJar(file, url)) { - loader.loadPlugin(file) + manager.loadPlugin(file) } } } diff --git a/plugin-api/build.gradle.kts b/plugin-api/build.gradle.kts index f0fe1556c..1395fd44c 100644 --- a/plugin-api/build.gradle.kts +++ b/plugin-api/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { api(libs.spring.boot.web) api(libs.lavaplayer) api(libs.kotlinx.serialization.json) + api(libs.pf4j) } java { diff --git a/plugin-api/src/main/java/dev/arbjerg/lavalink/api/PluginSystem.kt b/plugin-api/src/main/java/dev/arbjerg/lavalink/api/PluginSystem.kt new file mode 100644 index 000000000..fa55d9610 --- /dev/null +++ b/plugin-api/src/main/java/dev/arbjerg/lavalink/api/PluginSystem.kt @@ -0,0 +1,12 @@ +package dev.arbjerg.lavalink.api + +import org.pf4j.PluginManager + +/** + * Interface to interact with Lavalinks plugin system. + * + * @property manager the [PluginManager] instance + */ +interface PluginSystem { + val manager: PluginManager +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3e0926df6..70afc1b67 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -84,4 +84,5 @@ fun VersionCatalogBuilder.other() { plugin("maven-publish", "com.vanniktech.maven.publish").versionRef(mavenPublishPlugin) plugin("maven-publish-base", "com.vanniktech.maven.publish.base").versionRef(mavenPublishPlugin) library("pf4j", "org.pf4j", "pf4j").version("3.12.1") + library("pf4j-spring", "org.pf4j", "pf4j-spring").version("0.9.0") } From 0aa76647f02ff3c8501a7daabf92ea979a804781 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 03:13:59 +0100 Subject: [PATCH 05/19] Add implementation for new plugins --- .../src/main/java/lavalink/server/Launcher.kt | 47 +++++++++++------- .../server/bootstrap/ExtensionInjector.kt | 36 ++++++++++++++ .../server/bootstrap/PluginClassWrapper.kt | 20 +++++++- .../bootstrap/PluginCompomentClassLoader.kt | 1 + .../lavalink/server/bootstrap/PluginLoader.kt | 33 ++++++------ .../server/bootstrap/PluginManifest.kt | 12 +++-- .../server/bootstrap/PluginSystemImpl.kt | 11 ++-- .../server/config/WebConfiguration.kt | 26 ++++++++-- .../lavalink/server/info/InfoRestHandler.kt | 6 +-- .../plugin/AudioSearchRestHandler.class | Bin 0 -> 8153 bytes 10 files changed, 144 insertions(+), 48 deletions(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt create mode 100644 plugins/plugin-lavasearch-2.0.0/classes/com/github/topi314/lavasearch/plugin/AudioSearchRestHandler.class diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt index df03cb603..4bbb11f25 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt @@ -25,10 +25,13 @@ package lavalink.server import com.sedmelluq.discord.lavaplayer.tools.PlayerLibrary import lavalink.server.bootstrap.LavalinkPluginDescriptor import lavalink.server.bootstrap.PluginComponentClassLoader -import lavalink.server.bootstrap.PluginManager +import lavalink.server.bootstrap.PluginDescriptor +import lavalink.server.bootstrap.PluginSystemImpl import lavalink.server.info.AppInfo import lavalink.server.info.GitRepoState +import org.pf4j.PluginManager import org.slf4j.LoggerFactory +import org.springframework.beans.factory.getBean import org.springframework.boot.Banner import org.springframework.boot.SpringApplication import org.springframework.boot.WebApplicationType @@ -37,6 +40,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent import org.springframework.boot.context.event.ApplicationFailedEvent import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.runApplication import org.springframework.context.ApplicationListener import org.springframework.context.ConfigurableApplicationContext import org.springframework.context.annotation.ComponentScan @@ -49,10 +53,10 @@ import java.util.* @Suppress("SpringComponentScan") -@SpringBootApplication +@SpringBootApplication() @ComponentScan( value = ["\${componentScan}"], - excludeFilters = [ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [PluginManager::class])] + excludeFilters = [ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [PluginSystemImpl::class])] ) class LavalinkApplication @@ -103,11 +107,11 @@ object Launcher { val defaultC = "" var vanity = ("g . r _ _ _ _ g__ _ _\n" - + "g /\\\\ r| | __ ___ ____ _| (_)_ __ | | __g\\ \\ \\ \\\n" - + "g ( ( )r| |/ _` \\ \\ / / _` | | | '_ \\| |/ /g \\ \\ \\ \\\n" - + "g \\\\/ r| | (_| |\\ V / (_| | | | | | | < g ) ) ) )\n" - + "g ' r|_|\\__,_| \\_/ \\__,_|_|_|_| |_|_|\\_\\g / / / /\n" - + "d =========================================g/_/_/_/d") + + "g /\\\\ r| | __ ___ ____ _| (_)_ __ | | __g\\ \\ \\ \\\n" + + "g ( ( )r| |/ _` \\ \\ / / _` | | | '_ \\| |/ /g \\ \\ \\ \\\n" + + "g \\\\/ r| | (_| |\\ V / (_| | | | | | | < g ) ) ) )\n" + + "g ' r|_|\\__,_| \\_/ \\__,_|_|_|_| |_|_|\\_\\g / / / /\n" + + "d =========================================g/_/_/_/d") vanity = vanity.replace("r".toRegex(), red) vanity = vanity.replace("g".toRegex(), green) @@ -128,33 +132,40 @@ object Launcher { launchMain(parent, args) } - private fun launchPluginBootstrap() = SpringApplication(AppInfo::class.java, PluginManager::class.java).run { - setBannerMode(Banner.Mode.OFF) - webApplicationType = WebApplicationType.NONE - run() - } + private fun launchPluginBootstrap() = runApplication { + setBannerMode(Banner.Mode.OFF) + webApplicationType = WebApplicationType.NONE + } private fun launchMain(parent: ConfigurableApplicationContext, args: Array) { - val pluginManager = parent.getBean(PluginManager::class.java) + val pluginManager = parent.getBean() val properties = Properties() - properties["componentScan"] = pluginManager.loader.plugins + properties["componentScan"] = pluginManager.manager.plugins + .asSequence() + .filter { (it.descriptor as LavalinkPluginDescriptor).manifestVersion == PluginDescriptor.Version.V1 } .map { (it.descriptor as LavalinkPluginDescriptor).path } - .toMutableList().apply { add("lavalink.server") } + .toList() + "lavalink.server" SpringApplicationBuilder() + .parent(parent) .sources(LavalinkApplication::class.java) .properties(properties) .web(WebApplicationType.SERVLET) .bannerMode(Banner.Mode.OFF) - .resourceLoader(DefaultResourceLoader(PluginComponentClassLoader(pluginManager.loader))) + .resourceLoader(DefaultResourceLoader(PluginComponentClassLoader(pluginManager.manager))) .listeners( ApplicationListener { event: Any -> when (event) { is ApplicationEnvironmentPreparedEvent -> { log.info(getVersionInfo()) + } is ApplicationReadyEvent -> { + pluginManager.manager.applicationContext = event.applicationContext + pluginManager.manager.startPlugins() + pluginManager.manager.injector.injectExtensions() + log.info("Lavalink is ready to accept connections.") } @@ -163,7 +174,7 @@ object Launcher { } } } - ).parent(parent) + ) .run(*args) } } diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt new file mode 100644 index 000000000..2ffac8d9f --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -0,0 +1,36 @@ +package lavalink.server.bootstrap + +import lavalink.server.config.RequestHandlerMapping +import org.pf4j.spring.ExtensionsInjector +import org.pf4j.spring.SpringPluginManager +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.getBean +import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory +import org.springframework.web.bind.annotation.RestController +import kotlin.reflect.full.hasAnnotation + +private val log = LoggerFactory.getLogger(LavalinkExtensionInjector::class.java) + +class LavalinkExtensionInjector(pluginManager: SpringPluginManager, factory: AbstractAutowireCapableBeanFactory) : ExtensionsInjector(pluginManager, factory) { + + override fun registerExtension(extensionClass: Class<*>) { + val extensionBeanMap = springPluginManager.applicationContext.getBeansOfType(extensionClass); + if (extensionBeanMap.isEmpty()) { + val extension = springPluginManager.getExtensionFactory().create(extensionClass); + + this.beanFactory.registerSingleton(extensionClass.getName(), extension); + this.beanFactory.autowireBean(extension); + + if (extension::class.hasAnnotation()) { + log.debug("Extension {} is annotated with @RestController, forwarding registration to request mapper", extensionClass.getName()) + val mapping = springPluginManager.applicationContext.getBean("requestMappingHandlerMapping") + + mapping.registerExtension(extension) + } + + } else { + log.debug("Bean registeration aborted! Extension '{}' already existed as bean!", extensionClass.getName()); + } + + } +} \ No newline at end of file diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt index 7510de970..e23e45862 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt @@ -3,8 +3,26 @@ package lavalink.server.bootstrap import org.pf4j.Plugin import org.pf4j.PluginFactory import org.pf4j.PluginWrapper +import org.pf4j.spring.SpringPlugin +import org.pf4j.spring.SpringPluginManager +import org.springframework.context.ApplicationContext +import org.springframework.context.annotation.AnnotationConfigApplicationContext -class PluginClassWrapper(@get:JvmName("getLavalinkContext") val wrapper: PluginWrapper) : Plugin() +class PluginClassWrapper(@get:JvmName("getLavalinkContext") val wrapper: PluginWrapper) : SpringPlugin(wrapper) { + override fun createApplicationContext(): ApplicationContext { + val parent = (wrapper.pluginManager as SpringPluginManager).applicationContext + return AnnotationConfigApplicationContext().apply { + this.parent = parent + classLoader = wrapper.pluginClassLoader + (wrapper.descriptor as LavalinkPluginDescriptor).springConfigurationFiles.forEach { + log.debug("Registering configuration {} from plugin {}", it, wrapper.pluginId) + val clazz = wrapper.pluginClassLoader.loadClass(it) + register(clazz) + } + refresh() + } + } +} object LavalinkPluginFactory : PluginFactory { override fun create(pluginWrapper: PluginWrapper): Plugin = PluginClassWrapper(pluginWrapper) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt index d62cee84e..42938012c 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt @@ -66,6 +66,7 @@ class PluginComponentClassLoader(pluginLoader: PluginLoader) : ClassLoader() { ) private val cache = pluginLoader.plugins + .filter { (it.descriptor as LavalinkPluginDescriptor).manifestVersion == PluginDescriptor.Version.V1 } .associateBy { (it.descriptor as LavalinkPluginDescriptor).path.replace('.', '/') } .toMutableMap() diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 601e88e76..20e75fbf2 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -5,21 +5,31 @@ import org.pf4j.ClassLoadingStrategy import org.pf4j.CompoundPluginDescriptorFinder import org.pf4j.CompoundPluginLoader import org.pf4j.DefaultPluginLoader -import org.pf4j.DefaultPluginManager import org.pf4j.DevelopmentPluginLoader -import org.pf4j.PluginLoader as BasePluginLoader +import org.pf4j.ExtensionFactory import org.pf4j.PluginClassLoader import org.pf4j.PluginDescriptor import org.pf4j.PluginDescriptorFinder import org.pf4j.PluginFactory +import org.pf4j.spring.SpringExtensionFactory +import org.pf4j.spring.SpringPluginManager +import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory import org.springframework.stereotype.Component import java.nio.file.Path -import java.util.jar.Manifest import kotlin.io.path.Path import kotlin.io.path.extension +import org.pf4j.PluginLoader as BasePluginLoader @Component -class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : DefaultPluginManager(Path(pluginsConfig.pluginsDir)) { +class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : + SpringPluginManager(Path(pluginsConfig.pluginsDir)) { + + val injector by lazy { + LavalinkExtensionInjector( + this, + applicationContext.autowireCapableBeanFactory as AbstractAutowireCapableBeanFactory + ) + } override fun getSystemVersion(): String = appInfo.versionBuild override fun createPluginFactory(): PluginFactory = LavalinkPluginFactory @@ -28,6 +38,9 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : .add(DefaultPluginLoader(this)) .add(LegacyPluginLoader()) + // Add auto-wiring support to extensions + override fun createExtensionFactory(): ExtensionFactory = SpringExtensionFactory(this) + override fun createPluginDescriptorFinder(): PluginDescriptorFinder? { return CompoundPluginDescriptorFinder().apply { add(LegacyLavalinkDescriptorFinder) @@ -46,17 +59,5 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : return pluginClassLoader } - - } - - private inner class LavalinkPluginClassLoader(descriptor: lavalink.server.bootstrap.PluginDescriptor) : - PluginClassLoader( - this@PluginLoader, descriptor, javaClass.classLoader, - if (descriptor.manifestVersion == lavalink.server.bootstrap.PluginDescriptor.Version.V1) ClassLoadingStrategy.APD else ClassLoadingStrategy.PDA - ) { - - init { - definePackage(descriptor.path, Manifest(), null) - } } } diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt index a5012203b..a740c8753 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManifest.kt @@ -11,6 +11,7 @@ import org.pf4j.PluginDescriptor as BasePluginDescriptor interface PluginDescriptor : BasePluginDescriptor { val path: String val manifestVersion: Version + val springConfigurationFiles: List override fun getVersion(): String override fun getPluginId(): String override fun getPluginClass(): Nothing? = null @@ -31,6 +32,7 @@ interface PluginDescriptor : BasePluginDescriptor { class LavalinkPluginDescriptor(override val manifestVersion: PluginDescriptor.Version) : DefaultPluginDescriptor(), PluginDescriptor { override lateinit var path: String + override lateinit var springConfigurationFiles: List override fun getPluginClass(): Nothing? = super.getPluginClass() override fun setPluginClass(pluginClassName: String?): BasePluginDescriptor = this public override fun setPluginVersion(version: String): DefaultPluginDescriptor = super.setPluginVersion(version) @@ -41,9 +43,13 @@ object LavalinkDescriptorFinder : PropertiesPluginDescriptorFinder() { LavalinkPluginDescriptor(PluginDescriptor.Version.V2) override fun createPluginDescriptor(properties: Properties): BasePluginDescriptor { - return (super.createPluginDescriptor(properties) as LavalinkPluginDescriptor).apply { - val path = properties.getProperty("path") ?: error("'path' is not specified in plugin properties") - this.path = path + return super.createPluginDescriptor(properties).apply { + val configurations = properties.getProperty("plugin.configurations") + (this as LavalinkPluginDescriptor).springConfigurationFiles = if (configurations != null) { + configurations.split(",\\s*".toRegex()) + } else { + emptyList() + } } } } diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt index 71736730a..2afa86a54 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginSystemImpl.kt @@ -1,10 +1,12 @@ package lavalink.server.bootstrap import dev.arbjerg.lavalink.api.PluginSystem -import org.pf4j.PluginManager +import lavalink.server.info.AppInfo +import org.pf4j.spring.ExtensionsInjector import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Import import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest @@ -15,8 +17,12 @@ import kotlin.io.path.Path import kotlin.io.path.createDirectories import kotlin.io.path.div +@Import(AppInfo::class) @SpringBootApplication -class PluginSystemImpl(val config: PluginsConfig, override val manager: PluginManager) : PluginSystem { +class PluginSystemImpl( + val config: PluginsConfig, + override val manager: PluginLoader, +) : PluginSystem { val httpClient = HttpClient.newHttpClient() companion object { @@ -26,7 +32,6 @@ class PluginSystemImpl(val config: PluginsConfig, override val manager: PluginMa init { manager.loadPlugins() manageDownloads() - manager.startPlugins() } @OptIn(ExperimentalPathApi::class) diff --git a/LavalinkServer/src/main/java/lavalink/server/config/WebConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/WebConfiguration.kt index 3fca46db5..9f5b5cfd0 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/WebConfiguration.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/WebConfiguration.kt @@ -2,19 +2,26 @@ package lavalink.server.config import dev.arbjerg.lavalink.api.RestInterceptor import dev.arbjerg.lavalink.protocol.v4.json +import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary +import org.springframework.format.support.FormattingConversionService import org.springframework.http.converter.HttpMessageConverter import org.springframework.http.converter.StringHttpMessageConverter import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.web.servlet.config.annotation.EnableWebMvc +import org.springframework.web.accept.ContentNegotiationManager +import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration import org.springframework.web.servlet.config.annotation.InterceptorRegistry -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping +import org.springframework.web.servlet.resource.ResourceUrlProvider +class RequestHandlerMapping : RequestMappingHandlerMapping() { + fun registerExtension(extension: Any) = detectHandlerMethods(extension) +} @Configuration -@EnableWebMvc -class WebConfiguration(private val interceptors: List) : WebMvcConfigurer { +class WebConfiguration(private val interceptors: List) : DelegatingWebMvcConfiguration() { override fun configureMessageConverters(converters: MutableList>) { converters.add(StringHttpMessageConverter()) @@ -26,4 +33,15 @@ class WebConfiguration(private val interceptors: List) : WebMvc interceptors.forEach { registry.addInterceptor(it) } } + override fun createRequestMappingHandlerMapping(): RequestMappingHandlerMapping = RequestHandlerMapping() + + @Primary + @Bean + override fun requestMappingHandlerMapping( + contentNegotiationManager: ContentNegotiationManager, + conversionService: FormattingConversionService, + resourceUrlProvider: ResourceUrlProvider + ): RequestMappingHandlerMapping = RequestHandlerMapping().apply { + this.contentNegotiationManager = contentNegotiationManager + } } diff --git a/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt index 67ef7bd6f..1cfc181ce 100644 --- a/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt @@ -4,7 +4,7 @@ import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import com.sedmelluq.discord.lavaplayer.tools.PlayerLibrary import dev.arbjerg.lavalink.api.AudioFilterExtension import dev.arbjerg.lavalink.protocol.v4.* -import lavalink.server.bootstrap.PluginManager +import lavalink.server.bootstrap.PluginSystemImpl import lavalink.server.config.ServerConfig import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController @@ -17,7 +17,7 @@ class InfoRestHandler( appInfo: AppInfo, gitRepoState: GitRepoState, audioPlayerManager: AudioPlayerManager, - pluginManager: PluginManager, + pluginManager: PluginSystemImpl, serverConfig: ServerConfig, filterExtensions: List ) { @@ -45,7 +45,7 @@ class InfoRestHandler( PlayerLibrary.VERSION, audioPlayerManager.sourceManagers.map { it.sourceName }, enabledFilers, - Plugins(pluginManager.loader.plugins.map { + Plugins(pluginManager.manager.plugins.map { val descriptor = it.descriptor Plugin(descriptor.pluginId, descriptor.version) }) diff --git a/plugins/plugin-lavasearch-2.0.0/classes/com/github/topi314/lavasearch/plugin/AudioSearchRestHandler.class b/plugins/plugin-lavasearch-2.0.0/classes/com/github/topi314/lavasearch/plugin/AudioSearchRestHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..c4f3af68d28bac5e24c13e33c40bb801017af487 GIT binary patch literal 8153 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 literal 0 HcmV?d00001 From 43d609e65964c41fb6dc8ba69138a57f50ac32b4 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 03:16:48 +0100 Subject: [PATCH 06/19] Remove javax --- .../java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt index 42938012c..67628e509 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginCompomentClassLoader.kt @@ -45,7 +45,6 @@ class PluginComponentClassLoader(pluginLoader: PluginLoader) : ClassLoader() { "org/apache/jasper", "org/aspectj", "com/fasterxml/jackson/", - "javax", "com/hazelcast", "com/couchbase", "org/infinispan/spring", From 8302f7a6373628aba63914c388636752c6b494f9 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 19:38:13 +0100 Subject: [PATCH 07/19] Correctly autowire configuration properties --- .../java/lavalink/server/bootstrap/ExtensionInjector.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt index 2ffac8d9f..8961ee983 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -18,6 +18,12 @@ class LavalinkExtensionInjector(pluginManager: SpringPluginManager, factory: Abs if (extensionBeanMap.isEmpty()) { val extension = springPluginManager.getExtensionFactory().create(extensionClass); + if (extensionClass.kotlin.hasAnnotation()) { + val configBinder = + springPluginManager.applicationContext.getBean() + configBinder.postProcessBeforeInitialization(extension, extensionClass.getName()) + } + this.beanFactory.registerSingleton(extensionClass.getName(), extension); this.beanFactory.autowireBean(extension); From 3142d493f6d8b1a6d0b0f89fe3c0cf1dcdf9ac73 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 19:41:01 +0100 Subject: [PATCH 08/19] Correctly honor extension ordinals --- .../server/bootstrap/ExtensionInjector.kt | 60 ++++++++++++++++-- .../lavalink/server/bootstrap/PluginLoader.kt | 1 + build.gradle.kts | 2 +- .../plugin/AudioSearchRestHandler.class | Bin 8153 -> 0 bytes settings.gradle.kts | 2 +- 5 files changed, 58 insertions(+), 7 deletions(-) delete mode 100644 plugins/plugin-lavasearch-2.0.0/classes/com/github/topi314/lavasearch/plugin/AudioSearchRestHandler.class diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt index 8961ee983..7dd88b8c8 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -1,17 +1,64 @@ package lavalink.server.bootstrap import lavalink.server.config.RequestHandlerMapping +import org.pf4j.ExtensionDescriptor +import org.pf4j.ExtensionFactory +import org.pf4j.ExtensionWrapper import org.pf4j.spring.ExtensionsInjector -import org.pf4j.spring.SpringPluginManager +import org.pf4j.spring.SpringPlugin import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Configurable import org.springframework.beans.factory.getBean import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor +import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.web.bind.annotation.RestController import kotlin.reflect.full.hasAnnotation private val log = LoggerFactory.getLogger(LavalinkExtensionInjector::class.java) -class LavalinkExtensionInjector(pluginManager: SpringPluginManager, factory: AbstractAutowireCapableBeanFactory) : ExtensionsInjector(pluginManager, factory) { +private class PluginExtensionWrapper( + val pluginId: String, + descriptor: ExtensionDescriptor, + extensionFactory: ExtensionFactory +) : ExtensionWrapper(descriptor, extensionFactory) + +class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAutowireCapableBeanFactory) : + ExtensionsInjector(pluginManager, factory) { + + // We override this to use ExtensionWrappers instead, because the original does not respect ordinals + override fun injectExtensions() { + val pluginLoader = springPluginManager as PluginLoader + // add extensions from classpath (non plugin) + val extensions = + springPluginManager.plugins.flatMap { pluginLoader.extensionFinder.find(null) } + extensions.register() + + val pluginExtensions = springPluginManager.startedPlugins.flatMap { plugin -> + log.debug("Registering extensions of the plugin '{}' as beans", plugin.pluginId) + + pluginLoader.extensionFinder.find(plugin.pluginId) + .map { PluginExtensionWrapper(plugin.pluginId, it.descriptor, pluginLoader.extensionFactory) } + }.sortedBy { it.ordinal } + pluginExtensions.register() + } + + fun List>.register() = forEach { extensionWrapper -> + log.debug("Register extension '{}' as bean", extensionWrapper.descriptor.extensionClass.name) + try { + if (extensionWrapper is PluginExtensionWrapper<*> && extensionWrapper.descriptor.extensionClass.kotlin.hasAnnotation()) { + val context = + (springPluginManager.getPlugin(extensionWrapper.pluginId).plugin as SpringPlugin).applicationContext as AnnotationConfigApplicationContext + context.register(extensionWrapper.descriptor.extensionClass) + context.refresh() + } else { + registerExtension(extensionWrapper.descriptor.extensionClass) + } + } catch (e: ClassNotFoundException) { + log.error(e.message, e) + } + } override fun registerExtension(extensionClass: Class<*>) { val extensionBeanMap = springPluginManager.applicationContext.getBeansOfType(extensionClass); @@ -28,12 +75,15 @@ class LavalinkExtensionInjector(pluginManager: SpringPluginManager, factory: Abs this.beanFactory.autowireBean(extension); if (extension::class.hasAnnotation()) { - log.debug("Extension {} is annotated with @RestController, forwarding registration to request mapper", extensionClass.getName()) - val mapping = springPluginManager.applicationContext.getBean("requestMappingHandlerMapping") + log.debug( + "Extension {} is annotated with @RestController, forwarding registration to request mapper", + extensionClass.getName() + ) + val mapping = + springPluginManager.applicationContext.getBean("requestMappingHandlerMapping") mapping.registerExtension(extension) } - } else { log.debug("Bean registeration aborted! Extension '{}' already existed as bean!", extensionClass.getName()); } diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 20e75fbf2..92dc3377c 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -31,6 +31,7 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : ) } + val extensionFinder get() = super.extensionFinder override fun getSystemVersion(): String = appInfo.versionBuild override fun createPluginFactory(): PluginFactory = LavalinkPluginFactory override fun createPluginLoader(): BasePluginLoader = CompoundPluginLoader() diff --git a/build.gradle.kts b/build.gradle.kts index 1bb71a69f..ca5ce9d9c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,8 +22,8 @@ allprojects { version = versionFromTag() repositories { - mavenCentral() // main maven repo mavenLocal() // useful for developing + mavenCentral() // main maven repo maven("https://m2.dv8tion.net/releases") maven("https://maven.lavalink.dev/releases") jcenter() diff --git a/plugins/plugin-lavasearch-2.0.0/classes/com/github/topi314/lavasearch/plugin/AudioSearchRestHandler.class b/plugins/plugin-lavasearch-2.0.0/classes/com/github/topi314/lavasearch/plugin/AudioSearchRestHandler.class deleted file mode 100644 index c4f3af68d28bac5e24c13e33c40bb801017af487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8153 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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 70afc1b67..543f051a6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -83,6 +83,6 @@ fun VersionCatalogBuilder.other() { plugin("maven-publish", "com.vanniktech.maven.publish").versionRef(mavenPublishPlugin) plugin("maven-publish-base", "com.vanniktech.maven.publish.base").versionRef(mavenPublishPlugin) - library("pf4j", "org.pf4j", "pf4j").version("3.12.1") + library("pf4j", "org.pf4j", "pf4j").version("3.13.0-SNAPSHOT") library("pf4j-spring", "org.pf4j", "pf4j-spring").version("0.9.0") } From 0a144a319ab7f60e1b7e094883b2bd188f7f1c9b Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 19:41:21 +0100 Subject: [PATCH 09/19] Cleanup --- .../lavalink/server/bootstrap/ExtensionInjector.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt index 7dd88b8c8..132567d4d 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -61,9 +61,9 @@ class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAu } override fun registerExtension(extensionClass: Class<*>) { - val extensionBeanMap = springPluginManager.applicationContext.getBeansOfType(extensionClass); + val extensionBeanMap = springPluginManager.applicationContext.getBeansOfType(extensionClass) if (extensionBeanMap.isEmpty()) { - val extension = springPluginManager.getExtensionFactory().create(extensionClass); + val extension = springPluginManager.getExtensionFactory().create(extensionClass) if (extensionClass.kotlin.hasAnnotation()) { val configBinder = @@ -71,8 +71,8 @@ class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAu configBinder.postProcessBeforeInitialization(extension, extensionClass.getName()) } - this.beanFactory.registerSingleton(extensionClass.getName(), extension); - this.beanFactory.autowireBean(extension); + this.beanFactory.registerSingleton(extensionClass.getName(), extension) + this.beanFactory.autowireBean(extension) if (extension::class.hasAnnotation()) { log.debug( @@ -85,7 +85,7 @@ class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAu mapping.registerExtension(extension) } } else { - log.debug("Bean registeration aborted! Extension '{}' already existed as bean!", extensionClass.getName()); + log.debug("Bean registeration aborted! Extension '{}' already existed as bean!", extensionClass.getName()) } } From b4b53ad1326cbf6cd05bd1da2bca2606fb772e23 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 19:45:51 +0100 Subject: [PATCH 10/19] Remove outdated configurable code --- .../java/lavalink/server/bootstrap/ExtensionInjector.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt index 132567d4d..dfedcd758 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -47,14 +47,7 @@ class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAu fun List>.register() = forEach { extensionWrapper -> log.debug("Register extension '{}' as bean", extensionWrapper.descriptor.extensionClass.name) try { - if (extensionWrapper is PluginExtensionWrapper<*> && extensionWrapper.descriptor.extensionClass.kotlin.hasAnnotation()) { - val context = - (springPluginManager.getPlugin(extensionWrapper.pluginId).plugin as SpringPlugin).applicationContext as AnnotationConfigApplicationContext - context.register(extensionWrapper.descriptor.extensionClass) - context.refresh() - } else { - registerExtension(extensionWrapper.descriptor.extensionClass) - } + registerExtension(extensionWrapper.descriptor.extensionClass) } catch (e: ClassNotFoundException) { log.error(e.message, e) } From 28774dcae8cbe8e4814fe682835223b9c1ba4c3e Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 11 Nov 2024 20:11:09 +0100 Subject: [PATCH 11/19] Remove unused import --- .../main/java/lavalink/server/bootstrap/ExtensionInjector.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt index dfedcd758..6a63408c9 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -5,14 +5,11 @@ import org.pf4j.ExtensionDescriptor import org.pf4j.ExtensionFactory import org.pf4j.ExtensionWrapper import org.pf4j.spring.ExtensionsInjector -import org.pf4j.spring.SpringPlugin import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Configurable import org.springframework.beans.factory.getBean import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor -import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.web.bind.annotation.RestController import kotlin.reflect.full.hasAnnotation From 6bd1acbd26ac2a0350fbf042a42c01406182f939 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Nov 2024 01:33:34 +0100 Subject: [PATCH 12/19] Register lavalink internal extensions --- .../src/main/java/lavalink/server/Launcher.kt | 9 ++-- .../server/bootstrap/ExtensionInjector.kt | 24 +++++----- .../server/config/AudioPlayerConfiguration.kt | 2 + .../lavalink/server/config/WebsocketConfig.kt | 48 +++++++++++++++++-- .../lavalink/server/info/InfoRestHandler.kt | 3 ++ .../server/io/HandshakeInterceptorImpl.kt | 2 + .../lavalink/server/io/SessionRestHandler.kt | 2 + .../java/lavalink/server/io/SocketServer.kt | 12 +++-- .../java/lavalink/server/io/StatsCollector.kt | 2 + .../server/player/AudioLoaderRestHandler.kt | 2 + .../server/player/PlayerRestHandler.kt | 2 + .../main/resources/META-INF/extensions.idx | 9 ++++ 12 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 LavalinkServer/src/main/resources/META-INF/extensions.idx diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt index 4bbb11f25..021cdd43a 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt @@ -29,11 +29,10 @@ import lavalink.server.bootstrap.PluginDescriptor import lavalink.server.bootstrap.PluginSystemImpl import lavalink.server.info.AppInfo import lavalink.server.info.GitRepoState -import org.pf4j.PluginManager +import org.pf4j.Extension import org.slf4j.LoggerFactory import org.springframework.beans.factory.getBean import org.springframework.boot.Banner -import org.springframework.boot.SpringApplication import org.springframework.boot.WebApplicationType import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.builder.SpringApplicationBuilder @@ -56,7 +55,11 @@ import java.util.* @SpringBootApplication() @ComponentScan( value = ["\${componentScan}"], - excludeFilters = [ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [PluginSystemImpl::class])] + excludeFilters = [ + ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [PluginSystemImpl::class]), + // These are registered manually + ComponentScan.Filter(type = FilterType.ANNOTATION, classes = [Extension::class]) + ] ) class LavalinkApplication diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt index 6a63408c9..5345ea8fa 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/ExtensionInjector.kt @@ -1,8 +1,6 @@ package lavalink.server.bootstrap import lavalink.server.config.RequestHandlerMapping -import org.pf4j.ExtensionDescriptor -import org.pf4j.ExtensionFactory import org.pf4j.ExtensionWrapper import org.pf4j.spring.ExtensionsInjector import org.slf4j.LoggerFactory @@ -10,17 +8,13 @@ import org.springframework.beans.factory.getBean import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor +import org.springframework.context.annotation.AnnotationConfigRegistry +import org.springframework.context.annotation.Configuration import org.springframework.web.bind.annotation.RestController import kotlin.reflect.full.hasAnnotation private val log = LoggerFactory.getLogger(LavalinkExtensionInjector::class.java) -private class PluginExtensionWrapper( - val pluginId: String, - descriptor: ExtensionDescriptor, - extensionFactory: ExtensionFactory -) : ExtensionWrapper(descriptor, extensionFactory) - class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAutowireCapableBeanFactory) : ExtensionsInjector(pluginManager, factory) { @@ -28,17 +22,17 @@ class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAu override fun injectExtensions() { val pluginLoader = springPluginManager as PluginLoader // add extensions from classpath (non plugin) - val extensions = + val internalExtensions = springPluginManager.plugins.flatMap { pluginLoader.extensionFinder.find(null) } - extensions.register() val pluginExtensions = springPluginManager.startedPlugins.flatMap { plugin -> log.debug("Registering extensions of the plugin '{}' as beans", plugin.pluginId) pluginLoader.extensionFinder.find(plugin.pluginId) - .map { PluginExtensionWrapper(plugin.pluginId, it.descriptor, pluginLoader.extensionFactory) } - }.sortedBy { it.ordinal } - pluginExtensions.register() + } + (internalExtensions + pluginExtensions) + .sortedBy { it.ordinal } + .register() } fun List>.register() = forEach { extensionWrapper -> @@ -55,6 +49,10 @@ class LavalinkExtensionInjector(pluginManager: PluginLoader, factory: AbstractAu if (extensionBeanMap.isEmpty()) { val extension = springPluginManager.getExtensionFactory().create(extensionClass) + if (extensionClass.kotlin.hasAnnotation()) { + (springPluginManager.applicationContext as AnnotationConfigRegistry).register(extensionClass) + } + if (extensionClass.kotlin.hasAnnotation()) { val configBinder = springPluginManager.applicationContext.getBean() diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt index 96571e689..afe402575 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt @@ -28,6 +28,7 @@ import org.apache.http.impl.client.BasicCredentialsProvider import org.slf4j.LoggerFactory import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Lazy import java.net.InetAddress import java.util.* import java.util.concurrent.TimeUnit @@ -41,6 +42,7 @@ class AudioPlayerConfiguration { private val log = LoggerFactory.getLogger(AudioPlayerConfiguration::class.java) + @Lazy // Only create an AudioPlayerManager after all config contributors were loaded @Bean fun audioPlayerManagerSupplier( sources: AudioSourcesConfig, diff --git a/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt b/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt index 16c57eb61..719a160a3 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt @@ -3,31 +3,69 @@ package lavalink.server.config import lavalink.server.io.HandshakeInterceptorImpl import lavalink.server.io.SocketServer import org.slf4j.LoggerFactory +import org.springframework.beans.factory.getBean +import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Configuration +import org.springframework.http.server.ServerHttpRequest +import org.springframework.http.server.ServerHttpResponse import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController +import org.springframework.web.socket.CloseStatus +import org.springframework.web.socket.TextMessage +import org.springframework.web.socket.WebSocketHandler +import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.config.annotation.EnableWebSocket import org.springframework.web.socket.config.annotation.WebSocketConfigurer import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry +import org.springframework.web.socket.handler.TextWebSocketHandler +import org.springframework.web.socket.server.HandshakeInterceptor +import java.lang.Exception @Configuration @EnableWebSocket @RestController -class WebsocketConfig( - private val server: SocketServer, - private val handshakeInterceptor: HandshakeInterceptorImpl, -) : WebSocketConfigurer { +class WebsocketConfig(private val context: ApplicationContext) : WebSocketConfigurer { companion object { private val log = LoggerFactory.getLogger(WebsocketConfig::class.java) } override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { - registry.addHandler(server, "/v4/websocket").addInterceptors(handshakeInterceptor) + val proxy = WebSocketProxy() + registry.addHandler(proxy, "/v4/websocket").addInterceptors(proxy) } @GetMapping("/", "/v3/websocket") fun oldWebsocket() { log.warn("This is the old Lavalink websocket endpoint. Please use /v4/websocket instead. If you are using a client library, please update it to a Lavalink v4 compatible version or use Lavalink v3 instead.") } + + // This is required to lazily evaluate registerWebSocketHandlers() + inner class WebSocketProxy : TextWebSocketHandler(), HandshakeInterceptor { + private val socket by lazy { context.getBean() } + private val handshaker by lazy { context.getBean() } + + override fun beforeHandshake( + request: ServerHttpRequest, + response: ServerHttpResponse, + wsHandler: WebSocketHandler, + attributes: Map + ): Boolean = handshaker.beforeHandshake(request, response, wsHandler, attributes) + + override fun afterHandshake( + request: ServerHttpRequest, + response: ServerHttpResponse, + wsHandler: WebSocketHandler, + exception: Exception? + ) = handshaker.afterHandshake(request, response, wsHandler, exception) + + override fun handleTextMessage(session: WebSocketSession, message: TextMessage) = + socket.handleMessage(session, message) + + override fun afterConnectionEstablished(session: WebSocketSession) = + socket.afterConnectionEstablished(session) + + override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) = + socket.afterConnectionClosed(session, status) + } } diff --git a/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt index 1cfc181ce..72e1bceca 100644 --- a/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.kt @@ -6,12 +6,15 @@ import dev.arbjerg.lavalink.api.AudioFilterExtension import dev.arbjerg.lavalink.protocol.v4.* import lavalink.server.bootstrap.PluginSystemImpl import lavalink.server.config.ServerConfig +import org.pf4j.Extension import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController /** * Created by napster on 08.03.19. */ + +@Extension(ordinal = Int.MAX_VALUE) // Register this last, as we need to load plugin configuration contributors first @RestController class InfoRestHandler( appInfo: AppInfo, diff --git a/LavalinkServer/src/main/java/lavalink/server/io/HandshakeInterceptorImpl.kt b/LavalinkServer/src/main/java/lavalink/server/io/HandshakeInterceptorImpl.kt index 34a791516..9a4283e65 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/HandshakeInterceptorImpl.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/HandshakeInterceptorImpl.kt @@ -1,6 +1,7 @@ package lavalink.server.io import lavalink.server.config.ServerConfig +import org.pf4j.Extension import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpStatus @@ -10,6 +11,7 @@ import org.springframework.stereotype.Controller import org.springframework.web.socket.WebSocketHandler import org.springframework.web.socket.server.HandshakeInterceptor +@Extension(ordinal = Int.MAX_VALUE) // Register this last, as we need to load plugin configuration contributors first @Controller class HandshakeInterceptorImpl @Autowired constructor(private val serverConfig: ServerConfig, private val socketServer: SocketServer) : HandshakeInterceptor { diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SessionRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/io/SessionRestHandler.kt index 6bffb2479..45c3b616a 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SessionRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/SessionRestHandler.kt @@ -4,12 +4,14 @@ import dev.arbjerg.lavalink.protocol.v4.Session import dev.arbjerg.lavalink.protocol.v4.SessionUpdate import dev.arbjerg.lavalink.protocol.v4.ifPresent import lavalink.server.util.socketContext +import org.pf4j.Extension import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController +@Extension(ordinal = Int.MAX_VALUE) // Register this last, as we need to load plugin configuration contributors first @RestController class SessionRestHandler(private val socketServer: SocketServer) { diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt index 4f2a72743..6e0ff15ea 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt @@ -32,6 +32,7 @@ import lavalink.server.config.ServerConfig import lavalink.server.player.LavalinkPlayer import moe.kyokobot.koe.Koe import moe.kyokobot.koe.KoeOptions +import org.pf4j.Extension import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import org.springframework.web.socket.CloseStatus @@ -40,6 +41,9 @@ import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.handler.TextWebSocketHandler import java.util.concurrent.ConcurrentHashMap +// Register this second-to-last, as we need to load plugin configuration contributors first +// Move it up by 1 as other things depend on this +@Extension(ordinal = Int.MAX_VALUE - 1) @Service final class SocketServer( private val serverConfig: ServerConfig, @@ -68,8 +72,8 @@ final class SocketServer( val connection = socketContext.getMediaConnection(player).gatewayConnection socketContext.sendMessage( - Message.Serializer, - Message.PlayerUpdateEvent( + Message.Serializer, + Message.PlayerUpdateEvent( PlayerState( System.currentTimeMillis(), player.audioPlayer.playingTrack?.position ?: 0, @@ -150,7 +154,7 @@ final class SocketServer( resumableSessions.remove(context.sessionId)?.let { removed -> log.warn( "Shutdown resumable session with id ${removed.sessionId} because it has the same id as a " + - "newly disconnected resumable session." + "newly disconnected resumable session." ) removed.shutdown() } @@ -159,7 +163,7 @@ final class SocketServer( context.pause() log.info( "Connection closed from ${session.remoteAddress} with status $status -- " + - "Session can be resumed within the next ${context.resumeTimeout} seconds with id ${context.sessionId}", + "Session can be resumed within the next ${context.resumeTimeout} seconds with id ${context.sessionId}", ) return } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt b/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt index 4623851ff..e420c8f59 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt @@ -24,12 +24,14 @@ package lavalink.server.io import dev.arbjerg.lavalink.protocol.v4.* import lavalink.server.Launcher import lavalink.server.player.AudioLossCounter +import org.pf4j.Extension import org.slf4j.LoggerFactory import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import oshi.SystemInfo import kotlin.Exception +@Extension(ordinal = Int.MAX_VALUE) // Register this last, as we need to load plugin configuration contributors first @RestController class StatsCollector(val socketServer: SocketServer) { companion object { diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt index 4fdadb43c..0e7b3dcc2 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt @@ -32,12 +32,14 @@ import dev.arbjerg.lavalink.protocol.v4.Track import dev.arbjerg.lavalink.protocol.v4.Tracks import jakarta.servlet.http.HttpServletRequest import lavalink.server.util.* +import org.pf4j.Extension import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.server.ResponseStatusException +@Extension(ordinal = Int.MAX_VALUE) // Register this last, as we need to load plugin configuration contributors first @RestController class AudioLoaderRestHandler( private val audioPlayerManager: AudioPlayerManager, diff --git a/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt index 801614dfe..873672382 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt @@ -11,12 +11,14 @@ import lavalink.server.io.SocketServer import lavalink.server.player.filters.FilterChain import lavalink.server.util.* import moe.kyokobot.koe.VoiceServerInfo +import org.pf4j.Extension import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.server.ResponseStatusException +@Extension(ordinal = Int.MAX_VALUE) // Register this last, as we need to load plugin configuration contributors first @RestController class PlayerRestHandler( private val socketServer: SocketServer, diff --git a/LavalinkServer/src/main/resources/META-INF/extensions.idx b/LavalinkServer/src/main/resources/META-INF/extensions.idx new file mode 100644 index 000000000..6eeb1b960 --- /dev/null +++ b/LavalinkServer/src/main/resources/META-INF/extensions.idx @@ -0,0 +1,9 @@ +lavalink.server.io.SessionRestHandler +lavalink.server.io.SocketServer +lavalink.server.io.SessionRestHandler +lavalink.server.io.HandshakeInterceptorImpl +lavalink.server.info.InfoRestHandler +lavalink.server.config.WebsocketConfig +lavalink.server.player.AudioLoaderRestHandler +lavalink.server.player.PlayerRestHandler +lavalink.server.io.StatsCollector \ No newline at end of file From 6a83e3a53f629e36ce122d90f7bedd8cf858337b Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Nov 2024 15:28:56 +0100 Subject: [PATCH 13/19] Add VersionManager --- .../bootstrap/FlexibleVersionManager.kt | 32 ++++++++++++ .../lavalink/server/bootstrap/PluginLoader.kt | 2 + .../server/bootrap/VersionManagerTest.kt | 49 +++++++++++++++++++ .../java/lavalink/server/config/TestConfig.kt | 6 ++- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/bootstrap/FlexibleVersionManager.kt create mode 100644 LavalinkServer/src/test/java/lavalink/server/bootrap/VersionManagerTest.kt diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/FlexibleVersionManager.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/FlexibleVersionManager.kt new file mode 100644 index 000000000..bdd20109a --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/FlexibleVersionManager.kt @@ -0,0 +1,32 @@ +package lavalink.server.bootstrap + +import org.pf4j.VersionManager +import com.github.zafarkhaja.semver.Version + +private val commitHashRegex = Regex("^[0-9a-fA-F]{7,40}$") + +/** + * Implementation of [VersionManager] which also accepts commit hashes as versions. + */ +class FlexibleVersionManager : VersionManager { + override fun checkVersionConstraint(version: String, constraint: String): Boolean { + if (constraint == "*") return true + return if(Version.isValid(version)) { + Version.parse(version).satisfies(constraint) + } else { + return version.matches(commitHashRegex) + } + } + + override fun compareVersions(v1: String, v2: String): Int { + if (v1.matches(commitHashRegex)) { + // Commit hashes cannot be compared + if (v2.matches(commitHashRegex)) return 0 + // Commit hash should always win + return 1 + } + // Commit hash should always win + if (v2.matches(commitHashRegex)) return -1 + return Version.parse(v1).compareTo(Version.parse(v2)) + } +} \ No newline at end of file diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 92dc3377c..06fb70862 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -11,6 +11,7 @@ import org.pf4j.PluginClassLoader import org.pf4j.PluginDescriptor import org.pf4j.PluginDescriptorFinder import org.pf4j.PluginFactory +import org.pf4j.VersionManager import org.pf4j.spring.SpringExtensionFactory import org.pf4j.spring.SpringPluginManager import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory @@ -32,6 +33,7 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : } val extensionFinder get() = super.extensionFinder + override fun createVersionManager(): VersionManager = FlexibleVersionManager() override fun getSystemVersion(): String = appInfo.versionBuild override fun createPluginFactory(): PluginFactory = LavalinkPluginFactory override fun createPluginLoader(): BasePluginLoader = CompoundPluginLoader() diff --git a/LavalinkServer/src/test/java/lavalink/server/bootrap/VersionManagerTest.kt b/LavalinkServer/src/test/java/lavalink/server/bootrap/VersionManagerTest.kt new file mode 100644 index 000000000..b8bfbf448 --- /dev/null +++ b/LavalinkServer/src/test/java/lavalink/server/bootrap/VersionManagerTest.kt @@ -0,0 +1,49 @@ +package lavalink.server.bootrap + +import lavalink.server.bootstrap.FlexibleVersionManager +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class VersionManagerTest { + private val versionManager = FlexibleVersionManager() + + @Test + fun `test two commit hashes are compared as equal`() { + val hash1 = "0d7decb" + val hash2 = "184367d" + + Assertions.assertEquals(0, versionManager.compareVersions(hash1, hash2)) + } + + @Test + fun `test commit hash is always higher`() { + val hash1 = "0d7decb" + val hash2 = "2.0.0" + + Assertions.assertEquals(1, versionManager.compareVersions(hash1, hash2)) + } + + @Test + fun `test semver is always lower`() { + val hash1 = "0d7decb" + val hash2 = "2.0.0" + + Assertions.assertEquals(-1, versionManager.compareVersions(hash2, hash1)) + } + + @Test + fun `test hash meets constraint`() { + val constraint = ">= 2.0.0 && < 3.0.0" + val hash = "0d7decb" + + Assertions.assertTrue(versionManager.checkVersionConstraint(hash, constraint)) + } + + @Test + fun `test wilddard constraint`() { + val constraint = "*" + val hash = "0d7decb" + + Assertions.assertTrue(versionManager.checkVersionConstraint(hash, constraint)) + } +} diff --git a/LavalinkServer/src/test/java/lavalink/server/config/TestConfig.kt b/LavalinkServer/src/test/java/lavalink/server/config/TestConfig.kt index 64d529282..f5a39db3a 100644 --- a/LavalinkServer/src/test/java/lavalink/server/config/TestConfig.kt +++ b/LavalinkServer/src/test/java/lavalink/server/config/TestConfig.kt @@ -1,7 +1,9 @@ package lavalink.server.config -import lavalink.server.bootstrap.PluginManager +import lavalink.server.bootstrap.PluginLoader +import lavalink.server.bootstrap.PluginSystemImpl import lavalink.server.bootstrap.PluginsConfig +import lavalink.server.info.AppInfo import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean @@ -9,6 +11,6 @@ import org.springframework.context.annotation.Bean class TestConfig { @Bean - fun pluginManager() = PluginManager(PluginsConfig()) + fun pluginManager() = PluginSystemImpl(PluginsConfig(), PluginLoader(PluginsConfig(), AppInfo())) } \ No newline at end of file From 91e65de70699bc8b84bcab7801ffde0df138357e Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Nov 2024 15:32:54 +0100 Subject: [PATCH 14/19] Add clarification for ClassLoadingStrategy --- .../src/main/java/lavalink/server/bootstrap/PluginLoader.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 06fb70862..a774f5dcb 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -33,6 +33,7 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : } val extensionFinder get() = super.extensionFinder + override fun createVersionManager(): VersionManager = FlexibleVersionManager() override fun getSystemVersion(): String = appInfo.versionBuild override fun createPluginFactory(): PluginFactory = LavalinkPluginFactory @@ -56,6 +57,8 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : override fun loadPlugin(pluginPath: Path, pluginDescriptor: PluginDescriptor): ClassLoader? { val pluginClassLoader = PluginClassLoader( this@PluginLoader, pluginDescriptor, javaClass.getClassLoader(), + // This is required because the old distribution format can contain classes that the server contains + // as well, so we need the server classes to take priority ClassLoadingStrategy.APD ) pluginClassLoader.addFile(pluginPath.toFile()) From 1f8c46ef477bfc10a3a655e07206bd5adb6f5ed3 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Nov 2024 15:35:35 +0100 Subject: [PATCH 15/19] Add support for development mode --- .../src/main/java/lavalink/server/bootstrap/PluginLoader.kt | 6 +++++- .../main/java/lavalink/server/bootstrap/PluginsConfig.kt | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index a774f5dcb..3c0fd25a7 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -11,6 +11,7 @@ import org.pf4j.PluginClassLoader import org.pf4j.PluginDescriptor import org.pf4j.PluginDescriptorFinder import org.pf4j.PluginFactory +import org.pf4j.RuntimeMode import org.pf4j.VersionManager import org.pf4j.spring.SpringExtensionFactory import org.pf4j.spring.SpringPluginManager @@ -22,7 +23,7 @@ import kotlin.io.path.extension import org.pf4j.PluginLoader as BasePluginLoader @Component -class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : +class PluginLoader(private val pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : SpringPluginManager(Path(pluginsConfig.pluginsDir)) { val injector by lazy { @@ -34,6 +35,9 @@ class PluginLoader(pluginsConfig: PluginsConfig, private val appInfo: AppInfo) : val extensionFinder get() = super.extensionFinder + override fun getRuntimeMode(): RuntimeMode = + if (pluginsConfig.developmentMode) RuntimeMode.DEVELOPMENT else RuntimeMode.DEPLOYMENT + override fun createVersionManager(): VersionManager = FlexibleVersionManager() override fun getSystemVersion(): String = appInfo.versionBuild override fun createPluginFactory(): PluginFactory = LavalinkPluginFactory diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginsConfig.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginsConfig.kt index 5f6bd0421..7d5731a24 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginsConfig.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginsConfig.kt @@ -8,6 +8,7 @@ import org.springframework.stereotype.Component class PluginsConfig { var plugins: List = emptyList() var pluginsDir: String = "./plugins" + var developmentMode: Boolean = false var defaultPluginRepository: String = "https://maven.lavalink.dev/releases" var defaultPluginSnapshotRepository: String = "https://maven.lavalink.dev/snapshots" } From ece06b4036af342a10fc5f17fcfd5120b2313be4 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Nov 2024 16:43:01 +0100 Subject: [PATCH 16/19] Add DevelopmentPluginLoader --- .../lavalink/server/bootstrap/DevelopmentPluginLoader.kt | 9 +++++++++ .../main/java/lavalink/server/bootstrap/PluginLoader.kt | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/bootstrap/DevelopmentPluginLoader.kt diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/DevelopmentPluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/DevelopmentPluginLoader.kt new file mode 100644 index 000000000..aae8fc773 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/DevelopmentPluginLoader.kt @@ -0,0 +1,9 @@ +package lavalink.server.bootstrap + +import org.pf4j.BasePluginLoader +import org.pf4j.DevelopmentPluginClasspath +import org.pf4j.PluginManager + +private val developmentClasspath = DevelopmentPluginClasspath.GRADLE.addJarsDirectories("build/dependencies") + +class DevelopmentPluginLoader(pluginManager: PluginManager) : BasePluginLoader(pluginManager, developmentClasspath) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 3c0fd25a7..9bd5c5746 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -5,7 +5,6 @@ import org.pf4j.ClassLoadingStrategy import org.pf4j.CompoundPluginDescriptorFinder import org.pf4j.CompoundPluginLoader import org.pf4j.DefaultPluginLoader -import org.pf4j.DevelopmentPluginLoader import org.pf4j.ExtensionFactory import org.pf4j.PluginClassLoader import org.pf4j.PluginDescriptor From 97a8421db57f8ba72419a1a25bec72debc58242c Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 14 Nov 2024 02:06:59 +0100 Subject: [PATCH 17/19] Enable extension dependency checking --- LavalinkServer/build.gradle.kts | 2 +- .../lavalink/server/bootstrap/PluginLoader.kt | 15 +++++---------- settings.gradle.kts | 1 + 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/LavalinkServer/build.gradle.kts b/LavalinkServer/build.gradle.kts index a7fa70a37..3a319436c 100644 --- a/LavalinkServer/build.gradle.kts +++ b/LavalinkServer/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") } implementation(libs.pf4j.spring) + implementation(libs.asm) implementation(libs.bundles.metrics) implementation(libs.bundles.spring) { @@ -123,7 +124,6 @@ tasks { archiveClassifier = "musl" } - withType { archiveFileName = "Lavalink.jar" diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 9bd5c5746..117803411 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -1,17 +1,8 @@ package lavalink.server.bootstrap import lavalink.server.info.AppInfo -import org.pf4j.ClassLoadingStrategy -import org.pf4j.CompoundPluginDescriptorFinder -import org.pf4j.CompoundPluginLoader -import org.pf4j.DefaultPluginLoader -import org.pf4j.ExtensionFactory -import org.pf4j.PluginClassLoader +import org.pf4j.* import org.pf4j.PluginDescriptor -import org.pf4j.PluginDescriptorFinder -import org.pf4j.PluginFactory -import org.pf4j.RuntimeMode -import org.pf4j.VersionManager import org.pf4j.spring.SpringExtensionFactory import org.pf4j.spring.SpringPluginManager import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory @@ -45,6 +36,10 @@ class PluginLoader(private val pluginsConfig: PluginsConfig, private val appInfo .add(DefaultPluginLoader(this)) .add(LegacyPluginLoader()) + override fun createExtensionFinder(): ExtensionFinder = LegacyExtensionFinder(this).apply { + isCheckForExtensionDependencies = true + } + // Add auto-wiring support to extensions override fun createExtensionFactory(): ExtensionFactory = SpringExtensionFactory(this) diff --git a/settings.gradle.kts b/settings.gradle.kts index 543f051a6..6ff681c66 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -85,4 +85,5 @@ fun VersionCatalogBuilder.other() { plugin("maven-publish-base", "com.vanniktech.maven.publish.base").versionRef(mavenPublishPlugin) library("pf4j", "org.pf4j", "pf4j").version("3.13.0-SNAPSHOT") library("pf4j-spring", "org.pf4j", "pf4j-spring").version("0.9.0") + library("asm", "org.ow2.asm", "asm").version("9.7.1") } From 9c1a22c68a4a75a8e338964457168d18d738bb34 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 14 Nov 2024 02:09:57 +0100 Subject: [PATCH 18/19] Load configurations from configurations.idx --- .../server/bootstrap/PluginClassWrapper.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt index e23e45862..0681bf334 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginClassWrapper.kt @@ -5,20 +5,25 @@ import org.pf4j.PluginFactory import org.pf4j.PluginWrapper import org.pf4j.spring.SpringPlugin import org.pf4j.spring.SpringPluginManager +import org.pf4j.util.FileUtils import org.springframework.context.ApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext +import kotlin.io.path.useLines -class PluginClassWrapper(@get:JvmName("getLavalinkContext") val wrapper: PluginWrapper) : SpringPlugin(wrapper) { +class PluginClassWrapper(val context: PluginWrapper) : SpringPlugin(context) { override fun createApplicationContext(): ApplicationContext { - val parent = (wrapper.pluginManager as SpringPluginManager).applicationContext + val parent = (context.pluginManager as SpringPluginManager).applicationContext return AnnotationConfigApplicationContext().apply { this.parent = parent - classLoader = wrapper.pluginClassLoader - (wrapper.descriptor as LavalinkPluginDescriptor).springConfigurationFiles.forEach { - log.debug("Registering configuration {} from plugin {}", it, wrapper.pluginId) - val clazz = wrapper.pluginClassLoader.loadClass(it) - register(clazz) - } + classLoader = context.pluginClassLoader + FileUtils.getPath(context.pluginPath, "META-INF", "configurations.idx") + .useLines { + it.forEach { className -> + log.debug("Registering configuration {} from plugin {}", className, context.pluginId) + val clazz = context.pluginClassLoader.loadClass(className) + register(clazz) + } + } refresh() } } From 41f9c1fd616f488451abf4fa90c3fddfc69375bf Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 14 Nov 2024 02:10:24 +0100 Subject: [PATCH 19/19] Cleanup --- .../src/main/java/lavalink/server/bootstrap/PluginLoader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt index 117803411..c0b1dfb15 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginLoader.kt @@ -52,7 +52,7 @@ class PluginLoader(private val pluginsConfig: PluginsConfig, private val appInfo private inner class LegacyPluginLoader : BasePluginLoader { override fun isApplicable(pluginPath: Path): Boolean = pluginPath.extension == "jar" - override fun loadPlugin(pluginPath: Path, pluginDescriptor: PluginDescriptor): ClassLoader? { + override fun loadPlugin(pluginPath: Path, pluginDescriptor: PluginDescriptor): ClassLoader { val pluginClassLoader = PluginClassLoader( this@PluginLoader, pluginDescriptor, javaClass.getClassLoader(), // This is required because the old distribution format can contain classes that the server contains