From a3920fc6cb66a914fecc0d3684e2d42aba3ec1c5 Mon Sep 17 00:00:00 2001 From: kahverengi Date: Thu, 21 Mar 2024 23:52:59 +0300 Subject: [PATCH] feat: improve plugin system BREAKING CHANGE: The major of plugin API has been changed! Be causes. We will implement full documentation soon for v2. --- src/main/kotlin/co/statu/parsek/Main.kt | 7 +- .../co/statu/parsek/PluginEventManager.kt | 57 +++++------ .../kotlin/co/statu/parsek/PluginFactory.kt | 40 ++++++-- .../kotlin/co/statu/parsek/api/ParsekEvent.kt | 3 - .../co/statu/parsek/api/ParsekPlugin.kt | 96 ++++++++++++++++++- .../co/statu/parsek/api/PluginContext.kt | 14 --- .../kotlin/co/statu/parsek/api/PluginEvent.kt | 4 - .../parsek/api/annotation/EventListener.kt | 10 ++ .../parsek/api/config/PluginConfigManager.kt | 2 +- .../parsek/api/event/CoreEventListener.kt | 7 ++ .../statu/parsek/api/event/EventListener.kt | 4 + .../parsek/api/event/ParsekEventListener.kt | 12 +-- .../parsek/api/event/PluginEventListener.kt | 4 + .../parsek/api/event/RouterEventListener.kt | 4 +- .../co/statu/parsek/route/RouterProvider.kt | 2 +- 15 files changed, 185 insertions(+), 81 deletions(-) delete mode 100644 src/main/kotlin/co/statu/parsek/api/ParsekEvent.kt delete mode 100644 src/main/kotlin/co/statu/parsek/api/PluginContext.kt delete mode 100644 src/main/kotlin/co/statu/parsek/api/PluginEvent.kt create mode 100755 src/main/kotlin/co/statu/parsek/api/annotation/EventListener.kt create mode 100644 src/main/kotlin/co/statu/parsek/api/event/CoreEventListener.kt create mode 100644 src/main/kotlin/co/statu/parsek/api/event/EventListener.kt create mode 100644 src/main/kotlin/co/statu/parsek/api/event/PluginEventListener.kt diff --git a/src/main/kotlin/co/statu/parsek/Main.kt b/src/main/kotlin/co/statu/parsek/Main.kt index 8c80fce..e376976 100755 --- a/src/main/kotlin/co/statu/parsek/Main.kt +++ b/src/main/kotlin/co/statu/parsek/Main.kt @@ -1,7 +1,7 @@ package co.statu.parsek import co.statu.parsek.annotation.Boot -import co.statu.parsek.api.event.ParsekEventListener +import co.statu.parsek.api.event.CoreEventListener import co.statu.parsek.config.ConfigManager import co.statu.parsek.util.TimeUtil import io.vertx.core.Vertx @@ -77,6 +77,8 @@ class Main : CoroutineVerticle() { enum class EnvironmentType { DEVELOPMENT, RELEASE } + + internal lateinit var applicationContext: AnnotationConfigApplicationContext } private val logger by lazy { @@ -84,7 +86,6 @@ class Main : CoroutineVerticle() { } private lateinit var router: Router - private lateinit var applicationContext: AnnotationConfigApplicationContext private lateinit var configManager: ConfigManager private lateinit var pluginManager: PluginManager @@ -146,7 +147,7 @@ class Main : CoroutineVerticle() { configManager.init() try { - val parsekEventHandlers = PluginEventManager.getEventHandlers() + val parsekEventHandlers = PluginEventManager.getParsekEventListeners() parsekEventHandlers.forEach { eventHandler -> eventHandler.onConfigManagerReady(configManager) diff --git a/src/main/kotlin/co/statu/parsek/PluginEventManager.kt b/src/main/kotlin/co/statu/parsek/PluginEventManager.kt index c9581f9..18bba52 100644 --- a/src/main/kotlin/co/statu/parsek/PluginEventManager.kt +++ b/src/main/kotlin/co/statu/parsek/PluginEventManager.kt @@ -1,50 +1,45 @@ package co.statu.parsek -import co.statu.parsek.api.ParsekEvent import co.statu.parsek.api.ParsekPlugin -import co.statu.parsek.api.PluginEvent +import co.statu.parsek.api.event.EventListener +import co.statu.parsek.api.event.ParsekEventListener +import co.statu.parsek.api.event.PluginEventListener +import org.springframework.context.annotation.AnnotationConfigApplicationContext class PluginEventManager { - @PublishedApi - internal val pluginEventListeners = mutableMapOf>() - companion object { - internal val parsekEventListeners = mutableMapOf>() + private val eventListeners = mutableMapOf>() - internal inline fun getEventHandlers() = - parsekEventListeners.flatMap { it.value }.filterIsInstance() - } + fun getEventListeners() = eventListeners.toMap() - private fun initializePluginIfNot(plugin: ParsekPlugin) { - if (pluginEventListeners[plugin] == null) { - pluginEventListeners[plugin] = mutableListOf() - } - - if (parsekEventListeners[plugin] == null) { - parsekEventListeners[plugin] = mutableListOf() - } - } + internal inline fun getParsekEventListeners() = + eventListeners.flatMap { it.value }.filterIsInstance() - fun register(plugin: ParsekPlugin, pluginEvent: PluginEvent) { - initializePluginIfNot(plugin) - pluginEventListeners[plugin]!!.add(pluginEvent) + inline fun getEventListeners() = + getEventListeners().flatMap { it.value }.filter { it !is ParsekEventListener }.filterIsInstance() } - fun register(plugin: ParsekPlugin, parsekEvent: ParsekEvent) { - initializePluginIfNot(plugin) - - parsekEventListeners[plugin]!!.add(parsekEvent) + internal fun initializePlugin(plugin: ParsekPlugin, pluginBeanContext: AnnotationConfigApplicationContext) { + if (eventListeners[plugin] == null) { + eventListeners[plugin] = pluginBeanContext + .getBeansWithAnnotation(co.statu.parsek.api.annotation.EventListener::class.java) + .map { it.value as EventListener } + .toMutableList() + } } - fun unregister(plugin: ParsekPlugin, pluginEvent: PluginEvent) { - pluginEventListeners[plugin]?.remove(pluginEvent) + internal fun unregisterPlugin(plugin: ParsekPlugin) { + eventListeners.remove(plugin) } - fun unregister(plugin: ParsekPlugin, parsekEvent: ParsekEvent) { - parsekEventListeners[plugin]?.remove(parsekEvent) + fun register(plugin: ParsekPlugin, eventListener: EventListener) { + if (eventListeners[plugin]!!.none { it::class == eventListener::class }) { + eventListeners[plugin]!!.add(eventListener) + } } - inline fun getEventHandlers() = - pluginEventListeners.flatMap { it.value }.filterIsInstance() + fun unRegister(plugin: ParsekPlugin, eventListener: EventListener) { + eventListeners[plugin]?.remove(eventListener) + } } \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/PluginFactory.kt b/src/main/kotlin/co/statu/parsek/PluginFactory.kt index cf6e427..cc4c196 100644 --- a/src/main/kotlin/co/statu/parsek/PluginFactory.kt +++ b/src/main/kotlin/co/statu/parsek/PluginFactory.kt @@ -3,11 +3,12 @@ package co.statu.parsek import co.statu.parsek.PluginManager.Companion.pluginEventManager import co.statu.parsek.SpringConfig.Companion.vertx import co.statu.parsek.api.ParsekPlugin -import co.statu.parsek.api.PluginContext +import kotlinx.coroutines.runBlocking import org.pf4j.DefaultPluginFactory import org.pf4j.Plugin import org.pf4j.PluginWrapper import org.slf4j.LoggerFactory +import org.springframework.context.annotation.AnnotationConfigApplicationContext class PluginFactory : DefaultPluginFactory() { companion object { @@ -15,18 +16,37 @@ class PluginFactory : DefaultPluginFactory() { } override fun createInstance(pluginClass: Class<*>, pluginWrapper: PluginWrapper): Plugin? { - val context = PluginContext( - pluginId = pluginWrapper.pluginId, - vertx = vertx, - pluginEventManager, - Main.ENVIRONMENT, - Main.STAGE - ) + val pluginBeanContext by lazy { + val pluginBeanContext = AnnotationConfigApplicationContext() + + pluginBeanContext.parent = Main.applicationContext + pluginBeanContext.classLoader = pluginClass.classLoader + pluginBeanContext.scan(pluginClass.`package`.name) + pluginBeanContext.refresh() + + pluginBeanContext + } try { - val constructor = pluginClass.getConstructor(PluginContext::class.java) + val constructor = pluginClass.getConstructor() + + val plugin = constructor.newInstance() as ParsekPlugin + + pluginEventManager.initializePlugin(plugin, pluginBeanContext) + + plugin.pluginId = pluginWrapper.pluginId + plugin.vertx = vertx + plugin.pluginEventManager = pluginEventManager + plugin.environmentType = Main.ENVIRONMENT + plugin.releaseStage = Main.STAGE + plugin.pluginBeanContext = pluginBeanContext + plugin.applicationContext = Main.applicationContext + + runBlocking { + plugin.onLoad() + } - return constructor.newInstance(context) as ParsekPlugin + return plugin } catch (e: Exception) { logger.error(e.message, e) } diff --git a/src/main/kotlin/co/statu/parsek/api/ParsekEvent.kt b/src/main/kotlin/co/statu/parsek/api/ParsekEvent.kt deleted file mode 100644 index 0b146e0..0000000 --- a/src/main/kotlin/co/statu/parsek/api/ParsekEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package co.statu.parsek.api - -interface ParsekEvent \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/ParsekPlugin.kt b/src/main/kotlin/co/statu/parsek/api/ParsekPlugin.kt index f05bb0b..af49c04 100644 --- a/src/main/kotlin/co/statu/parsek/api/ParsekPlugin.kt +++ b/src/main/kotlin/co/statu/parsek/api/ParsekPlugin.kt @@ -1,5 +1,99 @@ package co.statu.parsek.api +import co.statu.parsek.Main +import co.statu.parsek.PluginEventManager +import co.statu.parsek.ReleaseStage +import co.statu.parsek.api.event.PluginEventListener +import io.vertx.core.Vertx +import kotlinx.coroutines.runBlocking import org.pf4j.Plugin +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.support.BeanDefinitionRegistry +import org.springframework.context.annotation.AnnotationConfigApplicationContext -abstract class ParsekPlugin(val context: PluginContext) : Plugin() \ No newline at end of file +abstract class ParsekPlugin : Plugin() { + lateinit var pluginId: String + internal set + lateinit var vertx: Vertx + internal set + lateinit var pluginEventManager: PluginEventManager + internal set + lateinit var environmentType: Main.Companion.EnvironmentType + internal set + lateinit var releaseStage: ReleaseStage + internal set + lateinit var pluginBeanContext: AnnotationConfigApplicationContext + internal set + + internal lateinit var applicationContext: AnnotationConfigApplicationContext + + val logger: Logger = LoggerFactory.getLogger(this::class.java) + + private val registeredBeans = mutableListOf>() + + fun register(bean: Class<*>) { + if (registeredBeans.contains(bean)) { + return + } + + applicationContext.register(bean) + + registeredBeans.add(bean) + } + + fun register(eventListener: PluginEventListener) { + pluginEventManager.register(this, eventListener) + } + + fun unRegister(bean: Class<*>) { + if (!registeredBeans.contains(bean)) { + return + } + + val registry = applicationContext.beanFactory as BeanDefinitionRegistry + val beanNames = registry.beanDefinitionNames + + for (beanName in beanNames) { + if (registry.getBeanDefinition(beanName).beanClassName == bean.name) { + registry.removeBeanDefinition(beanName) + return // Stop after removing the first bean definition of the given class + } + } + + registeredBeans.remove(bean) + } + + fun unRegister(eventListener: PluginEventListener) { + pluginEventManager.unRegister(this, eventListener) + } + + @Deprecated("Use onEnable method.") + override fun start() { + runBlocking { + onEnable() + } + } + + @Deprecated("Use onDisable method.") + override fun stop() { + pluginBeanContext.close() + + val copyOfRegisteredBeans = registeredBeans.toList() + + copyOfRegisteredBeans.forEach { + unRegister(it) + } + + pluginEventManager.unregisterPlugin(this) + + runBlocking { + onDisable() + } + } + + open suspend fun onLoad() {} + + open suspend fun onEnable() {} + open suspend fun onDisable() {} +} \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/PluginContext.kt b/src/main/kotlin/co/statu/parsek/api/PluginContext.kt deleted file mode 100644 index cc729d1..0000000 --- a/src/main/kotlin/co/statu/parsek/api/PluginContext.kt +++ /dev/null @@ -1,14 +0,0 @@ -package co.statu.parsek.api - -import co.statu.parsek.Main -import co.statu.parsek.PluginEventManager -import co.statu.parsek.ReleaseStage -import io.vertx.core.Vertx - -class PluginContext( - val pluginId: String, - val vertx: Vertx, - val pluginEventManager: PluginEventManager, - val environmentType: Main.Companion.EnvironmentType, - val releaseStage: ReleaseStage -) \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/PluginEvent.kt b/src/main/kotlin/co/statu/parsek/api/PluginEvent.kt deleted file mode 100644 index 1d7a350..0000000 --- a/src/main/kotlin/co/statu/parsek/api/PluginEvent.kt +++ /dev/null @@ -1,4 +0,0 @@ -package co.statu.parsek.api - -interface PluginEvent { -} \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/annotation/EventListener.kt b/src/main/kotlin/co/statu/parsek/api/annotation/EventListener.kt new file mode 100755 index 0000000..ea2d4c1 --- /dev/null +++ b/src/main/kotlin/co/statu/parsek/api/annotation/EventListener.kt @@ -0,0 +1,10 @@ +package co.statu.parsek.api.annotation + +import org.springframework.stereotype.Component + +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Component +annotation class EventListener( + val value: String = "" +) diff --git a/src/main/kotlin/co/statu/parsek/api/config/PluginConfigManager.kt b/src/main/kotlin/co/statu/parsek/api/config/PluginConfigManager.kt index 523d864..281e9ce 100644 --- a/src/main/kotlin/co/statu/parsek/api/config/PluginConfigManager.kt +++ b/src/main/kotlin/co/statu/parsek/api/config/PluginConfigManager.kt @@ -18,7 +18,7 @@ class PluginConfigManager( private val gson = Gson() } - private val pluginId = plugin.context.pluginId + private val pluginId = plugin.pluginId private var isMigrated = false private var configAsJsonObject = configManager.getConfig() diff --git a/src/main/kotlin/co/statu/parsek/api/event/CoreEventListener.kt b/src/main/kotlin/co/statu/parsek/api/event/CoreEventListener.kt new file mode 100644 index 0000000..09c7594 --- /dev/null +++ b/src/main/kotlin/co/statu/parsek/api/event/CoreEventListener.kt @@ -0,0 +1,7 @@ +package co.statu.parsek.api.event + +import co.statu.parsek.config.ConfigManager + +interface CoreEventListener : ParsekEventListener { + suspend fun onConfigManagerReady(configManager: ConfigManager) +} \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/event/EventListener.kt b/src/main/kotlin/co/statu/parsek/api/event/EventListener.kt new file mode 100644 index 0000000..3fdbea4 --- /dev/null +++ b/src/main/kotlin/co/statu/parsek/api/event/EventListener.kt @@ -0,0 +1,4 @@ +package co.statu.parsek.api.event + +interface EventListener { +} \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/event/ParsekEventListener.kt b/src/main/kotlin/co/statu/parsek/api/event/ParsekEventListener.kt index 794129b..97beed3 100644 --- a/src/main/kotlin/co/statu/parsek/api/event/ParsekEventListener.kt +++ b/src/main/kotlin/co/statu/parsek/api/event/ParsekEventListener.kt @@ -1,13 +1,3 @@ package co.statu.parsek.api.event -import co.statu.parsek.api.ParsekEvent -import co.statu.parsek.config.ConfigManager - -/** - * ParsekEventListener is an extension point for listening Parsek related events - * such as when config manager has been initialized. - */ -interface ParsekEventListener : ParsekEvent { - - suspend fun onConfigManagerReady(configManager: ConfigManager) -} \ No newline at end of file +interface ParsekEventListener : EventListener \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/event/PluginEventListener.kt b/src/main/kotlin/co/statu/parsek/api/event/PluginEventListener.kt new file mode 100644 index 0000000..1aa7bed --- /dev/null +++ b/src/main/kotlin/co/statu/parsek/api/event/PluginEventListener.kt @@ -0,0 +1,4 @@ +package co.statu.parsek.api.event + +interface PluginEventListener : EventListener { +} \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/api/event/RouterEventListener.kt b/src/main/kotlin/co/statu/parsek/api/event/RouterEventListener.kt index afedbb8..efb4594 100644 --- a/src/main/kotlin/co/statu/parsek/api/event/RouterEventListener.kt +++ b/src/main/kotlin/co/statu/parsek/api/event/RouterEventListener.kt @@ -1,8 +1,8 @@ package co.statu.parsek.api.event -import co.statu.parsek.api.ParsekEvent import co.statu.parsek.model.Route -interface RouterEventListener : ParsekEvent { +interface RouterEventListener : ParsekEventListener { + fun onInitRouteList(routes: MutableList) } \ No newline at end of file diff --git a/src/main/kotlin/co/statu/parsek/route/RouterProvider.kt b/src/main/kotlin/co/statu/parsek/route/RouterProvider.kt index 312f7a2..e456b91 100755 --- a/src/main/kotlin/co/statu/parsek/route/RouterProvider.kt +++ b/src/main/kotlin/co/statu/parsek/route/RouterProvider.kt @@ -66,7 +66,7 @@ class RouterProvider private constructor( val routeList = beans.map { it.value as Route }.toMutableList() - val routerEventHandlers = PluginEventManager.getEventHandlers() + val routerEventHandlers = PluginEventManager.getParsekEventListeners() routerEventHandlers.forEach { eventHandler -> eventHandler.onInitRouteList(routeList)