feat(plugin): adapter plugin -- influxdb (#876)
Some checks are pending
Before checker with maven / before_checker_loading (push) Waiting to run
Before checker with maven / before_checker_ui (push) Blocked by required conditions
Before checker with maven / before_checker_style (push) Blocked by required conditions
Before checker with maven / before_checker_bugs (push) Blocked by required conditions
Before checker with maven / before_checker_test (push) Blocked by required conditions
Before checker with maven / before_checker_package (push) Blocked by required conditions
Analysis java code / Analyze (java) (push) Waiting to run

This commit is contained in:
qianmoQ 2024-11-28 11:51:03 +08:00 committed by GitHub
commit 7f92adb448
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 486 additions and 305 deletions

View File

@ -1,5 +1,6 @@
datacap-plugin-mysql=plugin/datacap-plugin-mysql/pom.xml
datacap-plugin-clickhouse=plugin/datacap-plugin-clickhouse/pom.xml
datacap-plugin-influxdb=plugin/datacap-plugin-influxdb/pom.xml
datacap-convert-txt=convert/datacap-convert-txt/pom.xml
datacap-convert-json=convert/datacap-convert-json/pom.xml
datacap-convert-xml=convert/datacap-convert-xml/pom.xml

View File

@ -35,6 +35,21 @@
],
"url": "https://cdn.north.devlive.org/applications/datacap/plugins/2024.4.0-SNAPSHOT/plugin/datacap-plugin-clickhouse-bin.tar.gz"
},
{
"key": "datacap-plugin-influxdb",
"label": "InfluxDB",
"description": "InfluxDB is a time series database that stores and retrieves data points. It is a distributed database with a focus on performance, scalability, and ease of use.",
"i18nFormat": true,
"type": "Connector",
"version": "2024.4.0-SNAPSHOT",
"author": "datacap-community",
"logo": "https://cdn.north.devlive.org/applications/datacap/resources/logo/plugin/influxdb.svg",
"released": "2024-11-21 23:11:15",
"supportVersion": [
"ALL"
],
"url": "https://cdn.north.devlive.org/applications/datacap/plugins/2024.4.0-SNAPSHOT/plugin/datacap-plugin-influxdb-bin.tar.gz"
},
{
"key": "datacap-convert-csv",
"label": "CsvConvert",

View File

@ -7,6 +7,7 @@ import io.edurt.datacap.plugin.utils.PluginPathUtils;
import lombok.extern.slf4j.Slf4j;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@ -81,7 +82,7 @@ public class PropertiesPluginLoader
}
}
}
catch (Exception e) {
catch (IOException e) {
log.error("Failed to load plugins from properties file: {}", path, e);
}
return plugins;

View File

@ -93,31 +93,37 @@ public class TarPluginLoader
public List<Plugin> load(Path path, Path targetDir, Set<String> parentClassLoaderPackages)
{
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(":/", "://"));
if (isValidTarPath(path)) {
// 如果是 URL 路径先下载到本地
// If it's a URL path, download it first
if (path.toString().startsWith("http") || path.toString().startsWith("https")) {
path = downloadTarFile(path.toString().replace(":/", "://"));
}
if (isValidTarFile(path)) {
// 使用指定的目录或创建临时目录
// Use specified directory or create temporary directory
Path extractDir = targetDir != null ? targetDir : createTempDirectory();
// 解压 tar 文件
// Extract tar file
extractTarFile(path, extractDir);
// 从解压目录加载插件
// Load plugins from extracted directory
List<Plugin> plugins = loadPluginsFromDirectory(extractDir, parentClassLoaderPackages);
// 如果使用的是临时目录则清理
// Clean up if using temporary directory
if (targetDir == null) {
cleanupTempDirectory(extractDir);
}
return plugins;
}
}
// 使用指定的目录或创建临时目录
// Use specified directory or create temporary directory
Path extractDir = targetDir != null ? targetDir : createTempDirectory();
// 解压 tar 文件
// Extract tar file
extractTarFile(path, extractDir);
// 从解压目录加载插件
// Load plugins from extracted directory
List<Plugin> plugins = loadPluginsFromDirectory(extractDir, parentClassLoaderPackages);
// 如果使用的是临时目录则清理
// Clean up if using temporary directory
if (targetDir == null) {
cleanupTempDirectory(extractDir);
}
return plugins;
return List.of();
}
catch (Exception e) {
log.error("Failed to load plugins from tar file: {}", path, e);
@ -327,4 +333,39 @@ public class TarPluginLoader
log.warn("Failed to cleanup temporary directory: {}", directory, e);
}
}
/**
* 检查是否为合法 Tar 文件路径
* Check if it's a valid Tar file path
*
* @param path file path
* @return true if it's a valid Tar file
*/
private boolean isValidTarPath(Path path)
{
String pathStr = path.toString();
return (pathStr.startsWith("http") || pathStr.startsWith("https"))
&& (pathStr.endsWith("tar.gz") || pathStr.endsWith("tar") || pathStr.endsWith("tgz"));
}
/**
* 检查是否合法的 Tar 文件通过解压文件头检查
* Check if it's a valid Tar file, by checking the file header
*
* @param path file path
* @return true if it's a valid Tar file
*/
private boolean isValidTarFile(Path path)
{
try (InputStream fileIn = Files.newInputStream(path);
BufferedInputStream buffIn = new BufferedInputStream(fileIn);
GZIPInputStream gzipIn = new GZIPInputStream(buffIn)) {
log.debug("Validating tar file: {}", path);
log.debug("Tar file header: {}", gzipIn.readNBytes(512));
return true;
}
catch (IOException e) {
return false;
}
}
}

