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