mirror of
https://gitee.com/devlive-community/datacap.git
synced 2024-11-29 18:48:23 +08:00
feat(plugin): support tar loader
This commit is contained in:
parent
c7cda15d42
commit
39465a6a67
@ -43,7 +43,7 @@
|
|||||||
"type": "Converter",
|
"type": "Converter",
|
||||||
"version": "2024.4.0-SNAPSHOT",
|
"version": "2024.4.0-SNAPSHOT",
|
||||||
"author": "datacap-community",
|
"author": "datacap-community",
|
||||||
"logo": "",
|
"logo": "https://cdn.north.devlive.org/applications/datacap/resources/logo/convert/csv.svg",
|
||||||
"released": "2024-11-21 23:11:15",
|
"released": "2024-11-21 23:11:15",
|
||||||
"supportVersion": [
|
"supportVersion": [
|
||||||
"ALL"
|
"ALL"
|
||||||
@ -73,7 +73,7 @@
|
|||||||
"type": "Converter",
|
"type": "Converter",
|
||||||
"version": "2024.4.0-SNAPSHOT",
|
"version": "2024.4.0-SNAPSHOT",
|
||||||
"author": "datacap-community",
|
"author": "datacap-community",
|
||||||
"logo": "https://www.vectorlogo.zone/logos/json/json-icon.svg",
|
"logo": "https://cdn.north.devlive.org/applications/datacap/resources/logo/convert/json.svg",
|
||||||
"released": "2024-11-21 23:11:15",
|
"released": "2024-11-21 23:11:15",
|
||||||
"supportVersion": [
|
"supportVersion": [
|
||||||
"ALL"
|
"ALL"
|
||||||
@ -88,7 +88,7 @@
|
|||||||
"type": "Converter",
|
"type": "Converter",
|
||||||
"version": "2024.4.0-SNAPSHOT",
|
"version": "2024.4.0-SNAPSHOT",
|
||||||
"author": "datacap-community",
|
"author": "datacap-community",
|
||||||
"logo": "https://www.vectorlogo.zone/logos/w3c_xml/w3c_xml-icon.svg",
|
"logo": "https://cdn.north.devlive.org/applications/datacap/resources/logo/convert/xml.svg",
|
||||||
"released": "2024-11-21 23:11:15",
|
"released": "2024-11-21 23:11:15",
|
||||||
"supportVersion": [
|
"supportVersion": [
|
||||||
"ALL"
|
"ALL"
|
||||||
@ -103,7 +103,7 @@
|
|||||||
"type": "Converter",
|
"type": "Converter",
|
||||||
"version": "2024.4.0-SNAPSHOT",
|
"version": "2024.4.0-SNAPSHOT",
|
||||||
"author": "datacap-community",
|
"author": "datacap-community",
|
||||||
"logo": "",
|
"logo": "https://cdn.north.devlive.org/applications/datacap/resources/logo/convert/none.svg",
|
||||||
"released": "2024-11-21 23:11:15",
|
"released": "2024-11-21 23:11:15",
|
||||||
"supportVersion": [
|
"supportVersion": [
|
||||||
"ALL"
|
"ALL"
|
||||||
|
@ -32,7 +32,7 @@ for file in "$HOME/dist"/*bin.tar.gz; do
|
|||||||
if [ -e "$file" ]; then
|
if [ -e "$file" ]; then
|
||||||
filename=$(basename "$file")
|
filename=$(basename "$file")
|
||||||
echo "Uploading binary: $filename"
|
echo "Uploading binary: $filename"
|
||||||
qshell fput --overwrite "$BUCKET_NAME" "$DATACAP_HOME/$VERSION/$filename" "$file"
|
qshell fput --overwrite "$BUCKET_NAME" "$DATACAP_HOME/versions/$VERSION/$filename" "$file"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -13,6 +13,11 @@
|
|||||||
<artifactId>datacap-plugin</artifactId>
|
<artifactId>datacap-plugin</artifactId>
|
||||||
<description>DataCap - Plugin Core</description>
|
<description>DataCap - Plugin Core</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<common-compress.version>1.26.0</common-compress.version>
|
||||||
|
<httpclient.version>4.5.14</httpclient.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -35,6 +40,16 @@
|
|||||||
<artifactId>maven-model</artifactId>
|
<artifactId>maven-model</artifactId>
|
||||||
<version>${datacap.maven.model.version}</version>
|
<version>${datacap.maven.model.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-compress</artifactId>
|
||||||
|
<version>${common-compress.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>${httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -7,7 +7,8 @@ public enum SpiType
|
|||||||
POM("Pom"),
|
POM("Pom"),
|
||||||
PROPERTIES("Properties"),
|
PROPERTIES("Properties"),
|
||||||
SPI("Spi"),
|
SPI("Spi"),
|
||||||
INJECT("Inject");
|
INJECT("Inject"),
|
||||||
|
TAR("Tar");
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@ -23,11 +23,14 @@ public class PluginClassLoader
|
|||||||
@Getter
|
@Getter
|
||||||
private final String pluginVersion;
|
private final String pluginVersion;
|
||||||
|
|
||||||
public PluginClassLoader(URL[] urls, ClassLoader parent, String pluginName, String pluginVersion)
|
private final boolean parentFirst;
|
||||||
|
|
||||||
|
public PluginClassLoader(URL[] urls, ClassLoader parent, String pluginName, String pluginVersion, boolean parentFirst)
|
||||||
{
|
{
|
||||||
super(urls, parent);
|
super(urls, parent);
|
||||||
this.pluginName = pluginName;
|
this.pluginName = pluginName;
|
||||||
this.pluginVersion = pluginVersion;
|
this.pluginVersion = pluginVersion;
|
||||||
|
this.parentFirst = parentFirst;
|
||||||
this.name = String.join("-", "loader", pluginName.toLowerCase(), pluginVersion.toLowerCase());
|
this.name = String.join("-", "loader", pluginName.toLowerCase(), pluginVersion.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +56,10 @@ public class PluginClassLoader
|
|||||||
if (name.startsWith("java.") ||
|
if (name.startsWith("java.") ||
|
||||||
name.startsWith("javax.") ||
|
name.startsWith("javax.") ||
|
||||||
name.startsWith("com.google.") ||
|
name.startsWith("com.google.") ||
|
||||||
name.startsWith("org.")) {
|
name.startsWith("org.") ||
|
||||||
|
// 添加 Plugin 相关的包到父优先加载列表
|
||||||
|
// Add Plugin related packages to parent-first list
|
||||||
|
(parentFirst && name.startsWith("io.edurt.datacap.plugin"))) {
|
||||||
return super.loadClass(name, resolve);
|
return super.loadClass(name, resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ public class PluginLoaderFactory
|
|||||||
registerLoader(new DirectoryPluginLoader());
|
registerLoader(new DirectoryPluginLoader());
|
||||||
registerLoader(new PomPluginLoader());
|
registerLoader(new PomPluginLoader());
|
||||||
registerLoader(new SpiPluginLoader());
|
registerLoader(new SpiPluginLoader());
|
||||||
|
registerLoader(new TarPluginLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,6 +51,18 @@ public class PluginLoaderFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除插件加载器
|
||||||
|
* Remove a plugin loader by type
|
||||||
|
*
|
||||||
|
* @param type 要移除的加载器类型
|
||||||
|
* the type of the loader to remove
|
||||||
|
*/
|
||||||
|
public static void unregisterLoader(String type)
|
||||||
|
{
|
||||||
|
loaderRegistry.remove(type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据类型获取加载器
|
* 根据类型获取加载器
|
||||||
* Get loader by type
|
* Get loader by type
|
||||||
|
@ -0,0 +1,308 @@
|
|||||||
|
package io.edurt.datacap.plugin.loader;
|
||||||
|
|
||||||
|
import io.edurt.datacap.plugin.Plugin;
|
||||||
|
import io.edurt.datacap.plugin.PluginContextManager;
|
||||||
|
import io.edurt.datacap.plugin.SpiType;
|
||||||
|
import io.edurt.datacap.plugin.utils.PluginClassLoaderUtils;
|
||||||
|
import io.edurt.datacap.plugin.utils.VersionUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.ssl.SSLContextBuilder;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tar 格式插件加载器,支持从本地文件系统或网络 URL 加载插件
|
||||||
|
* Tar format plugin loader, supports loading plugins from local filesystem or network URL
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TarPluginLoader
|
||||||
|
implements PluginLoader
|
||||||
|
{
|
||||||
|
// 临时目录前缀
|
||||||
|
// Temporary directory prefix
|
||||||
|
private static final String TEMP_DIR_PREFIX = "plugin_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加载器类型
|
||||||
|
* Get loader type
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SpiType getType()
|
||||||
|
{
|
||||||
|
return SpiType.TAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载插件,支持本地路径和网络 URL
|
||||||
|
* Load plugins, supports local path and network URL
|
||||||
|
*
|
||||||
|
* @param path 插件路径或 URL
|
||||||
|
* Plugin path or URL
|
||||||
|
* @return 加载的插件列表
|
||||||
|
* List of loaded plugins
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Plugin> load(Path path)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 如果是 URL 路径,先下载到本地
|
||||||
|
// If it's a URL path, download it first
|
||||||
|
if (path.toString().startsWith("http") || path.toString().startsWith("https")) {
|
||||||
|
path = downloadTarFile(path.toString().replace(":/", "://"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建临时解压目录
|
||||||
|
// Create temporary directory for extraction
|
||||||
|
Path extractDir = createTempDirectory();
|
||||||
|
|
||||||
|
// 解压 tar 文件
|
||||||
|
// Extract tar file
|
||||||
|
extractTarFile(path, extractDir);
|
||||||
|
|
||||||
|
// 从解压目录加载插件
|
||||||
|
// Load plugins from extracted directory
|
||||||
|
List<Plugin> plugins = loadPluginsFromDirectory(extractDir);
|
||||||
|
|
||||||
|
// 清理临时目录
|
||||||
|
// Cleanup temporary directory
|
||||||
|
cleanupTempDirectory(extractDir);
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("Failed to load plugins from tar file: {}", path, e);
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从解压目录加载插件
|
||||||
|
* Load plugins from extracted directory
|
||||||
|
*
|
||||||
|
* @param directory 插件目录
|
||||||
|
* Plugin directory
|
||||||
|
* @return 加载的插件列表
|
||||||
|
* List of loaded plugins
|
||||||
|
*/
|
||||||
|
private List<Plugin> loadPluginsFromDirectory(Path directory)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
List<Plugin> allPlugins = new ArrayList<>();
|
||||||
|
|
||||||
|
// 遍历目录查找插件
|
||||||
|
// Traverse directory to find plugins
|
||||||
|
try (Stream<Path> pathStream = Files.walk(directory)) {
|
||||||
|
pathStream.filter(Files::isDirectory)
|
||||||
|
.filter(this::isValidDirectory)
|
||||||
|
.forEach(pluginDir -> {
|
||||||
|
try {
|
||||||
|
// 获取插件名称和版本
|
||||||
|
// Get plugin name and version
|
||||||
|
String pluginName = pluginDir.getFileName().toString();
|
||||||
|
String version = VersionUtils.determinePluginVersion(pluginDir);
|
||||||
|
|
||||||
|
// 创建插件专用类加载器
|
||||||
|
// Create plugin-specific class loader
|
||||||
|
PluginClassLoader classLoader = PluginClassLoaderUtils.createClassLoader(
|
||||||
|
pluginDir,
|
||||||
|
pluginName,
|
||||||
|
version,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// 在插件类加载器上下文中加载插件
|
||||||
|
// Load plugins in plugin class loader context
|
||||||
|
List<Plugin> plugins = PluginContextManager.runWithClassLoader(classLoader, () -> {
|
||||||
|
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, classLoader);
|
||||||
|
return StreamSupport.stream(serviceLoader.spliterator(), false)
|
||||||
|
.filter(plugin -> {
|
||||||
|
// 检查插件类是否来自排除目录
|
||||||
|
// Check if the plugin class is from an excluded directory
|
||||||
|
String className = plugin.getClass().getName();
|
||||||
|
Path classPath = Path.of(className.replace('.', File.separatorChar) + ".class");
|
||||||
|
return !isExcludedPath(classPath);
|
||||||
|
})
|
||||||
|
.peek(plugin -> {
|
||||||
|
// 设置插件的类加载器
|
||||||
|
// Set class loader for plugins
|
||||||
|
plugin.setPluginClassLoader(classLoader);
|
||||||
|
log.debug("Loaded TAR plugin: {} (version: {})", plugin.getClass().getName(), version);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
allPlugins.addAll(plugins);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("Failed to load plugins from directory: {}", pluginDir, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 URL 下载 tar 文件到临时文件
|
||||||
|
* Download tar file from URL to temporary file
|
||||||
|
*
|
||||||
|
* @param url tar 文件的 URL
|
||||||
|
* URL of the tar file
|
||||||
|
* @return 临时文件路径
|
||||||
|
* Path to temporary file
|
||||||
|
*/
|
||||||
|
private Path downloadTarFile(String url)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
log.info("Downloading tar file from URL: {}", url);
|
||||||
|
Path tempFile = Files.createTempFile(TEMP_DIR_PREFIX, ".tar");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建信任所有证书的 SSL 上下文
|
||||||
|
// Create SSL context that trusts all certificates
|
||||||
|
SSLContext sslContext = SSLContextBuilder.create()
|
||||||
|
.loadTrustMaterial(null, (cert, authType) -> true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 创建允许所有主机名的验证器
|
||||||
|
// Create verifier that allows all hostnames
|
||||||
|
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(
|
||||||
|
sslContext,
|
||||||
|
NoopHostnameVerifier.INSTANCE
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建 HttpClient
|
||||||
|
// Create HttpClient
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.custom()
|
||||||
|
.setSSLSocketFactory(sslFactory)
|
||||||
|
.build()) {
|
||||||
|
|
||||||
|
// 创建 GET 请求
|
||||||
|
// Create GET request
|
||||||
|
HttpGet request = new HttpGet(url);
|
||||||
|
request.setConfig(RequestConfig.custom()
|
||||||
|
.setConnectTimeout(30000)
|
||||||
|
.setSocketTimeout(30000)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// 执行请求并下载文件
|
||||||
|
// Execute request and download file
|
||||||
|
try (CloseableHttpResponse response = httpClient.execute(request);
|
||||||
|
InputStream in = response.getEntity().getContent();
|
||||||
|
OutputStream out = Files.newOutputStream(tempFile)) {
|
||||||
|
|
||||||
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
|
if (statusCode != 200) {
|
||||||
|
throw new IOException("HTTP request failed with status: " + statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
}
|
||||||
|
catch (IOException deleteError) {
|
||||||
|
log.warn("Failed to delete temporary file after download failure: {}", tempFile, deleteError);
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to download file from URL: " + url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建临时目录
|
||||||
|
* Create temporary directory
|
||||||
|
*/
|
||||||
|
private Path createTempDirectory()
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return Files.createTempDirectory(TEMP_DIR_PREFIX + UUID.randomUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解压 tar 文件到指定目录
|
||||||
|
* Extract tar file to specified directory
|
||||||
|
*
|
||||||
|
* @param tarFile tar 文件路径
|
||||||
|
* Path to tar file
|
||||||
|
* @param destDir 目标目录
|
||||||
|
* Destination directory
|
||||||
|
*/
|
||||||
|
private void extractTarFile(Path tarFile, Path destDir)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
log.info("Extracting tar file to: {}", destDir);
|
||||||
|
|
||||||
|
try (InputStream fileIn = Files.newInputStream(tarFile);
|
||||||
|
BufferedInputStream buffIn = new BufferedInputStream(fileIn);
|
||||||
|
GZIPInputStream gzipIn = new GZIPInputStream(buffIn);
|
||||||
|
TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn)) {
|
||||||
|
|
||||||
|
TarArchiveEntry entry;
|
||||||
|
while ((entry = tarIn.getNextEntry()) != null) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path outPath = destDir.resolve(entry.getName());
|
||||||
|
Files.createDirectories(outPath.getParent());
|
||||||
|
|
||||||
|
try (OutputStream out = Files.newOutputStream(outPath)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = tarIn.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理临时目录
|
||||||
|
* Clean up temporary directory
|
||||||
|
*/
|
||||||
|
private void cleanupTempDirectory(Path directory)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
FileUtils.deleteDirectory(directory.toFile());
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.warn("Failed to cleanup temporary directory: {}", directory, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件类加载器工具类
|
* 插件类加载器工具类
|
||||||
@ -16,35 +16,44 @@ import java.util.Set;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PluginClassLoaderUtils
|
public class PluginClassLoaderUtils
|
||||||
{
|
{
|
||||||
|
public static PluginClassLoader createClassLoader(Path directory, String pluginName, String pluginVersion)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
return createClassLoader(directory, pluginName, pluginVersion, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个新的插件类加载器
|
* 创建一个新的插件类加载器
|
||||||
* Create a new plugin class loader
|
* Create a new plugin class loader
|
||||||
*
|
*
|
||||||
* @param directory 插件目录
|
* @param directory 插件目录
|
||||||
* @param directory Plugin directory
|
* Plugin directory
|
||||||
* @param pluginName 插件名称
|
* @param pluginName 插件名称
|
||||||
* @param pluginName Plugin name
|
* Plugin name
|
||||||
* @param pluginVersion 插件版本
|
* @param pluginVersion 插件版本
|
||||||
* @param pluginVersion Plugin version
|
* Plugin version
|
||||||
|
* @param parentFirst 是否先加载父类加载器
|
||||||
|
* Whether to load the parent class loader first
|
||||||
* @return 新创建的类加载器
|
* @return 新创建的类加载器
|
||||||
* @return The newly created class loader
|
* The newly created class loader
|
||||||
* @throws Exception 创建类加载器时发生异常
|
* @throws Exception 创建类加载器时发生异常
|
||||||
* @throws Exception Exception occurred when creating the class loader
|
* Exception occurred when creating the class loader
|
||||||
*/
|
*/
|
||||||
public static PluginClassLoader createClassLoader(Path directory, String pluginName, String pluginVersion)
|
public static PluginClassLoader createClassLoader(Path directory, String pluginName, String pluginVersion, boolean parentFirst)
|
||||||
throws Exception
|
throws Exception
|
||||||
{
|
{
|
||||||
log.debug("Creating new class loader for plugin: {} version: {} directory: {}",
|
log.debug("Creating new class loader for plugin: {} version: {} directory: {}",
|
||||||
pluginName, pluginVersion, directory);
|
pluginName, pluginVersion, directory);
|
||||||
|
|
||||||
Set<URL> urls = new HashSet<>();
|
LinkedHashSet<URL> urls = new LinkedHashSet<>();
|
||||||
|
|
||||||
if (Files.isDirectory(directory)) {
|
if (Files.isDirectory(directory)) {
|
||||||
// 添加主插件JAR
|
// 添加主插件JAR
|
||||||
// Add the main plugin JAR
|
// Add the main plugin JAR
|
||||||
Files.walk(directory)
|
try (Stream<Path> pathStream = Files.walk(directory)) {
|
||||||
.filter(path -> path.toString().endsWith(".jar"))
|
pathStream.filter(path -> path.toString().endsWith(".jar"))
|
||||||
.forEach(path -> addJarAndDependencies(path, urls));
|
.forEach(path -> addJarAndDependencies(path, urls));
|
||||||
|
}
|
||||||
|
|
||||||
// 检查常见的依赖目录
|
// 检查常见的依赖目录
|
||||||
// Check common dependency directories
|
// Check common dependency directories
|
||||||
@ -62,7 +71,8 @@ public class PluginClassLoaderUtils
|
|||||||
urls.toArray(new URL[0]),
|
urls.toArray(new URL[0]),
|
||||||
systemClassLoader,
|
systemClassLoader,
|
||||||
pluginName,
|
pluginName,
|
||||||
pluginVersion
|
pluginVersion,
|
||||||
|
parentFirst
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,23 +83,24 @@ public class PluginClassLoaderUtils
|
|||||||
* @param dir 依赖目录 Dependency directory
|
* @param dir 依赖目录 Dependency directory
|
||||||
* @param urls URL集合 URL set
|
* @param urls URL集合 URL set
|
||||||
*/
|
*/
|
||||||
private static void addDependenciesFromDir(Path dir, Set<URL> urls)
|
private static void addDependenciesFromDir(Path dir, LinkedHashSet<URL> urls)
|
||||||
{
|
{
|
||||||
if (Files.isDirectory(dir)) {
|
if (Files.isDirectory(dir)) {
|
||||||
try {
|
try {
|
||||||
log.debug("Scanning dependency directory: {}", dir);
|
log.debug("Scanning dependency directory: {}", dir);
|
||||||
Files.walk(dir)
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
.filter(path -> path.toString().endsWith(".jar"))
|
pathStream.filter(path -> path.toString().endsWith(".jar"))
|
||||||
.forEach(path -> {
|
.forEach(path -> {
|
||||||
try {
|
try {
|
||||||
urls.add(path.toUri().toURL());
|
urls.add(path.toUri().toURL());
|
||||||
log.debug("Added dependency: {}", path);
|
log.debug("Added dependency: {}", path);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
log.error("Failed to add dependency: {}", path, e);
|
log.error("Failed to add dependency: {}", path, e);
|
||||||
throw new RuntimeException("Failed to add dependency: " + path, e);
|
throw new RuntimeException("Failed to add dependency: " + path, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
log.error("Failed to scan dependency directory: {}", dir, e);
|
log.error("Failed to scan dependency directory: {}", dir, e);
|
||||||
@ -105,7 +116,7 @@ public class PluginClassLoaderUtils
|
|||||||
* @param jarPath JAR文件路径 JAR file path
|
* @param jarPath JAR文件路径 JAR file path
|
||||||
* @param urls URL集合 URL set
|
* @param urls URL集合 URL set
|
||||||
*/
|
*/
|
||||||
private static void addJarAndDependencies(Path jarPath, Set<URL> urls)
|
private static void addJarAndDependencies(Path jarPath, LinkedHashSet<URL> urls)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
urls.add(jarPath.toUri().toURL());
|
urls.add(jarPath.toUri().toURL());
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package io.edurt.datacap.plugin;
|
|
||||||
|
|
||||||
import io.edurt.datacap.plugin.utils.PluginPathUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class PluginManagerTest
|
|
||||||
{
|
|
||||||
private PluginManager pluginManager;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void before()
|
|
||||||
{
|
|
||||||
Path projectRoot = PluginPathUtils.findProjectRoot();
|
|
||||||
PluginConfigure config = PluginConfigure.builder()
|
|
||||||
.pluginsDir(projectRoot.resolve("test/datacap-test-plugin"))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
pluginManager = new PluginManager(config);
|
|
||||||
pluginManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadPlugin()
|
|
||||||
{
|
|
||||||
pluginManager.getPluginInfos().forEach(info -> log.info("{}", info));
|
|
||||||
|
|
||||||
Assert.assertFalse(pluginManager.getPlugin("Local").isEmpty());
|
|
||||||
}
|
|
||||||
}
|
|
10
logo/convert/csv.svg
Normal file
10
logo/convert/csv.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<text x="50" y="60"
|
||||||
|
font-family="Arial, sans-serif"
|
||||||
|
font-size="30"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="middle"
|
||||||
|
fill="#000000">
|
||||||
|
CSV
|
||||||
|
</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 271 B |
16
logo/convert/json.svg
Normal file
16
logo/convert/json.svg
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="A" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" />
|
||||||
|
<stop offset="1" stop-color="#fff" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient x1="23.664" y1="23.664" x2="136.38" y2="136.38" id="B" xlink:href="#A" />
|
||||||
|
<linearGradient x1="136.38" y1="136.38" x2="23.664" y2="23.664" id="C" xlink:href="#A" />
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(11.423 -391.912)">
|
||||||
|
<g transform="matrix(.2 0 0 .2 -11.422664 391.91163)" fill-rule="evenodd">
|
||||||
|
<path d="M79.865 119.1c35.398 48.255 70.04-13.47 69.99-50.587C149.793 24.627 105.312.1 79.836.1 38.943.1 0 33.895 0 80.135 0 131.53 44.64 160 79.836 160c-7.964-1.147-34.506-6.834-34.863-67.967-.24-41.347 13.488-57.866 34.805-50.6.477.177 23.514 9.265 23.514 38.95 0 29.56-23.427 38.715-23.427 38.715z" fill="url(#B)" />
|
||||||
|
<path d="M79.823 41.4C56.433 33.34 27.78 52.617 27.78 91.23c0 63.048 46.72 68.77 52.384 68.77C121.057 160 160 126.204 160 79.964 160 28.568 115.36.1 80.164.1c9.748-1.35 52.54 10.55 52.54 69.037 0 38.14-31.953 58.905-52.735 50.033-.477-.177-23.514-9.265-23.514-38.95 0-29.56 23.367-38.818 23.367-38.818z" fill="url(#C)" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
10
logo/convert/none.svg
Normal file
10
logo/convert/none.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<text x="50" y="60"
|
||||||
|
font-family="Arial, sans-serif"
|
||||||
|
font-size="30"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="middle"
|
||||||
|
fill="#000000">
|
||||||
|
None
|
||||||
|
</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 272 B |
3
logo/convert/xml.svg
Normal file
3
logo/convert/xml.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64">
|
||||||
|
<path d="M7.208 31.416L.643 22.933H7.85l3.017 4.927 2.96-4.927h6.817l-6.398 8.533 7.124 9.6h-7.403l-3.408-5.41-3.52 5.41H0zm15.326-8.483h8.744l2.85 10.667h.056l2.85-10.667h8.744v18.134h-5.81V29.435h-.056l-3.464 11.632h-4.582L28.4 29.435h-.056v11.632h-5.81zm26.492 0h6.146V36.42H64v4.648H49.026z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 374 B |
1
pom.xml
1
pom.xml
@ -103,6 +103,7 @@
|
|||||||
<module>convert/datacap-convert-xml</module>
|
<module>convert/datacap-convert-xml</module>
|
||||||
<module>test/datacap-test-plugin</module>
|
<module>test/datacap-test-plugin</module>
|
||||||
<module>test/datacap-test-convert</module>
|
<module>test/datacap-test-convert</module>
|
||||||
|
<module>test/datacap-test-core</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<name>datacap</name>
|
<name>datacap</name>
|
||||||
|
58
test/datacap-test-core/pom.xml
Normal file
58
test/datacap-test-core/pom.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>io.edurt.datacap</groupId>
|
||||||
|
<artifactId>datacap</artifactId>
|
||||||
|
<version>2024.4.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>datacap-test-core</artifactId>
|
||||||
|
<description>DataCap - Test - Core</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-reflect</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.edurt.datacap</groupId>
|
||||||
|
<artifactId>datacap-plugin</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,21 @@
|
|||||||
|
package io.edurt.datacap.test.plugin
|
||||||
|
|
||||||
|
import io.edurt.datacap.plugin.Plugin
|
||||||
|
import io.edurt.datacap.plugin.loader.TarPluginLoader
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class TarPluginLoaderTest
|
||||||
|
{
|
||||||
|
private val tarUrl = "https://cdn.north.devlive.org/applications/datacap/plugins/2024.4.0-SNAPSHOT/convert/datacap-convert-txt-bin.tar.gz"
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test()
|
||||||
|
{
|
||||||
|
val loader = TarPluginLoader()
|
||||||
|
|
||||||
|
val plugins: List<Plugin> = loader.load(Path.of(tarUrl))
|
||||||
|
assertTrue(plugins.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user