From 2e49a1ec12d17e02f0d3d810c0deec5e316e9019 Mon Sep 17 00:00:00 2001 From: LamGC Date: Tue, 15 Feb 2022 17:07:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(extension):=20=E6=B7=BB=E5=8A=A0=20FoundEx?= =?UTF-8?q?tensionPackage=20=E6=8E=A5=E5=8F=A3.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用 FoundExtensionPackage 接口, ExtensionPackageFinder 可以将加载扩展包的时机推迟到需要加载的时候, 这么做可以让搜索器预先找好扩展包而无需立即加载, 避免了资源浪费. Issue #1 --- scalabot-app/src/main/kotlin/Extension.kt | 131 +++++++++++++++++----- 1 file changed, 103 insertions(+), 28 deletions(-) diff --git a/scalabot-app/src/main/kotlin/Extension.kt b/scalabot-app/src/main/kotlin/Extension.kt index e18b7ad..dbe6bad 100644 --- a/scalabot-app/src/main/kotlin/Extension.kt +++ b/scalabot-app/src/main/kotlin/Extension.kt @@ -12,9 +12,12 @@ import org.jdom2.input.SAXBuilder import org.jdom2.xpath.XPathFactory import org.telegram.abilitybots.api.util.AbilityExtension import java.io.File +import java.io.FileNotFoundException import java.io.InputStream import java.net.URL import java.net.URLClassLoader +import java.net.URLDecoder +import java.nio.charset.StandardCharsets import java.util.* import java.util.concurrent.atomic.AtomicInteger import java.util.jar.JarEntry @@ -32,48 +35,60 @@ internal class ExtensionLoader( MavenMetaInformationFinder ) - fun getExtensions(): Set { - val extensionEntries = mutableSetOf() + fun getExtensions(): Set { + val extensionEntries = mutableSetOf() for (extensionArtifact in bot.extensions) { val extensionFilesMap = findExtensionPackageFile(extensionArtifact) - val extensionFiles = filesMapToSet(extensionFilesMap) - if (extensionFiles.size > 1) { + val foundedNumber = allFoundedPackageNumber(extensionFilesMap) + if (foundedNumber > 1) { printExtensionFileConflictError(extensionArtifact, extensionFilesMap) continue - } else if (extensionFiles.isEmpty()) { + } else if (foundedNumber == 0) { log.warn { "[Bot ${bot.botUsername}] 找不到符合的扩展包文件: $extensionArtifact" } continue } - extensionEntries.addAll(getExtensionFactories(extensionArtifact, extensionFiles.first())) + val files = loadFoundExtensionPackage(extensionFilesMap) + extensionEntries.addAll(getExtensionFactories(extensionArtifact, files.first())) } return extensionEntries.toSet() } - private fun getExtensionFactories(extensionArtifact: Artifact, extensionFile: File): Set { + private fun loadFoundExtensionPackage(packageMap: Map>): Set { + val files = mutableSetOf() + for (set in packageMap.values) { + for (foundedExtensionPackage in set) { + files.add(foundedExtensionPackage.loadExtension()) + } + } + return files + } + + private fun getExtensionFactories(extensionArtifact: Artifact, extensionFile: File): Set { val extClassLoader = ExtensionClassLoaderCleaner.getOrCreateExtensionClassLoader(extensionArtifact, extensionFile) - val factories = mutableSetOf() + val factories = mutableSetOf() for (factory in extClassLoader.serviceLoader) { val extension = factory.createExtensionInstance(bot, getExtensionDataFolder(extensionArtifact)) - factories.add(ExtensionEntry(extensionArtifact, factory::class.java, extension)) + factories.add(LoadedExtensionEntry(extensionArtifact, factory::class.java, extension)) } return factories.toSet() } - private fun filesMapToSet(filesMap: Map>): MutableSet { - val result: MutableSet = mutableSetOf() + private fun allFoundedPackageNumber(filesMap: Map>): Int { + val result = mutableSetOf() for (files in filesMap.values) { - result.addAll(files) + for (file in files) { + result.add(file.getRawUrl()) + } } - return result + return result.size } private fun findExtensionPackageFile( extensionArtifact: Artifact, - extensionsPath: File = AppPaths.EXTENSIONS.file - ): Map> { - val result = mutableMapOf>() + ): Map> { + val result = mutableMapOf>() for (finder in finders) { val artifacts = finder.findByArtifact(extensionArtifact, extensionsPath) if (artifacts.isNotEmpty()) { @@ -85,17 +100,19 @@ internal class ExtensionLoader( private fun printExtensionFileConflictError( extensionArtifact: Artifact, - foundResult: Map> + foundResult: Map> ) { val errMessage = StringBuilder( """ [Bot ${bot.botUsername}] 扩展包 $extensionArtifact 存在多个文件, 为防止安全问题, 已禁止加载该扩展包: """.trimIndent() ).append('\n') + foundResult.forEach { (finder, files) -> errMessage.append("\t- 搜索器 `").append(finder::class.simpleName).append("` 找到了以下扩展包: \n") 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 } @@ -110,8 +127,7 @@ internal class ExtensionLoader( return dataFolder } - - data class ExtensionEntry( + data class LoadedExtensionEntry( val extensionArtifact: Artifact, val factoryClass: Class, val extension: AbilityExtension @@ -180,22 +196,78 @@ internal interface ExtensionPackageFinder { * @param extensionsPath 建议的搜索路径, 如搜索器希望通过网络来获取也可以. * @return 返回按搜索器的方式可以找到的所有与构件坐标有关的扩展包路径. */ - fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set + fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set +} + +/** + * 已找到的扩展包信息. + * 通过实现该接口, 以寻找远端文件的 [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 { - override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set { + override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set { val focusName = getExtensionFilename(extensionArtifact) val files = extensionsPath.listFiles { file -> file.nameWithoutExtension.endsWith(focusName) + } ?: return emptySet() + + val extensionPackage = mutableSetOf() + 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) = @@ -203,14 +275,17 @@ internal object FileNameFinder : ExtensionPackageFinder { } +/** + * 通过检查 Maven 在发布构件时打包进去的元信息(包括 POM 文件)来获取构件坐标. + */ internal object MavenMetaInformationFinder : ExtensionPackageFinder { private const val MAVEN_META_XML = "pom.xml" private const val MAVEN_META_PROPERTIES = "pom.properties" - override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set { + override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set { val files = extensionsPath.listFiles() ?: return emptySet() - val result = mutableSetOf() + val result = mutableSetOf() for (file in files) { if (file.isFile) { val foundArtifact = when (file.extension) { @@ -222,12 +297,12 @@ internal object MavenMetaInformationFinder : ExtensionPackageFinder { else -> null } if (foundArtifact != null && extensionArtifact.equalsArtifact(foundArtifact)) { - result.add(file) + result.add(FileFoundExtensionPackage(extensionArtifact, file)) } } else if (file.isDirectory) { val foundArtifact = getArtifactCoordinateFromArtifactDirectory(file) if (foundArtifact != null && extensionArtifact.equalsArtifact(foundArtifact)) { - result.add(file) + result.add(FileFoundExtensionPackage(extensionArtifact, file)) } } }