Skip to content

Commit

Permalink
feat: improve plugin system
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The major of plugin API has been changed! Be cautious. We will implement full documentation soon for v2.
  • Loading branch information
duruer committed Mar 21, 2024
1 parent 2e79ace commit a4744bc
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 81 deletions.
7 changes: 4 additions & 3 deletions src/main/kotlin/co/statu/parsek/Main.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -77,14 +77,15 @@ class Main : CoroutineVerticle() {
enum class EnvironmentType {
DEVELOPMENT, RELEASE
}

internal lateinit var applicationContext: AnnotationConfigApplicationContext
}

private val logger by lazy {
LoggerFactory.getLogger("Parsek")
}

private lateinit var router: Router
private lateinit var applicationContext: AnnotationConfigApplicationContext
private lateinit var configManager: ConfigManager
private lateinit var pluginManager: PluginManager

Expand Down Expand Up @@ -146,7 +147,7 @@ class Main : CoroutineVerticle() {
configManager.init()

try {
val parsekEventHandlers = PluginEventManager.getEventHandlers<ParsekEventListener>()
val parsekEventHandlers = PluginEventManager.getParsekEventListeners<CoreEventListener>()

parsekEventHandlers.forEach { eventHandler ->
eventHandler.onConfigManagerReady(configManager)
Expand Down
57 changes: 26 additions & 31 deletions src/main/kotlin/co/statu/parsek/PluginEventManager.kt
Original file line number Diff line number Diff line change
@@ -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<ParsekPlugin, MutableList<PluginEvent>>()

companion object {
internal val parsekEventListeners = mutableMapOf<ParsekPlugin, MutableList<ParsekEvent>>()
private val eventListeners = mutableMapOf<ParsekPlugin, MutableList<EventListener>>()

internal inline fun <reified T : ParsekEvent> getEventHandlers() =
parsekEventListeners.flatMap { it.value }.filterIsInstance<T>()
}
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 <reified T : ParsekEventListener> getParsekEventListeners() =
eventListeners.flatMap { it.value }.filterIsInstance<T>()

fun register(plugin: ParsekPlugin, pluginEvent: PluginEvent) {
initializePluginIfNot(plugin)

pluginEventListeners[plugin]!!.add(pluginEvent)
inline fun <reified T : PluginEventListener> getEventListeners() =
getEventListeners().flatMap { it.value }.filter { it !is ParsekEventListener }.filterIsInstance<T>()
}

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 <reified T : PluginEvent> getEventHandlers() =
pluginEventListeners.flatMap { it.value }.filterIsInstance<T>()
fun unRegister(plugin: ParsekPlugin, eventListener: EventListener) {
eventListeners[plugin]?.remove(eventListener)
}
}
40 changes: 30 additions & 10 deletions src/main/kotlin/co/statu/parsek/PluginFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,50 @@ 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 {
private val logger = LoggerFactory.getLogger(PluginFactory::class.java)
}

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)
}
Expand Down
3 changes: 0 additions & 3 deletions src/main/kotlin/co/statu/parsek/api/ParsekEvent.kt

This file was deleted.

96 changes: 95 additions & 1 deletion src/main/kotlin/co/statu/parsek/api/ParsekPlugin.kt
Original file line number Diff line number Diff line change
@@ -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()
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<Class<*>>()

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() {}
}
14 changes: 0 additions & 14 deletions src/main/kotlin/co/statu/parsek/api/PluginContext.kt

This file was deleted.

4 changes: 0 additions & 4 deletions src/main/kotlin/co/statu/parsek/api/PluginEvent.kt

This file was deleted.

10 changes: 10 additions & 0 deletions src/main/kotlin/co/statu/parsek/api/annotation/EventListener.kt
Original file line number Diff line number Diff line change
@@ -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 = ""
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PluginConfigManager<T : PluginConfig>(
private val gson = Gson()
}

private val pluginId = plugin.context.pluginId
private val pluginId = plugin.pluginId
private var isMigrated = false

private var configAsJsonObject = configManager.getConfig()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.statu.parsek.api.event

import co.statu.parsek.config.ConfigManager

interface CoreEventListener : ParsekEventListener {
suspend fun onConfigManagerReady(configManager: ConfigManager)
}
4 changes: 4 additions & 0 deletions src/main/kotlin/co/statu/parsek/api/event/EventListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package co.statu.parsek.api.event

interface EventListener {
}
12 changes: 1 addition & 11 deletions src/main/kotlin/co/statu/parsek/api/event/ParsekEventListener.kt
Original file line number Diff line number Diff line change
@@ -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)
}
interface ParsekEventListener : EventListener
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package co.statu.parsek.api.event

interface PluginEventListener : EventListener {
}
Original file line number Diff line number Diff line change
@@ -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<Route>)
}
2 changes: 1 addition & 1 deletion src/main/kotlin/co/statu/parsek/route/RouterProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class RouterProvider private constructor(

val routeList = beans.map { it.value as Route }.toMutableList()

val routerEventHandlers = PluginEventManager.getEventHandlers<RouterEventListener>()
val routerEventHandlers = PluginEventManager.getParsekEventListeners<RouterEventListener>()

routerEventHandlers.forEach { eventHandler ->
eventHandler.onInitRouteList(routeList)
Expand Down

0 comments on commit a4744bc

Please sign in to comment.