View File

@ -421,12 +421,6 @@
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-influxdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-captcha</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- </dependency>-->

View File

@ -6,14 +6,17 @@ import io.edurt.datacap.spi.adapter.HttpAdapter;
import io.edurt.datacap.spi.adapter.JdbcAdapter;
import io.edurt.datacap.spi.adapter.NativeAdapter;
import io.edurt.datacap.spi.connection.Connection;
import io.edurt.datacap.spi.connection.JdbcConfigure;
import io.edurt.datacap.spi.connection.JdbcConnection;
import io.edurt.datacap.spi.model.Configure;
import io.edurt.datacap.spi.model.Response;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public interface PluginService
extends Service
{
@ -54,23 +57,64 @@ public interface PluginService
{
Response response = new Response();
try {
JdbcConfigure jdbcConfigure = new JdbcConfigure();
if (jdbcConfigure.getJdbcDriver() == null) {
jdbcConfigure.setJdbcDriver(this.driver());
}
if (jdbcConfigure.getJdbcType() == null) {
jdbcConfigure.setJdbcType(this.connectType());
}
BeanUtils.copyProperties(jdbcConfigure, configure);
local.set(new JdbcConnection(jdbcConfigure, response));
configure.setDriver(this.driver());
configure.setType(this.connectType());
configure.setUrl(Optional.of(url(configure)));
local.set(new JdbcConnection(configure, response));
}
catch (Exception ex) {
response.setIsConnected(Boolean.FALSE);
response.setMessage(ex.getMessage());
log.error("Error connecting : {}", ex);
log.error("Error connecting :", ex);
}
}
/**
* 构建驱动路径
* Build the driver path
*
* @param configure 配置信息 | Configuration information
* @return 驱动路径 | Driver path
*/
default String url(Configure configure)
{
if (configure.getUrl().isPresent()) {
return configure.getUrl().get();
}
StringBuilder buffer = new StringBuilder();
buffer.append("jdbc:");
buffer.append(configure.getType());
buffer.append("://");
buffer.append(configure.getHost());
buffer.append(":");
buffer.append(configure.getPort());
if (configure.getDatabase().isPresent()) {
buffer.append("/");
buffer.append(configure.getDatabase().get());
}
if (configure.getSsl().isPresent()) {
buffer.append(String.format("?ssl=%s", configure.getSsl().get()));
}
if (configure.getEnv().isPresent()) {
Map<String, Object> env = configure.getEnv().get();
List<String> flatEnv = env.entrySet()
.stream()
.map(value -> String.format("%s=%s", value.getKey(), value.getValue()))
.collect(Collectors.toList());
if (configure.getSsl().isEmpty()) {
buffer.append("?");
}
else {
if (configure.getIsAppendChar()) {
buffer.append("&");
}
}
buffer.append(String.join("&", flatEnv));
}
return buffer.toString();
}
@Deprecated
default Response execute(String content)
{

View File

@ -3,8 +3,8 @@ package io.edurt.datacap.spi.adapter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.spi.column.Column;
import io.edurt.datacap.spi.column.JdbcColumn;
import io.edurt.datacap.spi.connection.JdbcConfigure;
import io.edurt.datacap.spi.connection.JdbcConnection;
import io.edurt.datacap.spi.model.Configure;
import io.edurt.datacap.spi.model.Response;
import io.edurt.datacap.spi.model.Time;
import lombok.extern.slf4j.Slf4j;
@ -39,7 +39,7 @@ public class JdbcAdapter
processorTime.setStart(new Date().getTime());
Response response = jdbcConnection.getResponse();
Connection connection = (Connection) jdbcConnection.getConnection();
JdbcConfigure configure = (JdbcConfigure) jdbcConnection.getConfigure();
Configure configure = jdbcConnection.getConfigure();
if (response.getIsConnected()) {
try (Statement statement = connection.createStatement()) {
List<String> headers = new ArrayList<>();

View File

@ -25,8 +25,6 @@ public abstract class Connection
this.response.setConnection(connectionTime);
}
protected abstract String formatJdbcUrl();
protected abstract java.sql.Connection openConnection();
public Object getConnection()

View File

@ -1,20 +0,0 @@
package io.edurt.datacap.spi.connection;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.spi.model.Configure;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@ToString
@EqualsAndHashCode
@SuppressFBWarnings(value = {"EQ_OVERRIDING_EQUALS_NOT_SYMMETRIC"},
justification = "I prefer to suppress these FindBugs warnings")
public class JdbcConfigure
extends Configure
{
private String jdbcDriver;
private String jdbcType;
private Boolean isAppendChar = Boolean.TRUE;
}

View File

@ -2,6 +2,7 @@ package io.edurt.datacap.spi.connection;
import io.edurt.datacap.plugin.PluginContextManager;
import io.edurt.datacap.plugin.loader.PluginClassLoader;
import io.edurt.datacap.spi.model.Configure;
import io.edurt.datacap.spi.model.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
@ -11,11 +12,8 @@ import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@Slf4j
public class JdbcConnection
@ -23,88 +21,41 @@ public class JdbcConnection
{
private java.sql.Connection jdbcConnection;
public JdbcConnection(JdbcConfigure configure, Response response)
public JdbcConnection(Configure configure, Response response)
{
super(configure, response);
}
protected String formatJdbcUrl()
{
JdbcConfigure jdbcConfigure = getJdbcConfigure();
StringBuilder buffer = new StringBuilder();
buffer.append("jdbc:");
buffer.append(jdbcConfigure.getJdbcType());
if (jdbcConfigure.getJdbcType().equals("influxdb")) {
buffer.append(":");
}
else {
buffer.append("://");
}
buffer.append(jdbcConfigure.getHost());
buffer.append(":");
buffer.append(jdbcConfigure.getPort());
if (jdbcConfigure.getDatabase().isPresent()) {
if (jdbcConfigure.getJdbcType().equals("solr")) {
buffer.append("/?collection=");
}
else {
buffer.append("/");
}
buffer.append(jdbcConfigure.getDatabase().get());
}
if (jdbcConfigure.getSsl().isPresent()) {
buffer.append(String.format("?ssl=%s", jdbcConfigure.getSsl().get()));
}
if (jdbcConfigure.getEnv().isPresent()) {
Map<String, Object> env = jdbcConfigure.getEnv().get();
List<String> flatEnv = env.entrySet()
.stream()
.map(value -> String.format("%s=%s", value.getKey(), value.getValue()))
.collect(Collectors.toList());
if (jdbcConfigure.getSsl().isEmpty()) {
buffer.append("?");
}
else {
if (jdbcConfigure.getIsAppendChar()) {
buffer.append("&");
}
}
buffer.append(String.join("&", flatEnv));
}
return buffer.toString();
}
protected JdbcConfigure getJdbcConfigure()
{
return (JdbcConfigure) this.configure;
}
protected java.sql.Connection openConnection()
{
JdbcConfigure jdbcConfigure = getJdbcConfigure();
try {
PluginClassLoader pluginClassLoader = jdbcConfigure.getPlugin().getPluginClassLoader();
PluginClassLoader pluginClassLoader = configure.getPlugin().getPluginClassLoader();
PluginContextManager.runWithClassLoader(pluginClassLoader, () -> {
Class<?> driverClass = Class.forName(jdbcConfigure.getJdbcDriver(), true, pluginClassLoader);
Class<?> driverClass = Class.forName(configure.getDriver(), true, pluginClassLoader);
Driver driver = (Driver) driverClass.getDeclaredConstructor().newInstance();
DriverManager.registerDriver(new DriverShim(driver));
String url = formatJdbcUrl();
log.info("Connection driver {}", jdbcConfigure.getJdbcDriver());
if (configure.getUrl().isEmpty()) {
throw new RuntimeException("Connection url not present");
}
String url = configure.getUrl().get();
log.info("Connection driver {}", configure.getType());
log.info("Connection url {}", url);
if (jdbcConfigure.getUsername().isPresent() && jdbcConfigure.getPassword().isPresent()) {
if (configure.getUsername().isPresent() && configure.getPassword().isPresent()) {
log.info("Connection username with {} password with {}",
jdbcConfigure.getUsername().get(), "***");
this.jdbcConnection = DriverManager.getConnection(url,
jdbcConfigure.getUsername().get(),
jdbcConfigure.getPassword().get());
configure.getUsername().get(), "***");
this.jdbcConnection = DriverManager.getConnection(
url,
configure.getUsername().get(),
configure.getPassword().get()
);
}
else {
log.info("Connection username and password not present");
Properties properties = new Properties();
if (jdbcConfigure.getUsername().isPresent()) {
properties.put("user", jdbcConfigure.getUsername().get());
if (configure.getUsername().isPresent()) {
properties.put("user", configure.getUsername().get());
}
this.jdbcConnection = DriverManager.getConnection(url, properties);
}

View File

@ -27,12 +27,26 @@ public class Configure
private PluginManager pluginManager;
private String host;
private Integer port;
@Builder.Default
private Optional<String> username = Optional.empty();
@Builder.Default
private Optional<String> password = Optional.empty();
@Builder.Default
private Optional<String> database = Optional.empty();
@Builder.Default
private Optional<String> version = Optional.empty();
@Builder.Default
private Optional<Map<String, Object>> env = Optional.empty();
@Builder.Default
private Optional<Boolean> ssl = Optional.empty();
@Builder.Default
private String format = "JsonConvert";
// if `to`: skip
private Optional<String> query = Optional.empty();
@ -40,4 +54,15 @@ public class Configure
private String home;
private boolean usedConfig;
private String id;
// 自定义 url
// Custom url
@Builder.Default
private Optional<String> url = Optional.empty();
private String driver;
private String type;
@Builder.Default
private Boolean isAppendChar = Boolean.TRUE;
}

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -15,7 +15,7 @@
:key="plugin.name"
:value="plugin.name">
<ShadcnTooltip :content="plugin.name" class="p-1">
<img class="h-16 w-16 object-contain" :src="'/static/images/plugin/' + plugin.name + '.png'" :alt="plugin.name">
<img class="h-16 w-16 object-contain" :src="'/static/images/plugin/' + plugin.name.toLowerCase() + '.png'" :alt="plugin.name">
</ShadcnTooltip>
</ShadcnToggle>
</ShadcnToggleGroup>

View File

@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<!-- 背景圆 -->
<!-- Background circle -->
<circle cx="100" cy="100" r="90" fill="#f0f8ff" />
<circle cx="100" cy="100" r="100" fill="#f0f8ff" />
<!-- 外部圆环 -->
<!-- Outer ring -->
<circle cx="100" cy="100" r="80" fill="none" stroke="#1E90FF" stroke-width="8" />
<circle cx="100" cy="100" r="96" fill="none" stroke="#1E90FF" stroke-width="8" />
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg version="1.1"

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 290 KiB

4
logo/plugin/influxdb.svg Normal file
View File

@ -0,0 +1,4 @@
<svg enable-background="new -173 -143 900 900" height="900" viewBox="-173 -143 900 900" width="900" xmlns="http://www.w3.org/2000/svg">
<path d="m-173-143h900v900h-900z" fill="none" />
<path d="m694.1 394.9-81-352.7c-4.6-19.3-22.1-38.6-41.4-44.2l-370.2-114.2c-4.6-1.8-10.1-1.8-15.7-1.8-15.7 0-32.2 6.4-43.3 15.7l-265.2 246.8c-14.7 12.9-22.1 38.7-17.5 57.1l86.6 377.6c4.6 19.3 22.1 38.7 41.4 44.2l346.2 106.8c4.6 1.8 10.1 1.8 15.7 1.8 15.7 0 32.2-6.4 43.3-15.7l283.6-263.3c14.8-13.8 22.1-38.7 17.5-58.1zm-453.9-427.3 254.1 78.3c10.1 2.8 10.1 7.4 0 10.1l-133.5 30.4c-10.1 2.8-23.9-1.8-31.3-9.2l-93-100.4c-8.3-8.2-6.5-11.9 3.7-9.2zm158.3 455.9c2.8 10.1-3.7 15.7-13.8 12.9l-274.4-84.7c-10.1-2.8-12-11.1-4.6-18.4l210-195.3c7.4-7.4 15.7-4.6 18.4 5.5zm-452.1-248.7 222.9-207.2c7.4-7.4 19.3-6.4 26.7.9l111.4 120.7c7.4 7.4 6.4 19.3-.9 26.7l-222.9 207.2c-7.4 7.4-19.3 6.4-26.7-.9l-111.4-120.6c-7.4-8.3-6.4-20.3.9-26.8zm54.4 328.8-58.9-258.8c-2.8-10.1 1.8-12 8.3-4.6l93 100.4c7.4 7.4 10.1 22.1 7.4 32.2l-40.6 130.8c-2.8 10.1-7.4 10.1-9.2 0zm325.9 151-291-89.3c-10.1-2.8-15.7-13.8-12.9-23.9l48.8-156.6c2.8-10.1 13.8-15.7 23.9-12.9l291 89.3c10.1 2.8 15.7 13.8 12.9 23.9l-48.8 156.6c-3.6 10.2-13.7 15.7-23.9 12.9zm257.8-211.8-194.2 180.5c-7.4 7.4-11 4.6-8.3-5.5l40.5-130.8c2.8-10.1 13.8-20.3 23.9-22.1l133.5-30.4c10.2-2.7 12 1.9 4.6 8.3zm21.2-38.6-160.2 36.8c-10.1 2.8-20.3-3.7-23-13.8l-68.1-296.5c-2.8-10.1 3.7-20.3 13.8-23l160.2-36.8c10.1-2.8 20.3 3.7 23 13.8l68.1 296.5c2.8 11-3.6 21.1-13.8 23z" fill="#22adf6" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -11,11 +11,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>datacap-plugin-influxdb</artifactId>
<description>DataCap - InfluxDB</description>
<properties>
<plugin.name>datacap-influxdb</plugin.name>
</properties>
<description>DataCap - Plugin - InfluxDB</description>
<dependencies>
<dependency>
@ -32,11 +28,6 @@
<artifactId>kotlin-reflect</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.suteren.jdbc.influxdb</groupId>
<artifactId>influxdb-jdbc</artifactId>
@ -70,7 +61,24 @@
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
<excludeScope>provided</excludeScope>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,21 +0,0 @@
package io.edurt.datacap.plugin.influxdb
import com.google.inject.multibindings.Multibinder
import io.edurt.datacap.spi.AbstractPluginModule
import io.edurt.datacap.spi.PluginService
import io.edurt.datacap.spi.PluginModule
class InfluxDBModule : AbstractPluginModule(), PluginModule
{
override fun get(): AbstractPluginModule
{
return this
}
override fun configure()
{
Multibinder.newSetBinder(binder(), _root_ide_package_.io.edurt.datacap.spi.PluginService::class.java)
.addBinding()
.to(InfluxDBPlugin::class.java)
}
}

View File

@ -1,21 +0,0 @@
package io.edurt.datacap.plugin.influxdb
import io.edurt.datacap.spi.PluginService
class InfluxDBPlugin : _root_ide_package_.io.edurt.datacap.spi.PluginService
{
override fun validator(): String
{
return "SELECT 1"
}
override fun driver(): String
{
return "net.suteren.jdbc.influxdb.InfluxDbDriver"
}
override fun connectType(): String
{
return "influxdb"
}
}

View File

@ -0,0 +1,5 @@
package io.edurt.datacap.plugin.influxdb
import io.edurt.datacap.plugin.Plugin
class InfluxdbPlugin : Plugin()

View File

@ -0,0 +1,79 @@
package io.edurt.datacap.plugin.influxdb
import io.edurt.datacap.spi.PluginService
import io.edurt.datacap.spi.model.Configure
class InfluxdbService : PluginService
{
override fun validator(): String
{
return "SELECT 1"
}
override fun driver(): String
{
return "net.suteren.jdbc.influxdb.InfluxDbDriver"
}
override fun connectType(): String
{
return "influxdb"
}
override fun url(configure: Configure): String
{
// 如果已经有自定义 URL直接使用
// If there is a custom URL, use it
if (configure.url.isPresent)
{
return configure.url.get()
}
val builder = StringBuilder("jdbc:${configure.type}:")
// 添加主机和端口
// Add host and port
builder.append(configure.host)
if (configure.port != null)
{
builder.append(":").append(configure.port)
}
builder.append("/")
// 添加参数
// Add parameters
val params = mutableListOf<String>()
// 添加数据库
// Add database
configure.database.ifPresent { db ->
params.add("db=$db")
}
// 添加用户名和密码
// Add username and password
configure.username.ifPresent { username ->
params.add("u=$username")
}
configure.password.ifPresent { password ->
params.add("p=$password")
}
// 添加其他环境参数
// Add other environment parameters
configure.env.ifPresent { envMap ->
envMap.forEach { (key, value) ->
params.add("$key=$value")
}
}
// 将所有参数添加到 URL
// Add all parameters to the URL
if (params.isNotEmpty())
{
builder.append("?").append(params.joinToString("&"))
}
return builder.toString()
}
}

View File

@ -0,0 +1 @@
io.edurt.datacap.plugin.influxdb.InfluxdbPlugin

View File

@ -0,0 +1 @@
io.edurt.datacap.plugin.influxdb.InfluxdbService

View File

@ -1 +0,0 @@
io.edurt.datacap.plugin.influxdb.InfluxDBModule

View File

@ -1,32 +0,0 @@
package io.edurt.datacap.plugin.influxdb
import com.google.inject.Guice.createInjector
import com.google.inject.Injector
import com.google.inject.Key
import com.google.inject.TypeLiteral
import io.edurt.datacap.spi.Plugin
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
class InfluxDBModuleTest
{
private val name = "InfluxDB"
private var injector: Injector? = null
@Before
fun before()
{
injector = createInjector(InfluxDBModule())
}
@Test
fun test()
{
injector !!.getInstance(Key.get(object : TypeLiteral<Set<Plugin>>()
{}))
.stream()
.findFirst()
.ifPresent { assertEquals(name, it.name()) }
}
}

View File

@ -1,80 +0,0 @@
package io.edurt.datacap.plugin.influxdb
import com.google.common.collect.Lists
import com.google.inject.Guice.createInjector
import com.google.inject.Injector
import com.google.inject.Key
import com.google.inject.TypeLiteral
import io.edurt.datacap.file.FileManager
import io.edurt.datacap.spi.Plugin
import io.edurt.datacap.spi.model.Configure
import io.edurt.datacap.spi.model.Response
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.slf4j.LoggerFactory.getLogger
import org.testcontainers.containers.Network
import org.testcontainers.lifecycle.Startables
import org.testcontainers.shaded.org.awaitility.Awaitility.given
import java.util.*
import java.util.concurrent.TimeUnit
class InfluxDBPluginTest
{
private val log = getLogger(this.javaClass)
private val host = "TestInfluxDBContainer"
private var container: InfluxDBContainer? = null
private var injector: Injector? = null
private var configure: Configure? = null
@Before
fun before()
{
val network = Network.newNetwork()
container = InfluxDBContainer()
.withNetwork(network)
.withNetworkAliases(host)
container?.portBindings = Lists.newArrayList(String.format("%s:%s", InfluxDBContainer.PORT, InfluxDBContainer.DOCKER_PORT))
Startables.deepStart(java.util.stream.Stream.of(container))
.join()
log.info("InfluxDB container started")
given().ignoreExceptions()
.await()
.atMost(1, TimeUnit.MINUTES)
injector = createInjector(
InfluxDBModule(),
FileManager()
)
configure = Configure()
configure !!.injector = injector
configure !!.host = container?.host
configure !!.port = InfluxDBContainer.PORT
configure !!.username = Optional.of(InfluxDBContainer.USERNAME)
configure !!.password = Optional.of(InfluxDBContainer.PASSWORD)
configure !!.env = Optional.of(mapOf("useEncryption" to false, "useHTTP2" to false, "db" to "test"))
}
@Test
fun test()
{
injector !!.getInstance(Key.get(object : TypeLiteral<Set<Plugin>>()
{}))
.stream()
.findFirst()
.ifPresent {
it.connect(configure)
val response: Response = it.execute(it.validator())
log.info("================ Plugin executed information =================")
if (! response.isSuccessful)
{
log.error("Message: {}", response.message)
}
else
{
response.columns.forEach { column -> log.info(column.toString()) }
}
assertTrue(response.isSuccessful)
}
}
}

View File

@ -75,7 +75,7 @@
<!-- <module>plugin/datacap-plugin-paradedb</module>-->
<!-- <module>plugin/datacap-plugin-timescale</module>-->
<!-- <module>plugin/datacap-plugin-solr</module>-->
<!-- <module>plugin/datacap-plugin-influxdb</module>-->
<module>plugin/datacap-plugin-influxdb</module>
<module>executor/datacap-executor-spi</module>
<module>executor/datacap-executor-local</module>
<module>executor/datacap-executor-seatunnel</module>

View File

@ -47,6 +47,10 @@
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>

View File

@ -35,6 +35,10 @@
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>

View File

@ -53,6 +53,10 @@
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>

View File

@ -40,6 +40,10 @@
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>

View File

@ -19,5 +19,52 @@
<artifactId>datacap-plugin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-spi</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-spi</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-influxdb</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<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>

View File

@ -4,7 +4,7 @@ import org.testcontainers.containers.GenericContainer
import org.testcontainers.utility.DockerImageName
import org.testcontainers.utility.DockerImageName.parse
class InfluxDBContainer : GenericContainer<InfluxDBContainer>
class InfluxdbContainer : GenericContainer<InfluxdbContainer>
{
constructor() : super(parse(DEFAULT_IMAGE_NAME))
{

View File

@ -0,0 +1,29 @@
package io.edurt.datacap.plugin.influxdb
import io.edurt.datacap.plugin.PluginConfigure
import io.edurt.datacap.plugin.PluginManager
import io.edurt.datacap.plugin.utils.PluginPathUtils
import org.junit.Assert.assertNotNull
import org.junit.Test
class InfluxdbPluginTest
{
private val pluginManager: PluginManager
private val pluginName = "Influxdb"
init
{
val projectRoot = PluginPathUtils.findProjectRoot()
val config = PluginConfigure.builder()
.pluginsDir(projectRoot.resolve("plugin/datacap-plugin-influxdb"))
.build()
pluginManager = PluginManager(config).apply { start() }
}
@Test
fun test()
{
assertNotNull(pluginManager.getPlugin(pluginName).get())
}
}

View File

@ -0,0 +1,90 @@
package io.edurt.datacap.plugin.influxdb
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import io.edurt.datacap.plugin.PluginConfigure
import io.edurt.datacap.plugin.PluginManager
import io.edurt.datacap.plugin.utils.PluginPathUtils
import io.edurt.datacap.spi.PluginService
import io.edurt.datacap.spi.model.Configure
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.slf4j.LoggerFactory.getLogger
import org.testcontainers.containers.Network
import org.testcontainers.lifecycle.Startables
import org.testcontainers.shaded.org.awaitility.Awaitility.given
import java.nio.file.Path
import java.util.*
import java.util.concurrent.TimeUnit
@SuppressFBWarnings(value = ["RV_RETURN_VALUE_IGNORED_INFERRED", "SA_LOCAL_SELF_ASSIGNMENT"])
class InfluxdbServiceTest
{
private val log = getLogger(this.javaClass)
private val pluginManager: PluginManager
private var container: InfluxdbContainer
private val pluginName = "Influxdb"
init
{
val network = Network.newNetwork()
container = InfluxdbContainer()
.withNetwork(network)
.withNetworkAliases(this.javaClass.simpleName)
container.portBindings = listOf(String.format("%s:%s", InfluxdbContainer.PORT, InfluxdbContainer.DOCKER_PORT))
Startables.deepStart(java.util.stream.Stream.of(container))
.join()
given().ignoreExceptions()
.await()
.atMost(1, TimeUnit.MINUTES)
log.info("Influxdb container started")
val configFile = this::class
.java
.classLoader
.getResource("influxdb-config.properties")
assertNotNull(configFile)
log.info("Specified config file: {}", configFile)
val projectRoot = PluginPathUtils.findProjectRoot()
val config = PluginConfigure.builder()
.pluginsDir(projectRoot.resolve(Path.of(configFile.path)))
.build()
log.info("Initializing plugin manager")
pluginManager = PluginManager(config).apply { start() }
}
@Test
fun test()
{
val plugin = pluginManager.getPlugin(pluginName).get()
assertNotNull(plugin)
val configure: Configure = Configure.builder()
.host(container.host)
.port(InfluxdbContainer.PORT)
.username(Optional.of(InfluxdbContainer.USERNAME))
.password(Optional.of(InfluxdbContainer.PASSWORD))
.database(Optional.of(InfluxdbContainer.BUCKET))
.plugin(plugin)
.pluginManager(pluginManager)
.env(
Optional.of(
mapOf(
"useEncryption" to false,
"useHTTP2" to false,
"org" to "TestOrg"
)
)
)
.build()
val pluginService = plugin.getService(PluginService::class.java)
val response = pluginService.execute(configure, pluginService.validator())
assertNotNull(response)
}
}

View File

@ -0,0 +1,2 @@
datacap-plugin-influxdb=plugin/datacap-plugin-influxdb/pom.xml
datacap-convert-json=convert/datacap-convert-json/pom.xml

View File

@ -47,6 +47,10 @@
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>