Skip to content

Commit 2c4e57d

Browse files
committed
feat: improve plugin system with better event management & support for enabling disabling plugins
1 parent b64af16 commit 2c4e57d

File tree

7 files changed

+230
-42
lines changed

7 files changed

+230
-42
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package co.statu.parsek
2+
3+
import org.pf4j.ManifestPluginDescriptorFinder
4+
import org.pf4j.PluginDescriptor
5+
import org.pf4j.util.StringUtils
6+
import java.util.jar.Manifest
7+
8+
class ParsekManifestPluginDescriptorFinder : ManifestPluginDescriptorFinder() {
9+
companion object {
10+
private const val PLUGIN_SOURCE_URL: String = "Plugin-Source-Url"
11+
}
12+
13+
override fun createPluginDescriptorInstance(): ParsekPluginDescriptor {
14+
return ParsekPluginDescriptor()
15+
}
16+
17+
override fun createPluginDescriptor(manifest: Manifest): PluginDescriptor {
18+
val pluginDescriptor = createPluginDescriptorInstance()
19+
20+
val attributes = manifest.mainAttributes
21+
val id = attributes.getValue(PLUGIN_ID)
22+
pluginDescriptor.pluginId = id
23+
24+
val description = attributes.getValue(PLUGIN_DESCRIPTION)
25+
pluginDescriptor.pluginDescription = if (StringUtils.isNullOrEmpty(description)) {
26+
""
27+
} else {
28+
description
29+
}
30+
31+
val clazz = attributes.getValue(PLUGIN_CLASS)
32+
if (StringUtils.isNotNullOrEmpty(clazz)) {
33+
pluginDescriptor.setPluginClass(clazz)
34+
}
35+
36+
val version = attributes.getValue(PLUGIN_VERSION)
37+
if (StringUtils.isNotNullOrEmpty(version)) {
38+
pluginDescriptor.setPluginVersion(version)
39+
}
40+
41+
val provider = attributes.getValue(PLUGIN_PROVIDER)
42+
pluginDescriptor.setProvider(provider)
43+
val dependencies = attributes.getValue(PLUGIN_DEPENDENCIES)
44+
pluginDescriptor.setDependencies(dependencies)
45+
46+
val requires = attributes.getValue(PLUGIN_REQUIRES)
47+
if (StringUtils.isNotNullOrEmpty(requires)) {
48+
pluginDescriptor.setRequires(requires)
49+
}
50+
51+
pluginDescriptor.setLicense(attributes.getValue(PLUGIN_LICENSE))
52+
53+
val sourceUrl = attributes.getValue(PLUGIN_SOURCE_URL)
54+
if (StringUtils.isNotNullOrEmpty(sourceUrl)) {
55+
pluginDescriptor.sourceUrl = sourceUrl
56+
}
57+
58+
return pluginDescriptor
59+
}
60+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package co.statu.parsek
2+
3+
import org.pf4j.DefaultPluginDescriptor
4+
import org.pf4j.PluginDescriptor
5+
6+
class ParsekPluginDescriptor : DefaultPluginDescriptor() {
7+
var sourceUrl: String? = null
8+
9+
public override fun setPluginId(pluginId: String): DefaultPluginDescriptor {
10+
return super.setPluginId(pluginId)
11+
}
12+
13+
public override fun setPluginDescription(pluginDescription: String?): PluginDescriptor {
14+
return super.setPluginDescription(pluginDescription)
15+
}
16+
17+
public override fun setPluginClass(pluginClassName: String?): PluginDescriptor {
18+
return super.setPluginClass(pluginClassName)
19+
}
20+
21+
public override fun setPluginVersion(version: String?): DefaultPluginDescriptor {
22+
return super.setPluginVersion(version)
23+
}
24+
25+
public override fun setProvider(provider: String?): PluginDescriptor {
26+
return super.setProvider(provider)
27+
}
28+
29+
public override fun setDependencies(dependencies: String?): PluginDescriptor {
30+
return super.setDependencies(dependencies)
31+
}
32+
33+
public override fun setRequires(requires: String?): PluginDescriptor {
34+
return super.setRequires(requires)
35+
}
36+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package co.statu.parsek
2+
3+
import co.statu.parsek.util.HashUtil.hash
4+
import com.typesafe.config.ConfigFactory
5+
import org.pf4j.PluginDescriptor
6+
import org.pf4j.PluginWrapper
7+
import java.nio.file.Path
8+
9+
class ParsekPluginWrapper(
10+
pluginManager: PluginManager,
11+
descriptor: PluginDescriptor,
12+
pluginPath: Path,
13+
internal val pluginClassLoader: ClassLoader
14+
) : PluginWrapper(pluginManager, descriptor, pluginPath, pluginClassLoader) {
15+
internal val config by lazy {
16+
val configResource = pluginClassLoader.getResourceAsStream("config.conf") ?: return@lazy null
17+
18+
val rawConfig = configResource.bufferedReader().readText()
19+
20+
return@lazy ConfigFactory.parseString(rawConfig)
21+
}
22+
23+
internal val hash = try {
24+
pluginPath.toFile().inputStream().hash()
25+
} catch (_: Exception) {
26+
""
27+
}
28+
}

src/main/kotlin/co/statu/parsek/PluginFactory.kt

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import org.pf4j.DefaultPluginFactory
88
import org.pf4j.Plugin
99
import org.pf4j.PluginWrapper
1010
import org.slf4j.LoggerFactory
11-
import org.springframework.context.annotation.AnnotationConfigApplicationContext
1211

1312
class PluginFactory : DefaultPluginFactory() {
1413
companion object {
@@ -29,30 +28,10 @@ class PluginFactory : DefaultPluginFactory() {
2928
plugin.pluginGlobalBeanContext = PluginManager.pluginGlobalBeanContext
3029
plugin.applicationContext = Main.applicationContext
3130

32-
val pluginBeanContext by lazy {
33-
val pluginBeanContext = AnnotationConfigApplicationContext()
34-
35-
pluginBeanContext.setAllowBeanDefinitionOverriding(true)
36-
37-
pluginBeanContext.parent = PluginManager.pluginGlobalBeanContext
38-
pluginBeanContext.classLoader = pluginClass.classLoader
39-
pluginBeanContext.scan(pluginClass.`package`.name)
40-
41-
pluginBeanContext.beanFactory.registerSingleton(plugin.logger.javaClass.name, plugin.logger)
42-
pluginBeanContext.beanFactory.registerSingleton(pluginEventManager.javaClass.name, pluginEventManager)
43-
pluginBeanContext.beanFactory.registerSingleton(plugin.javaClass.name, plugin)
44-
45-
pluginBeanContext.refresh()
46-
47-
pluginBeanContext
48-
}
49-
50-
plugin.pluginBeanContext = pluginBeanContext
51-
52-
pluginEventManager.initializePlugin(plugin, pluginBeanContext)
53-
5431
runBlocking {
55-
plugin.onLoad()
32+
plugin.load()
33+
plugin.onCreate()
34+
plugin.onStart()
5635
}
5736

5837
return plugin

src/main/kotlin/co/statu/parsek/PluginManager.kt

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package co.statu.parsek
22

3+
import co.statu.parsek.api.ParsekPlugin
4+
import kotlinx.coroutines.runBlocking
35
import org.pf4j.*
46
import org.springframework.context.annotation.AnnotationConfigApplicationContext
57
import java.nio.file.Path
@@ -22,10 +24,8 @@ class PluginManager(importPaths: List<Path>) : DefaultPluginManager(importPaths)
2224
}
2325

2426
override fun createPluginDescriptorFinder(): CompoundPluginDescriptorFinder {
25-
return CompoundPluginDescriptorFinder() // Demo is using the Manifest file
26-
// PropertiesPluginDescriptorFinder is commented out just to avoid error log
27-
//.add(PropertiesPluginDescriptorFinder())
28-
.add(ManifestPluginDescriptorFinder())
27+
return CompoundPluginDescriptorFinder()
28+
.add(ParsekManifestPluginDescriptorFinder())
2929
}
3030

3131
override fun createPluginFactory(): PluginFactory {
@@ -36,4 +36,56 @@ class PluginManager(importPaths: List<Path>) : DefaultPluginManager(importPaths)
3636
return CompoundPluginLoader()
3737
.add(ParsekPluginLoader(this)) { this.isNotDevelopment }
3838
}
39+
40+
fun getActivePlugins(): List<ParsekPlugin> = getPlugins(PluginState.STARTED).mapNotNull { plugin ->
41+
runCatching {
42+
val pluginWrapper = plugin as ParsekPluginWrapper
43+
pluginWrapper.plugin as ParsekPlugin
44+
}.getOrNull()
45+
}
46+
47+
fun getPluginWrappers() = plugins.values.map { it as ParsekPluginWrapper }
48+
49+
override fun createPluginWrapper(
50+
pluginDescriptor: PluginDescriptor,
51+
pluginPath: Path,
52+
pluginClassLoader: ClassLoader
53+
): PluginWrapper {
54+
val pluginWrapper = ParsekPluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader)
55+
56+
pluginWrapper.setPluginFactory(getPluginFactory())
57+
58+
return pluginWrapper
59+
}
60+
61+
62+
override fun enablePlugin(pluginId: String): Boolean {
63+
val result = super.enablePlugin(pluginId)
64+
65+
val plugin = getPlugin(pluginId)?.plugin as ParsekPlugin?
66+
67+
if (result) {
68+
plugin?.let {
69+
runBlocking {
70+
it.load()
71+
it.onEnable()
72+
it.onStart()
73+
}
74+
}
75+
}
76+
77+
return result
78+
}
79+
80+
override fun disablePlugin(pluginId: String): Boolean {
81+
val plugin = getPlugin(pluginId).plugin as ParsekPlugin
82+
83+
runBlocking {
84+
plugin.onStop()
85+
plugin.onDisable()
86+
plugin.unload()
87+
}
88+
89+
return super.disablePlugin(pluginId)
90+
}
3991
}

src/main/kotlin/co/statu/parsek/api/ParsekPlugin.kt

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package co.statu.parsek.api
22

33
import co.statu.parsek.Main
44
import co.statu.parsek.PluginEventManager
5+
import co.statu.parsek.PluginManager
56
import co.statu.parsek.ReleaseStage
67
import co.statu.parsek.api.event.PluginEventListener
78
import io.vertx.core.Vertx
8-
import kotlinx.coroutines.runBlocking
99
import org.pf4j.Plugin
1010
import org.slf4j.Logger
1111
import org.slf4j.LoggerFactory
@@ -65,32 +65,55 @@ abstract class ParsekPlugin : Plugin() {
6565
pluginEventManager.unRegister(this, eventListener)
6666
}
6767

68-
@Deprecated("Use onEnable method.")
68+
@Deprecated("Use onStart method.")
6969
override fun start() {
70-
runBlocking {
71-
onEnable()
72-
}
7370
}
7471

75-
@Deprecated("Use onDisable method.")
72+
@Deprecated("Use onStop method.")
7673
override fun stop() {
77-
pluginBeanContext.close()
74+
}
75+
76+
internal fun load() {
77+
val pluginBeanContext by lazy {
78+
val pluginBeanContext = AnnotationConfigApplicationContext()
79+
80+
pluginBeanContext.setAllowBeanDefinitionOverriding(true)
7881

82+
pluginBeanContext.parent = PluginManager.pluginGlobalBeanContext
83+
pluginBeanContext.classLoader = this.javaClass.classLoader
84+
pluginBeanContext.scan(this.javaClass.`package`.name)
85+
86+
pluginBeanContext.beanFactory.registerSingleton(this.logger.javaClass.name, this.logger)
87+
pluginBeanContext.beanFactory.registerSingleton(pluginEventManager.javaClass.name, pluginEventManager)
88+
pluginBeanContext.beanFactory.registerSingleton(this.javaClass.name, this)
89+
90+
pluginBeanContext.refresh()
91+
92+
pluginBeanContext
93+
}
94+
95+
this.pluginBeanContext = pluginBeanContext
96+
97+
pluginEventManager.initializePlugin(this, pluginBeanContext)
98+
}
99+
100+
internal fun unload() {
79101
val copyOfRegisteredBeans = registeredBeans.toList()
80102

81103
copyOfRegisteredBeans.forEach {
82-
unRegisterGlobal(it)
104+
try {
105+
unRegisterGlobal(it)
106+
} catch (e: Exception) {
107+
e.printStackTrace()
108+
}
83109
}
84110

85111
pluginEventManager.unregisterPlugin(this)
86-
87-
runBlocking {
88-
onDisable()
89-
}
90112
}
91113

92-
open suspend fun onLoad() {}
93-
114+
open suspend fun onCreate() {}
94115
open suspend fun onEnable() {}
95116
open suspend fun onDisable() {}
117+
open suspend fun onStart() {}
118+
open suspend fun onStop() {}
96119
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package co.statu.parsek.util
2+
3+
import java.io.InputStream
4+
import java.math.BigInteger
5+
import java.security.MessageDigest
6+
7+
object HashUtil {
8+
fun InputStream.hash() =
9+
String.format("%064x", BigInteger(1, MessageDigest.getInstance("SHA-256").digest(this.readBytes())))
10+
}

0 commit comments

Comments
 (0)