From a1e0860a0b6b1d1c89a1e2202caf285f7fdd1bf7 Mon Sep 17 00:00:00 2001 From: junhyung Date: Fri, 2 Feb 2024 07:53:16 +0900 Subject: [PATCH] Add metadata reader to allow Spring Boot can read another plugin jar's classpath resources --- .../SpringBukkitMetadataReaderFactoryBean.kt | 36 ++++++++++++ .../springbukkit/core/SpringBukkitPlugin.kt | 36 ++++++++++-- .../core/SpringBukkitResourceLoader.kt | 57 +++++++++++++++++++ .../support/folia/FoliaBukkitTaskScheduler.kt | 7 ++- 4 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitMetadataReaderFactoryBean.kt create mode 100644 spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitResourceLoader.kt diff --git a/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitMetadataReaderFactoryBean.kt b/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitMetadataReaderFactoryBean.kt new file mode 100644 index 0000000..dc82ce3 --- /dev/null +++ b/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitMetadataReaderFactoryBean.kt @@ -0,0 +1,36 @@ +package kr.summitsystems.springbukkit.core + +import org.springframework.beans.factory.FactoryBean +import org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory +import org.springframework.context.ApplicationListener +import org.springframework.context.ResourceLoaderAware +import org.springframework.context.event.ContextRefreshedEvent +import org.springframework.core.io.ResourceLoader +import org.springframework.core.type.classreading.CachingMetadataReaderFactory + +/** + * https://github.com/spring-projects/spring-boot/pull/39321 + */ +internal class SpringBukkitMetadataReaderFactoryBean : FactoryBean, ApplicationListener, ResourceLoaderAware { + private var metadataReaderFactory: ConcurrentReferenceCachingMetadataReaderFactory? = null + + override fun setResourceLoader(resourceLoader: ResourceLoader) { + metadataReaderFactory = ConcurrentReferenceCachingMetadataReaderFactory(resourceLoader) + } + + override fun getObject(): ConcurrentReferenceCachingMetadataReaderFactory? { + return metadataReaderFactory + } + + override fun getObjectType(): Class<*> { + return CachingMetadataReaderFactory::class.java + } + + override fun isSingleton(): Boolean { + return true + } + + override fun onApplicationEvent(event: ContextRefreshedEvent) { + metadataReaderFactory?.clearCache() + } +} \ No newline at end of file diff --git a/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitPlugin.kt b/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitPlugin.kt index 0b84893..73cb167 100644 --- a/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitPlugin.kt +++ b/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitPlugin.kt @@ -1,20 +1,25 @@ package kr.summitsystems.springbukkit.core +import org.bukkit.plugin.Plugin import org.bukkit.plugin.java.JavaPlugin +import org.springframework.beans.factory.config.BeanDefinition +import org.springframework.beans.factory.support.BeanDefinitionBuilder +import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.boot.Banner import org.springframework.boot.WebApplicationType import org.springframework.boot.builder.SpringApplicationBuilder import org.springframework.boot.env.YamlPropertySourceLoader import org.springframework.context.ApplicationContextInitializer import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.support.GenericApplicationContext import org.springframework.core.annotation.AnnotationUtils import org.springframework.core.env.PropertiesPropertySource -import org.springframework.core.io.DefaultResourceLoader import org.springframework.core.io.FileSystemResource import java.io.File import java.util.* -abstract class SpringBukkitPlugin : JavaPlugin(), ApplicationContextInitializer, DisposableContainer, Disposable { +abstract class SpringBukkitPlugin : JavaPlugin(), ApplicationContextInitializer, + DisposableContainer, Disposable { private var applicationContext: ConfigurableApplicationContext? = null private val disposables: MutableList = mutableListOf() @@ -55,7 +60,10 @@ abstract class SpringBukkitPlugin : JavaPlugin(), ApplicationContextInitializer< private fun runApplication(applicationSource: Class<*>): ConfigurableApplicationContext { Thread.currentThread().contextClassLoader = this.classLoader - return SpringApplicationBuilder(DefaultResourceLoader(this.classLoader), applicationSource) + return SpringApplicationBuilder( + SpringBukkitResourceLoader(this, this.classLoader, server.pluginManager), + applicationSource + ) .web(WebApplicationType.NONE) .bannerMode(Banner.Mode.OFF) .logStartupInfo(false) @@ -63,11 +71,12 @@ abstract class SpringBukkitPlugin : JavaPlugin(), ApplicationContextInitializer< .run() } - override fun initialize(applicationContext: ConfigurableApplicationContext) { + override fun initialize(applicationContext: GenericApplicationContext) { applicationContext.setClassLoader(this.classLoader) registerYamlPropertySource(applicationContext, "application.yml") registerYamlPropertySource(applicationContext, "config.yml") registerPropertiesPropertySource(applicationContext, "application.properties") + registerMetadataReaderFactory(applicationContext) registerPluginBean(applicationContext) } @@ -92,7 +101,22 @@ abstract class SpringBukkitPlugin : JavaPlugin(), ApplicationContextInitializer< } } - private fun registerPluginBean(applicationContext: ConfigurableApplicationContext) { - applicationContext.beanFactory.registerSingleton("plugin", this) + private fun registerPluginBean(beanDefinitionRegistry: BeanDefinitionRegistry) { + val definition: BeanDefinition = BeanDefinitionBuilder + .rootBeanDefinition( + Plugin::class.java + ) { this } + .getBeanDefinition() + beanDefinitionRegistry.registerBeanDefinition(this::class.qualifiedName!!, definition) + } + + private fun registerMetadataReaderFactory(beanDefinitionRegistry: BeanDefinitionRegistry) { + val beanName = "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory" + val definition: BeanDefinition = BeanDefinitionBuilder + .rootBeanDefinition( + SpringBukkitMetadataReaderFactoryBean::class.java + ) { SpringBukkitMetadataReaderFactoryBean() } + .getBeanDefinition() + beanDefinitionRegistry.registerBeanDefinition(beanName, definition) } } \ No newline at end of file diff --git a/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitResourceLoader.kt b/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitResourceLoader.kt new file mode 100644 index 0000000..4b2576d --- /dev/null +++ b/spring-bukkit-core/src/main/kotlin/kr/summitsystems/springbukkit/core/SpringBukkitResourceLoader.kt @@ -0,0 +1,57 @@ +package kr.summitsystems.springbukkit.core + +import org.bukkit.plugin.PluginManager +import org.bukkit.plugin.java.JavaPlugin +import org.springframework.core.io.DefaultResourceLoader +import org.springframework.core.io.Resource + +class SpringBukkitResourceLoader( + plugin: JavaPlugin, + classLoader: ClassLoader, + private val pluginManager: PluginManager +): DefaultResourceLoader(classLoader) { + private val dependResourceLoaders: List = plugin.description.depend.map { pluginId -> + val javaPlugin = pluginManager.getPlugin(pluginId) as? JavaPlugin + ?: throw IllegalStateException("The plugin with id $pluginId is not found.") + val getClassLoaderMethodAccessor = JavaPlugin::class.java.getDeclaredMethod("getClassLoader") + getClassLoaderMethodAccessor.isAccessible = true + val pluginClassLoader = getClassLoaderMethodAccessor.invoke(javaPlugin) as ClassLoader + InternalDefaultResourceLoader(pluginClassLoader) + } + + override fun getResource(location: String): Resource { + val superResource = super.getResource(location) + if (superResource.exists()) { + return superResource + } else { + dependResourceLoaders.forEach { dependResourceLoader -> + val dependResource = dependResourceLoader.getResource(location) + if (dependResource.exists()) { + return dependResource + } + } + } + return superResource + } + + override fun getResourceByPath(path: String): Resource { + val superResource = super.getResourceByPath(path) + if (superResource.exists()) { + return superResource + } else { + dependResourceLoaders.forEach { dependResourceLoader -> + val dependResource = dependResourceLoader.getResourceByPath(path) + if (dependResource.exists()) { + return dependResource + } + } + } + return superResource + } + + private class InternalDefaultResourceLoader(classLoader: ClassLoader): DefaultResourceLoader(classLoader) { + public override fun getResourceByPath(path: String): Resource { + return super.getResourceByPath(path) + } + } +} \ No newline at end of file diff --git a/spring-bukkit-support/src/main/kotlin/kr/summitsystems/springbukkit/support/folia/FoliaBukkitTaskScheduler.kt b/spring-bukkit-support/src/main/kotlin/kr/summitsystems/springbukkit/support/folia/FoliaBukkitTaskScheduler.kt index 8b0922b..13a07ae 100644 --- a/spring-bukkit-support/src/main/kotlin/kr/summitsystems/springbukkit/support/folia/FoliaBukkitTaskScheduler.kt +++ b/spring-bukkit-support/src/main/kotlin/kr/summitsystems/springbukkit/support/folia/FoliaBukkitTaskScheduler.kt @@ -9,10 +9,11 @@ import org.bukkit.plugin.Plugin import java.util.concurrent.TimeUnit class FoliaBukkitTaskScheduler( - private val plugin: Plugin -) : BukkitTaskScheduler { - private val globalRegionScheduler: GlobalRegionScheduler = Bukkit.getGlobalRegionScheduler() + private val plugin: Plugin, + private val globalRegionScheduler: GlobalRegionScheduler = Bukkit.getGlobalRegionScheduler(), private val asyncScheduler: AsyncScheduler = Bukkit.getAsyncScheduler() +) : BukkitTaskScheduler { + override fun schedule(task: (BukkitScheduledTask) -> Unit): BukkitScheduledTask { val scheduledTask = globalRegionScheduler.run(plugin) {