feat(extension): 添加 FoundExtensionPackage 接口.

使用 FoundExtensionPackage 接口, ExtensionPackageFinder 可以将加载扩展包的时机推迟到需要加载的时候,
这么做可以让搜索器预先找好扩展包而无需立即加载, 避免了资源浪费.

Issue #1
This commit is contained in:
LamGC 2022-02-15 17:07:41 +08:00
parent 5556613087
commit 2e49a1ec12
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D

View File

@ -12,9 +12,12 @@ import org.jdom2.input.SAXBuilder
import org.jdom2.xpath.XPathFactory import org.jdom2.xpath.XPathFactory
import org.telegram.abilitybots.api.util.AbilityExtension import org.telegram.abilitybots.api.util.AbilityExtension
import java.io.File import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream import java.io.InputStream
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.JarEntry import java.util.jar.JarEntry
@ -32,48 +35,60 @@ internal class ExtensionLoader(
MavenMetaInformationFinder MavenMetaInformationFinder
) )
fun getExtensions(): Set<ExtensionEntry> { fun getExtensions(): Set<LoadedExtensionEntry> {
val extensionEntries = mutableSetOf<ExtensionEntry>() val extensionEntries = mutableSetOf<LoadedExtensionEntry>()
for (extensionArtifact in bot.extensions) { for (extensionArtifact in bot.extensions) {
val extensionFilesMap = findExtensionPackageFile(extensionArtifact) val extensionFilesMap = findExtensionPackageFile(extensionArtifact)
val extensionFiles = filesMapToSet(extensionFilesMap) val foundedNumber = allFoundedPackageNumber(extensionFilesMap)
if (extensionFiles.size > 1) { if (foundedNumber > 1) {
printExtensionFileConflictError(extensionArtifact, extensionFilesMap) printExtensionFileConflictError(extensionArtifact, extensionFilesMap)
continue continue
} else if (extensionFiles.isEmpty()) { } else if (foundedNumber == 0) {
log.warn { "[Bot ${bot.botUsername}] 找不到符合的扩展包文件: $extensionArtifact" } log.warn { "[Bot ${bot.botUsername}] 找不到符合的扩展包文件: $extensionArtifact" }
continue continue
} }
extensionEntries.addAll(getExtensionFactories(extensionArtifact, extensionFiles.first())) val files = loadFoundExtensionPackage(extensionFilesMap)
extensionEntries.addAll(getExtensionFactories(extensionArtifact, files.first()))
} }
return extensionEntries.toSet() return extensionEntries.toSet()
} }
private fun getExtensionFactories(extensionArtifact: Artifact, extensionFile: File): Set<ExtensionEntry> { private fun loadFoundExtensionPackage(packageMap: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>): Set<File> {
val files = mutableSetOf<File>()
for (set in packageMap.values) {
for (foundedExtensionPackage in set) {
files.add(foundedExtensionPackage.loadExtension())
}
}
return files
}
private fun getExtensionFactories(extensionArtifact: Artifact, extensionFile: File): Set<LoadedExtensionEntry> {
val extClassLoader = val extClassLoader =
ExtensionClassLoaderCleaner.getOrCreateExtensionClassLoader(extensionArtifact, extensionFile) ExtensionClassLoaderCleaner.getOrCreateExtensionClassLoader(extensionArtifact, extensionFile)
val factories = mutableSetOf<ExtensionEntry>() val factories = mutableSetOf<LoadedExtensionEntry>()
for (factory in extClassLoader.serviceLoader) { for (factory in extClassLoader.serviceLoader) {
val extension = val extension =
factory.createExtensionInstance(bot, getExtensionDataFolder(extensionArtifact)) factory.createExtensionInstance(bot, getExtensionDataFolder(extensionArtifact))
factories.add(ExtensionEntry(extensionArtifact, factory::class.java, extension)) factories.add(LoadedExtensionEntry(extensionArtifact, factory::class.java, extension))
} }
return factories.toSet() return factories.toSet()
} }
private fun filesMapToSet(filesMap: Map<ExtensionPackageFinder, Set<File>>): MutableSet<File> { private fun allFoundedPackageNumber(filesMap: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>): Int {
val result: MutableSet<File> = mutableSetOf() val result = mutableSetOf<URL>()
for (files in filesMap.values) { for (files in filesMap.values) {
result.addAll(files) for (file in files) {
result.add(file.getRawUrl())
}
} }
return result return result.size
} }
private fun findExtensionPackageFile( private fun findExtensionPackageFile(
extensionArtifact: Artifact, extensionArtifact: Artifact,
extensionsPath: File = AppPaths.EXTENSIONS.file ): Map<ExtensionPackageFinder, Set<FoundExtensionPackage>> {
): Map<ExtensionPackageFinder, Set<File>> { val result = mutableMapOf<ExtensionPackageFinder, Set<FoundExtensionPackage>>()
val result = mutableMapOf<ExtensionPackageFinder, Set<File>>()
for (finder in finders) { for (finder in finders) {
val artifacts = finder.findByArtifact(extensionArtifact, extensionsPath) val artifacts = finder.findByArtifact(extensionArtifact, extensionsPath)
if (artifacts.isNotEmpty()) { if (artifacts.isNotEmpty()) {
@ -85,17 +100,19 @@ internal class ExtensionLoader(
private fun printExtensionFileConflictError( private fun printExtensionFileConflictError(
extensionArtifact: Artifact, extensionArtifact: Artifact,
foundResult: Map<ExtensionPackageFinder, Set<File>> foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>
) { ) {
val errMessage = StringBuilder( val errMessage = StringBuilder(
""" """
[Bot ${bot.botUsername}] 扩展包 $extensionArtifact 存在多个文件, 为防止安全问题, 已禁止加载该扩展包: [Bot ${bot.botUsername}] 扩展包 $extensionArtifact 存在多个文件, 为防止安全问题, 已禁止加载该扩展包:
""".trimIndent() """.trimIndent()
).append('\n') ).append('\n')
foundResult.forEach { (finder, files) -> foundResult.forEach { (finder, files) ->
errMessage.append("\t- 搜索器 `").append(finder::class.simpleName).append("` 找到了以下扩展包: \n") errMessage.append("\t- 搜索器 `").append(finder::class.simpleName).append("` 找到了以下扩展包: \n")
for (file in files) { for (file in files) {
errMessage.append("\t\t* ").append(file.canonicalPath).append('\n') errMessage.append("\t\t* ")
.append(URLDecoder.decode(file.getRawUrl().toString(), StandardCharsets.UTF_8)).append('\n')
} }
} }
log.error { errMessage } log.error { errMessage }
@ -110,8 +127,7 @@ internal class ExtensionLoader(
return dataFolder return dataFolder
} }
data class LoadedExtensionEntry(
data class ExtensionEntry(
val extensionArtifact: Artifact, val extensionArtifact: Artifact,
val factoryClass: Class<out BotExtensionFactory>, val factoryClass: Class<out BotExtensionFactory>,
val extension: AbilityExtension val extension: AbilityExtension
@ -180,22 +196,78 @@ internal interface ExtensionPackageFinder {
* @param extensionsPath 建议的搜索路径, 如搜索器希望通过网络来获取也可以. * @param extensionsPath 建议的搜索路径, 如搜索器希望通过网络来获取也可以.
* @return 返回按搜索器的方式可以找到的所有与构件坐标有关的扩展包路径. * @return 返回按搜索器的方式可以找到的所有与构件坐标有关的扩展包路径.
*/ */
fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<File> fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<FoundExtensionPackage>
}
/**
* 已找到的扩展包信息.
* 通过实现该接口, 以寻找远端文件的 [ExtensionPackageFinder]
* 可以在适当的时候将扩展包下载到本地, 而无需在搜索阶段下载扩展包.
*/
internal interface FoundExtensionPackage {
/**
* 获取扩展包的构件坐标.
* @return 返回扩展包的构件坐标.
*/
fun getExtensionArtifact(): Artifact
/**
* 获取原始的扩展 Url.
* @return 返回扩展包所在的 Url.
*/
fun getRawUrl(): URL
/**
* 获取扩展包并返回扩展包在本地的 File 对象.
*
* 当调用本方法时, Finder 可以将扩展包下载到本地(如果扩展包在远端服务器的话).
* @return 返回扩展包在本地存储时指向扩展包文件的 File 对象.
*/
fun loadExtension(): File
}
/**
* 已找到的扩展包文件.
* @param artifact 扩展包构件坐标.
* @param file 已找到的扩展包文件.
*/
internal class FileFoundExtensionPackage(private val artifact: Artifact, private val file: File) :
FoundExtensionPackage {
init {
if (!file.exists()) {
throw FileNotFoundException(file.canonicalPath)
}
}
override fun getExtensionArtifact(): Artifact = artifact
override fun getRawUrl(): URL = file.canonicalFile.toURI().toURL()
override fun loadExtension(): File = file
} }
/** /**
* 基于文件名的搜索器. * 基于文件名的搜索器.
* *
* 将搜索文件名(不带扩展包名)结尾为 `${groupId}-${artifactId}-${version}` 的文件. * 将搜索文件名(不带扩展包名)结尾为 `${groupId}_${artifactId}_${version}` 的文件.
* 比如说 `(Example Extension) org.example_scalabot-example_v1.0.0-SNAPSHOT.jar` 是可以的
*/ */
internal object FileNameFinder : ExtensionPackageFinder { internal object FileNameFinder : ExtensionPackageFinder {
override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<File> { override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<FoundExtensionPackage> {
val focusName = getExtensionFilename(extensionArtifact) val focusName = getExtensionFilename(extensionArtifact)
val files = extensionsPath.listFiles { file -> val files = extensionsPath.listFiles { file ->
file.nameWithoutExtension.endsWith(focusName) file.nameWithoutExtension.endsWith(focusName)
} ?: return emptySet()
val extensionPackage = mutableSetOf<FoundExtensionPackage>()
for (file in files) {
extensionPackage.add(FileFoundExtensionPackage(extensionArtifact, file))
} }
return files?.toSet() ?: emptySet() return if (extensionPackage.isEmpty()) emptySet() else extensionPackage
} }
private fun getExtensionFilename(extensionArtifact: Artifact) = private fun getExtensionFilename(extensionArtifact: Artifact) =
@ -203,14 +275,17 @@ internal object FileNameFinder : ExtensionPackageFinder {
} }
/**
* 通过检查 Maven 在发布构件时打包进去的元信息(包括 POM 文件)来获取构件坐标.
*/
internal object MavenMetaInformationFinder : ExtensionPackageFinder { internal object MavenMetaInformationFinder : ExtensionPackageFinder {
private const val MAVEN_META_XML = "pom.xml" private const val MAVEN_META_XML = "pom.xml"
private const val MAVEN_META_PROPERTIES = "pom.properties" private const val MAVEN_META_PROPERTIES = "pom.properties"
override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<File> { override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<FoundExtensionPackage> {
val files = extensionsPath.listFiles() ?: return emptySet() val files = extensionsPath.listFiles() ?: return emptySet()
val result = mutableSetOf<File>() val result = mutableSetOf<FoundExtensionPackage>()
for (file in files) { for (file in files) {
if (file.isFile) { if (file.isFile) {
val foundArtifact = when (file.extension) { val foundArtifact = when (file.extension) {
@ -222,12 +297,12 @@ internal object MavenMetaInformationFinder : ExtensionPackageFinder {
else -> null else -> null
} }
if (foundArtifact != null && extensionArtifact.equalsArtifact(foundArtifact)) { if (foundArtifact != null && extensionArtifact.equalsArtifact(foundArtifact)) {
result.add(file) result.add(FileFoundExtensionPackage(extensionArtifact, file))
} }
} else if (file.isDirectory) { } else if (file.isDirectory) {
val foundArtifact = getArtifactCoordinateFromArtifactDirectory(file) val foundArtifact = getArtifactCoordinateFromArtifactDirectory(file)
if (foundArtifact != null && extensionArtifact.equalsArtifact(foundArtifact)) { if (foundArtifact != null && extensionArtifact.equalsArtifact(foundArtifact)) {
result.add(file) result.add(FileFoundExtensionPackage(extensionArtifact, file))
} }
} }
} }