diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml
new file mode 100644
index 00000000..a1e70324
--- /dev/null
+++ b/.github/workflows/pr-title-lint.yml
@@ -0,0 +1,29 @@
+# Copyright 2024 openGemini Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: pr title lint
+
+on:
+ pull_request:
+ types: [opened, edited, synchronize, reopened]
+ branches:
+ - main
+
+jobs:
+ runner:
+ name: pr title lint
+ runs-on: ubuntu-latest
+ steps:
+ - name: PR Title Lint
+ uses: openGemini/pr-title-checker@v1.0.2
diff --git a/.typos.toml b/.typos.toml
new file mode 100644
index 00000000..2fe0d9b4
--- /dev/null
+++ b/.typos.toml
@@ -0,0 +1,17 @@
+# Copyright 2024 openGemini Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[default.extend-words]
+# abbr
+"vertx" = "vertx"
diff --git a/ci/spotbugs/exclude.xml b/ci/spotbugs/exclude.xml
index e53dad01..6094db4b 100644
--- a/ci/spotbugs/exclude.xml
+++ b/ci/spotbugs/exclude.xml
@@ -41,5 +41,8 @@
-
+
+
+
+
diff --git a/opengemini-client-api/pom.xml b/opengemini-client-api/pom.xml
index cf5e4a62..ddb6e7f7 100644
--- a/opengemini-client-api/pom.xml
+++ b/opengemini-client-api/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-client-parent
- 0.3.1
+ 0.3.2
opengemini-client-api
diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/OpenGeminiAsyncClient.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/OpenGeminiAsyncClient.java
index cd87ecff..b1723698 100644
--- a/opengemini-client-api/src/main/java/io/opengemini/client/api/OpenGeminiAsyncClient.java
+++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/OpenGeminiAsyncClient.java
@@ -108,6 +108,14 @@ public interface OpenGeminiAsyncClient extends AutoCloseable {
*/
CompletableFuture write(String database, String retentionPolicy, List points);
+ /**
+ * Writing via GRPC points to the database.
+ *
+ * @param database the name of the database.
+ * @param points the points to write.
+ */
+ CompletableFuture writeByGrpc(String database, List points);
+
/**
* Ping the OpenGemini server
*/
diff --git a/opengemini-client-common/pom.xml b/opengemini-client-common/pom.xml
index eb4ebe04..895b5564 100644
--- a/opengemini-client-common/pom.xml
+++ b/opengemini-client-common/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-client-parent
- 0.3.1
+ 0.3.2
opengemini-client-common
diff --git a/opengemini-client-common/src/main/java/io/opengemini/client/common/BaseAsyncClient.java b/opengemini-client-common/src/main/java/io/opengemini/client/common/BaseAsyncClient.java
index 60cf32a4..c40924fb 100644
--- a/opengemini-client-common/src/main/java/io/opengemini/client/common/BaseAsyncClient.java
+++ b/opengemini-client-common/src/main/java/io/opengemini/client/common/BaseAsyncClient.java
@@ -154,6 +154,14 @@ public CompletableFuture write(String database, String retentionPolicy, Li
return executeWrite(database, retentionPolicy, body);
}
+ @Override
+ public CompletableFuture writeByGrpc(String database, List points) {
+ if (points.isEmpty()) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return executeWriteByGrpc(database, points);
+ }
+
/**
* {@inheritDoc}
*/
@@ -187,6 +195,15 @@ protected abstract CompletableFuture executeWrite(String database,
String retentionPolicy,
String lineProtocol);
+ /**
+ * The implementation class needs to implement this method to execute a write operation via an RPC call.
+ *
+ * @param database the name of the database.
+ * @param points the points to write.
+ */
+ protected abstract CompletableFuture executeWriteByGrpc(String database,
+ List points);
+
/**
* The implementation class needs to implement this method to execute a ping call.
*/
diff --git a/opengemini-client-common/src/main/java/io/opengemini/client/common/ResultMapper.java b/opengemini-client-common/src/main/java/io/opengemini/client/common/ResultMapper.java
index 92ec0660..c143e685 100644
--- a/opengemini-client-common/src/main/java/io/opengemini/client/common/ResultMapper.java
+++ b/opengemini-client-common/src/main/java/io/opengemini/client/common/ResultMapper.java
@@ -18,22 +18,25 @@
import io.opengemini.client.api.QueryResult;
import io.opengemini.client.api.RetentionPolicy;
+import io.opengemini.client.api.Series;
+import io.opengemini.client.api.SeriesResult;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
public class ResultMapper {
public static List toDatabases(QueryResult result) {
- return result.getResults()
- .get(0)
- .getSeries()
- .get(0)
- .getValues()
- .stream()
- .map(x -> String.valueOf(x.get(0)))
- .collect(Collectors.toList());
+ return Optional.ofNullable(result.getResults())
+ .map(list -> list.get(0))
+ .map(SeriesResult::getSeries)
+ .map(list -> list.get(0))
+ .map(Series::getValues)
+ .map(list -> list.stream().map(x -> String.valueOf(x.get(0))).collect(Collectors.toList()))
+ .orElse(Collections.emptyList());
}
public static List toRetentionPolicies(QueryResult result) {
diff --git a/opengemini-client-reactor/pom.xml b/opengemini-client-reactor/pom.xml
index a1447c8b..d40f7cde 100644
--- a/opengemini-client-reactor/pom.xml
+++ b/opengemini-client-reactor/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-client-parent
- 0.3.1
+ 0.3.2
opengemini-client-reactor
diff --git a/opengemini-client/pom.xml b/opengemini-client/pom.xml
index e6673d86..90c449ee 100644
--- a/opengemini-client/pom.xml
+++ b/opengemini-client/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-client-parent
- 0.3.1
+ 0.3.2
opengemini-client
@@ -54,6 +54,23 @@
${okhttp.version}
provided
+
+ io.grpc
+ grpc-netty
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.vertx
+ vertx-grpc
+ ${vertx.version}
+
diff --git a/opengemini-client/src/main/java/io/opengemini/client/impl/OpenGeminiClient.java b/opengemini-client/src/main/java/io/opengemini/client/impl/OpenGeminiClient.java
index a3af405e..a7dbcd9e 100644
--- a/opengemini-client/src/main/java/io/opengemini/client/impl/OpenGeminiClient.java
+++ b/opengemini-client/src/main/java/io/opengemini/client/impl/OpenGeminiClient.java
@@ -25,6 +25,7 @@
import io.opengemini.client.api.AuthType;
import io.opengemini.client.api.Configuration;
import io.opengemini.client.api.OpenGeminiException;
+import io.opengemini.client.api.Point;
import io.opengemini.client.api.Pong;
import io.opengemini.client.api.Query;
import io.opengemini.client.api.QueryResult;
@@ -35,6 +36,7 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -90,6 +92,11 @@ protected CompletableFuture executeWrite(String database, String retention
return post(writeUrl, lineProtocol).thenCompose(response -> convertResponse(response, Void.class));
}
+ @Override
+ protected CompletableFuture executeWriteByGrpc(String database, List points) {
+ return null;
+ }
+
/**
* Execute a ping call with java HttpClient.
*/
diff --git a/opengemini-client/src/main/proto/write.proto b/opengemini-client/src/main/proto/write.proto
new file mode 100644
index 00000000..5e51f2c7
--- /dev/null
+++ b/opengemini-client/src/main/proto/write.proto
@@ -0,0 +1,61 @@
+syntax = "proto3";
+package proto;
+option java_multiple_files = true;
+option java_package = "io.opengemini.client.proto";
+option java_outer_classname = "WriteProto";
+
+// WriteService represents a openGemini RPC write service.
+service WriteService {
+ // Write writes the given records to the specified database and retention policy.
+ rpc Write (WriteRequest) returns (WriteResponse) {}
+ // Ping is used to check if the server is alive
+ rpc Ping(PingRequest) returns (PingResponse) {}
+}
+
+message WriteRequest {
+ uint32 version = 1;
+ string database = 2;
+ string retention_policy = 3;
+ string username = 4;
+ string password = 5;
+ repeated Record records = 6;
+}
+
+message WriteResponse {
+ ResponseCode code = 1;
+}
+
+message Record {
+ string measurement = 1;
+ int64 min_time = 2;
+ int64 max_time = 3;
+ CompressMethod compress_method = 4;
+ bytes block = 5;
+}
+
+enum CompressMethod {
+ UNCOMPRESSED = 0;
+ LZ4_FAST = 1;
+ ZSTD_FAST = 2;
+ SNAPPY = 3;
+}
+
+enum ResponseCode {
+ Success = 0;
+ Partial = 1;
+ Failed = 2;
+}
+
+message PingRequest {
+ string client_id = 1;
+}
+
+enum ServerStatus {
+ Up = 0;
+ Down = 1;
+ Unknown = 99;
+}
+
+message PingResponse {
+ ServerStatus status = 1;
+}
diff --git a/pom.xml b/pom.xml
index d9a315ee..41f7f448 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-client-parent
- 0.3.1
+ 0.3.2
pom
opengemini-client
@@ -42,7 +42,8 @@
13.0
3.0.0.Beta3
3.17.0
- 0.2.0
+ 1.61.1
+ 0.2.1
2.17.2
5.11.1
1.9.22
@@ -52,11 +53,14 @@
5.13.0
4.12.0
10.18.1
+ 3.25.1
+ 3.25.1
1.1.21
3.4.0
2.0.7
6.0.19
3.1.11
+ 4.5.10
0.6.0
1.18.20.0
@@ -70,6 +74,8 @@
3.1.1
3.3.1
3.5.0
+ 1.7.1
+ 0.6.1
1.6.13
4.9.2
4.8.6.4
@@ -95,6 +101,13 @@
jackson-databind
${jackson.version}
+
+ io.grpc
+ grpc-bom
+ ${grpc.version}
+ pom
+ import
+
@@ -146,6 +159,13 @@
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${maven-os-maven-plugin.version}
+
+
${src.dir}
@@ -168,6 +188,44 @@
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${maven-protobuf-maven-plugin}
+
+ com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
+
+
+
+
+
+ grpc-java
+
+ compile
+ compile-custom
+
+
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+
+
+
+
+
+ vertx-grpc
+
+ compile
+ compile-custom
+
+
+ grpc-vertx
+
+ io.vertx:vertx-grpc-protoc-plugin:${vertx.version}:exe:${os.detected.classifier}
+
+
+
+
+
org.apache.maven.plugins
maven-compiler-plugin
diff --git a/spring/opengemini-spring-boot-starter-reactive/pom.xml b/spring/opengemini-spring-boot-starter-reactive/pom.xml
index 729b4ada..3c4ceb74 100644
--- a/spring/opengemini-spring-boot-starter-reactive/pom.xml
+++ b/spring/opengemini-spring-boot-starter-reactive/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-spring-parent
- 0.3.1
+ 0.3.2
../pom.xml
diff --git a/spring/opengemini-spring-boot-starter/pom.xml b/spring/opengemini-spring-boot-starter/pom.xml
index 2f8b97ec..9e0826b8 100644
--- a/spring/opengemini-spring-boot-starter/pom.xml
+++ b/spring/opengemini-spring-boot-starter/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-spring-parent
- 0.3.1
+ 0.3.2
../pom.xml
diff --git a/spring/opengemini-spring-boot-starter/src/main/java/io/opengemini/client/spring/data/config/OpenGeminiAutoConfiguration.java b/spring/opengemini-spring-boot-starter/src/main/java/io/opengemini/client/spring/data/config/OpenGeminiAutoConfiguration.java
index 7b5eeb85..620a04dc 100644
--- a/spring/opengemini-spring-boot-starter/src/main/java/io/opengemini/client/spring/data/config/OpenGeminiAutoConfiguration.java
+++ b/spring/opengemini-spring-boot-starter/src/main/java/io/opengemini/client/spring/data/config/OpenGeminiAutoConfiguration.java
@@ -21,6 +21,8 @@
import io.opengemini.client.impl.OpenGeminiClientFactory;
import io.opengemini.client.spring.data.core.ClientConfigurationBuilderCustomizer;
import io.opengemini.client.spring.data.core.DefaultOpenGeminiSerializerFactory;
+import io.opengemini.client.spring.data.core.MeasurementScanConfigurer;
+import io.opengemini.client.spring.data.core.MeasurementScanInitializer;
import io.opengemini.client.spring.data.core.OpenGeminiProperties;
import io.opengemini.client.spring.data.core.OpenGeminiPropertiesConverter;
import io.opengemini.client.spring.data.core.OpenGeminiSerializerFactory;
@@ -31,6 +33,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
+import org.springframework.lang.Nullable;
@AutoConfiguration
@ConditionalOnClass(OpenGeminiTemplate.class)
@@ -59,4 +62,10 @@ public OpenGeminiSerializerFactory openGeminiSerializerFactory() {
return new DefaultOpenGeminiSerializerFactory();
}
+ @Bean
+ public MeasurementScanInitializer measurementScanInitializer(OpenGeminiTemplate openGeminiTemplate,
+ @Nullable MeasurementScanConfigurer configurer) {
+ return new MeasurementScanInitializer(openGeminiTemplate, configurer);
+ }
+
}
diff --git a/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/config/OpenGeminiTemplateTest.java b/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/config/OpenGeminiTemplateTest.java
index ead26e56..9e073b52 100644
--- a/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/config/OpenGeminiTemplateTest.java
+++ b/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/config/OpenGeminiTemplateTest.java
@@ -22,6 +22,7 @@
import io.opengemini.client.spring.data.core.MeasurementOperations;
import io.opengemini.client.spring.data.core.OpenGeminiTemplate;
import io.opengemini.client.spring.data.sample.TestApplication;
+import io.opengemini.client.spring.data.sample.measurement.WeatherFixNameAutoCreate;
import io.opengemini.client.spring.data.sample.measurement.WeatherFixNameNoCreate;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -74,4 +75,35 @@ public void write_with_measurement_operations() throws Exception {
Assertions.assertEquals(weatherForWrite.getTime(), weather1.getTime());
}
+ @Test
+ void database_should_auto_created() throws Exception {
+ String databaseName = "weather_db_auto_create";
+ String rpName = "weather_rp_auto_create";
+
+ Assertions.assertTrue(openGeminiTemplate.isDatabaseExists(databaseName));
+ Assertions.assertTrue(openGeminiTemplate.isRetentionPolicyExists(databaseName, rpName));
+
+ MeasurementOperations measurementOperations = openGeminiTemplate.opsForMeasurement(
+ WeatherFixNameAutoCreate.class);
+ WeatherFixNameAutoCreate weatherForWrite = new WeatherFixNameAutoCreate();
+ weatherForWrite.setLocation("shenzhen");
+ weatherForWrite.setTemperature(28.5D);
+ weatherForWrite.setTime(System.currentTimeMillis());
+ measurementOperations.write(weatherForWrite);
+
+ Thread.sleep(5000);
+
+ String measurementName = "weather_ms";
+ Query selectQuery = new Query("select * from " + measurementName, databaseName, rpName);
+ List weatherList = measurementOperations.query(selectQuery);
+
+ openGeminiTemplate.dropRetentionPolicy(databaseName, rpName);
+ openGeminiTemplate.dropDatabase(databaseName);
+
+ Assertions.assertEquals(weatherList.size(), 1);
+ WeatherFixNameAutoCreate weather1 = weatherList.get(0);
+ Assertions.assertEquals(weatherForWrite.getLocation(), weather1.getLocation());
+ Assertions.assertEquals(weatherForWrite.getTemperature(), weather1.getTemperature());
+ Assertions.assertEquals(weatherForWrite.getTime(), weather1.getTime());
+ }
}
diff --git a/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/TestApplication.java b/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/TestApplication.java
index ea79823b..42c5ae87 100644
--- a/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/TestApplication.java
+++ b/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/TestApplication.java
@@ -16,10 +16,12 @@
package io.opengemini.client.spring.data.sample;
+import io.opengemini.client.spring.data.annotation.MeasurementScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
+@MeasurementScan("io.opengemini.client.spring.data.sample.measurement")
public class TestApplication {
public static void main(String[] args) {
diff --git a/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/measurement/WeatherFixNameAutoCreate.java b/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/measurement/WeatherFixNameAutoCreate.java
new file mode 100644
index 00000000..3d11fa6f
--- /dev/null
+++ b/spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/measurement/WeatherFixNameAutoCreate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 openGemini Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opengemini.client.spring.data.sample.measurement;
+
+import io.opengemini.client.api.Precision;
+import io.opengemini.client.spring.data.annotation.Database;
+import io.opengemini.client.spring.data.annotation.Field;
+import io.opengemini.client.spring.data.annotation.Measurement;
+import io.opengemini.client.spring.data.annotation.RetentionPolicy;
+import io.opengemini.client.spring.data.annotation.Tag;
+import io.opengemini.client.spring.data.annotation.Time;
+import lombok.Getter;
+import lombok.Setter;
+
+@Database(name = "weather_db_auto_create")
+@RetentionPolicy(name = "weather_rp_auto_create", duration = "3d", shardGroupDuration = "1h", indexDuration = "7h")
+@Measurement(name = "weather_ms")
+@Getter
+@Setter
+public class WeatherFixNameAutoCreate {
+
+ @Tag(name = "Location")
+ private String location;
+
+ @Field(name = "Temperature")
+ private Double temperature;
+
+ @Time(precision = Precision.PRECISIONMILLISECOND)
+ private Long time;
+
+}
diff --git a/spring/opengemini-spring/pom.xml b/spring/opengemini-spring/pom.xml
index 7d5a8963..8755ba22 100644
--- a/spring/opengemini-spring/pom.xml
+++ b/spring/opengemini-spring/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-spring-parent
- 0.3.1
+ 0.3.2
../pom.xml
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/annotation/MeasurementScan.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/annotation/MeasurementScan.java
index 511757df..3906ef1d 100644
--- a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/annotation/MeasurementScan.java
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/annotation/MeasurementScan.java
@@ -16,6 +16,8 @@
package io.opengemini.client.spring.data.annotation;
+import io.opengemini.client.spring.data.core.MeasurementScanRegistrar;
+import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
@@ -25,6 +27,7 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
+@Import(MeasurementScanRegistrar.class)
public @interface MeasurementScan {
/**
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanConfigurer.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanConfigurer.java
new file mode 100644
index 00000000..f8234959
--- /dev/null
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanConfigurer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 openGemini Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opengemini.client.spring.data.core;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+@Setter
+@Getter
+public class MeasurementScanConfigurer implements InitializingBean {
+
+ private List basePackages;
+
+ @Override
+ public void afterPropertiesSet() {
+ Assert.notEmpty(basePackages, "Property 'basePackage' is required");
+ }
+}
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanInitializer.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanInitializer.java
new file mode 100644
index 00000000..5f131ac6
--- /dev/null
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanInitializer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 openGemini Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opengemini.client.spring.data.core;
+
+import io.opengemini.client.api.RpConfig;
+import io.opengemini.client.spring.data.annotation.Database;
+import io.opengemini.client.spring.data.annotation.Measurement;
+import io.opengemini.client.spring.data.annotation.RetentionPolicy;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MeasurementScanInitializer implements InitializingBean {
+
+ private final OpenGeminiTemplate openGeminiTemplate;
+ private final MeasurementScanConfigurer measurementScanConfigurer;
+
+ public MeasurementScanInitializer(OpenGeminiTemplate openGeminiTemplate, MeasurementScanConfigurer configurer) {
+ this.openGeminiTemplate = openGeminiTemplate;
+ this.measurementScanConfigurer = configurer;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ Set> measurementClassSet = new HashSet<>();
+ if (measurementScanConfigurer != null) {
+ for (String basePackage : measurementScanConfigurer.getBasePackages()) {
+ scanForMeasurementClass(basePackage, measurementClassSet);
+ }
+ }
+ initDatabase(measurementClassSet);
+ }
+
+ private void scanForMeasurementClass(String basePackage, Set> measurementClassSet) {
+ if (!StringUtils.hasText(basePackage)) {
+ return;
+ }
+
+ ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
+ provider.addIncludeFilter(new AnnotationTypeFilter(Measurement.class));
+ ClassLoader classLoader = this.getClass().getClassLoader();
+ for (BeanDefinition candidate : provider.findCandidateComponents(basePackage)) {
+ String beanClassName = candidate.getBeanClassName();
+ if (beanClassName != null) {
+ try {
+ measurementClassSet.add(ClassUtils.forName(beanClassName, classLoader));
+ } catch (ClassNotFoundException | LinkageError ignored) {
+ }
+ }
+ }
+ }
+
+ private void initDatabase(Set> measurementClassSet) {
+ for (Class> clazz : measurementClassSet) {
+ Database dbAnnotation = clazz.getAnnotation(Database.class);
+ if (dbAnnotation == null || !dbAnnotation.create()) {
+ continue;
+ }
+ String database = dbAnnotation.name();
+ openGeminiTemplate.createDatabaseIfAbsent(database);
+
+ RetentionPolicy rpAnnotation = clazz.getAnnotation(RetentionPolicy.class);
+ if (rpAnnotation == null || !rpAnnotation.create()) {
+ continue;
+ }
+
+ RpConfig rpConfig = new RpConfig(rpAnnotation.name(), rpAnnotation.duration(),
+ rpAnnotation.shardGroupDuration(), rpAnnotation.indexDuration());
+ openGeminiTemplate.createRetentionPolicyIfAbsent(database, rpConfig, rpAnnotation.isDefault());
+ }
+ }
+
+}
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanRegistrar.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanRegistrar.java
new file mode 100644
index 00000000..6d9b96eb
--- /dev/null
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/MeasurementScanRegistrar.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 openGemini Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opengemini.client.spring.data.core;
+
+import io.opengemini.client.spring.data.annotation.MeasurementScan;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MeasurementScanRegistrar implements ImportBeanDefinitionRegistrar {
+
+ @Override
+ public void registerBeanDefinitions(@NotNull AnnotationMetadata importingClassMetadata,
+ @NotNull BeanDefinitionRegistry registry) {
+ AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(
+ importingClassMetadata.getAnnotationAttributes(MeasurementScan.class.getName()));
+ if (mapperScanAttrs != null) {
+ String beanName = generateBaseBeanName(importingClassMetadata);
+ registerBeanDefinitions(mapperScanAttrs, registry, beanName);
+ }
+ }
+
+ private String generateBaseBeanName(AnnotationMetadata importingClassMetadata) {
+ return importingClassMetadata.getClassName() + "#" + MeasurementScanRegistrar.class.getSimpleName();
+ }
+
+ private void registerBeanDefinitions(AnnotationAttributes annoAttrs,
+ BeanDefinitionRegistry registry,
+ String beanName) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MeasurementScanConfigurer.class);
+
+ List basePackages = Arrays.stream(annoAttrs.getStringArray("basePackages"))
+ .filter(StringUtils::hasText)
+ .collect(Collectors.toList());
+ builder.addPropertyValue("basePackages", basePackages);
+
+ registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
+ }
+
+}
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiOperations.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiOperations.java
index 314824be..5ddacc3b 100644
--- a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiOperations.java
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiOperations.java
@@ -16,11 +16,30 @@
package io.opengemini.client.spring.data.core;
+import io.opengemini.client.api.RpConfig;
+
/**
* Interface that specified a basic set of OpenGemini operations, implemented by {@link OpenGeminiTemplate}.
* A useful option for extensibility and testability (as it can be easily mocked or stubbed).
*/
public interface OpenGeminiOperations {
+
+ void createDatabaseIfAbsent(String database);
+
+ void createDatabase(String database);
+
+ boolean isDatabaseExists(String database);
+
+ void dropDatabase(String database);
+
+ void createRetentionPolicyIfAbsent(String database, RpConfig rpConfig, boolean isDefault);
+
+ void createRetentionPolicy(String database, RpConfig rpConfig, boolean isDefault);
+
+ boolean isRetentionPolicyExists(String database, String retentionPolicy);
+
+ void dropRetentionPolicy(String database, String retentionPolicy);
+
MeasurementOperations opsForMeasurement(Class clazz);
MeasurementOperations opsForMeasurement(String databaseName,
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverter.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverter.java
index 80fe3ce3..a604b255 100644
--- a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverter.java
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverter.java
@@ -83,7 +83,7 @@ public HttpClientConfig toHttpClientConfig(OpenGeminiProperties.Http http) {
HttpClientConfig.Builder builder = new HttpClientConfig.Builder();
builder.engine(http.getEngine());
builder.timeout(http.getTimeout());
- builder.timeout(http.getTimeout());
+ builder.connectTimeout(http.getConnectTimeout());
Optional.ofNullable(http.getSsl()).map(this::toTlsConfig).ifPresent(builder::tlsConfig);
Optional.ofNullable(http.getOkHttp()).map(this::toOkHttpConfig).ifPresent(builder::okHttpConfig);
return builder.build();
diff --git a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiTemplate.java b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiTemplate.java
index abf6a068..73211054 100644
--- a/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiTemplate.java
+++ b/spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiTemplate.java
@@ -17,15 +17,20 @@
package io.opengemini.client.spring.data.core;
import io.opengemini.client.api.OpenGeminiAsyncClient;
+import io.opengemini.client.api.RpConfig;
import io.opengemini.client.spring.data.annotation.Database;
import io.opengemini.client.spring.data.annotation.Measurement;
import io.opengemini.client.spring.data.annotation.RetentionPolicy;
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
/**
* Helper class that simplifies OpenGemini data access code.
@@ -42,6 +47,62 @@ public OpenGeminiTemplate(OpenGeminiAsyncClient asyncClient, OpenGeminiSerialize
this.serializerFactory = serializerFactory;
}
+ @Override
+ public void createDatabaseIfAbsent(String database) {
+ if (!isDatabaseExists(database)) {
+ createDatabase(database);
+ }
+ }
+
+ @SneakyThrows
+ @Override
+ public void createDatabase(String database) {
+ asyncClient.createDatabase(database).get();
+ }
+
+ @SneakyThrows
+ @Override
+ public boolean isDatabaseExists(String database) {
+ List databases = asyncClient.showDatabases().get();
+ return databases != null && databases.contains(database);
+ }
+
+ @SneakyThrows
+ @Override
+ public void dropDatabase(String database) {
+ asyncClient.dropDatabase(database).get();
+ }
+
+ @Override
+ public void createRetentionPolicyIfAbsent(String database, RpConfig rpConfig, boolean isDefault) {
+ if (!isRetentionPolicyExists(database, rpConfig.getName())) {
+ createRetentionPolicy(database, rpConfig, isDefault);
+ }
+ }
+
+ @SneakyThrows
+ @Override
+ public void createRetentionPolicy(String database, RpConfig rpConfig, boolean isDefault) {
+ asyncClient.createRetentionPolicy(database, rpConfig, isDefault).get();
+ }
+
+ @SneakyThrows
+ @Override
+ public boolean isRetentionPolicyExists(String database, String retentionPolicy) {
+ Set retentionPolicies = asyncClient.showRetentionPolicies(database)
+ .get()
+ .stream()
+ .map(io.opengemini.client.api.RetentionPolicy::getName)
+ .collect(Collectors.toSet());
+ return retentionPolicies.contains(retentionPolicy);
+ }
+
+ @SneakyThrows
+ @Override
+ public void dropRetentionPolicy(String database, String retentionPolicy) {
+ asyncClient.dropRetentionPolicy(database, retentionPolicy).get();
+ }
+
@Override
public MeasurementOperations opsForMeasurement(Class clazz) {
MeasurementOperationsCacheKey key = MeasurementOperationsCacheKey.of(clazz);
@@ -54,7 +115,7 @@ public MeasurementOperations opsForMeasurement(String databaseName,
String measurementName,
Class clazz) {
MeasurementOperationsCacheKey key = new MeasurementOperationsCacheKey(databaseName, retentionPolicyName,
- measurementName, clazz);
+ measurementName, clazz);
return getMeasurementOperations(key);
}
@@ -64,7 +125,7 @@ public MeasurementOperations opsForMeasurement(String databaseName,
OpenGeminiSerializer serializer = (OpenGeminiSerializer) serializerFactory.getSerializer(
k.getClazz());
return new MeasurementOperationsImpl<>(asyncClient, serializer, k.getDatabaseName(),
- k.getRetentionPolicyName(), k.getMeasurementName());
+ k.getRetentionPolicyName(), k.getMeasurementName());
});
}
@@ -100,7 +161,7 @@ public static MeasurementOperationsCacheKey of(Class> clazz) {
throw new IllegalArgumentException("Class " + clazz.getName() + " has no @Database annotation");
}
return new MeasurementOperationsCacheKey(dbAnnotation.name(), rpAnnotation.name(), msAnnotation.name(),
- clazz);
+ clazz);
}
}
}
diff --git a/spring/opengemini-spring/src/test/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverterTest.java b/spring/opengemini-spring/src/test/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverterTest.java
new file mode 100644
index 00000000..4ed7314f
--- /dev/null
+++ b/spring/opengemini-spring/src/test/java/io/opengemini/client/spring/data/core/OpenGeminiPropertiesConverterTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 openGemini Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opengemini.client.spring.data.core;
+
+import io.github.openfacade.http.HttpClientConfig;
+import io.github.openfacade.http.HttpClientEngine;
+import io.opengemini.client.api.Configuration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.support.StaticListableBeanFactory;
+
+import java.time.Duration;
+
+class OpenGeminiPropertiesConverterTest {
+
+ @Test
+ void toConfiguration_should_right_for_convert_http_config() {
+ OpenGeminiProperties.Http http = new OpenGeminiProperties.Http();
+ http.setEngine(HttpClientEngine.OkHttp);
+ http.setTimeout(Duration.ofSeconds(30));
+ http.setConnectTimeout(Duration.ofSeconds(10));
+
+ OpenGeminiProperties properties = new OpenGeminiProperties();
+ properties.setHttp(http);
+
+ ObjectProvider provider =
+ new StaticListableBeanFactory().getBeanProvider(ClientConfigurationBuilderCustomizer.class);
+ OpenGeminiPropertiesConverter converter = new OpenGeminiPropertiesConverter(properties, provider);
+
+ Configuration configuration = converter.toConfiguration();
+ HttpClientConfig httpConfig = configuration.getHttpConfig();
+
+ Assertions.assertEquals(http.getEngine(), httpConfig.engine());
+ Assertions.assertEquals(http.getTimeout(), httpConfig.timeout());
+ Assertions.assertEquals(http.getConnectTimeout(), httpConfig.connectTimeout());
+ }
+}
diff --git a/spring/pom.xml b/spring/pom.xml
index 13080d5b..646d5275 100644
--- a/spring/pom.xml
+++ b/spring/pom.xml
@@ -21,7 +21,7 @@
io.opengemini
opengemini-client-parent
- 0.3.1
+ 0.3.2
opengemini-spring-parent