Skip to content

Commit aee18e2

Browse files
authored
feat: support automatic database and retention policy creation (openGemini#144)
Signed-off-by: weiping-code <[email protected]>
1 parent 5012992 commit aee18e2

File tree

11 files changed

+376
-11
lines changed

11 files changed

+376
-11
lines changed

opengemini-client-common/src/main/java/io/opengemini/client/common/ResultMapper.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,25 @@
1818

1919
import io.opengemini.client.api.QueryResult;
2020
import io.opengemini.client.api.RetentionPolicy;
21+
import io.opengemini.client.api.Series;
22+
import io.opengemini.client.api.SeriesResult;
2123

2224
import java.util.ArrayList;
25+
import java.util.Collections;
2326
import java.util.List;
27+
import java.util.Optional;
2428
import java.util.stream.Collectors;
2529

2630
public class ResultMapper {
2731

2832
public static List<String> toDatabases(QueryResult result) {
29-
return result.getResults()
30-
.get(0)
31-
.getSeries()
32-
.get(0)
33-
.getValues()
34-
.stream()
35-
.map(x -> String.valueOf(x.get(0)))
36-
.collect(Collectors.toList());
33+
return Optional.ofNullable(result.getResults())
34+
.map(list -> list.get(0))
35+
.map(SeriesResult::getSeries)
36+
.map(list -> list.get(0))
37+
.map(Series::getValues)
38+
.map(list -> list.stream().map(x -> String.valueOf(x.get(0))).collect(Collectors.toList()))
39+
.orElse(Collections.emptyList());
3740
}
3841

3942
public static List<RetentionPolicy> toRetentionPolicies(QueryResult result) {

spring/opengemini-spring-boot-starter/src/main/java/io/opengemini/client/spring/data/config/OpenGeminiAutoConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import io.opengemini.client.impl.OpenGeminiClientFactory;
2222
import io.opengemini.client.spring.data.core.ClientConfigurationBuilderCustomizer;
2323
import io.opengemini.client.spring.data.core.DefaultOpenGeminiSerializerFactory;
24+
import io.opengemini.client.spring.data.core.MeasurementScanConfigurer;
25+
import io.opengemini.client.spring.data.core.MeasurementScanInitializer;
2426
import io.opengemini.client.spring.data.core.OpenGeminiProperties;
2527
import io.opengemini.client.spring.data.core.OpenGeminiPropertiesConverter;
2628
import io.opengemini.client.spring.data.core.OpenGeminiSerializerFactory;
@@ -31,6 +33,7 @@
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3234
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3335
import org.springframework.context.annotation.Bean;
36+
import org.springframework.lang.Nullable;
3437

3538
@AutoConfiguration
3639
@ConditionalOnClass(OpenGeminiTemplate.class)
@@ -59,4 +62,10 @@ public OpenGeminiSerializerFactory openGeminiSerializerFactory() {
5962
return new DefaultOpenGeminiSerializerFactory();
6063
}
6164

65+
@Bean
66+
public MeasurementScanInitializer measurementScanInitializer(OpenGeminiTemplate openGeminiTemplate,
67+
@Nullable MeasurementScanConfigurer configurer) {
68+
return new MeasurementScanInitializer(openGeminiTemplate, configurer);
69+
}
70+
6271
}

spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/config/OpenGeminiTemplateTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.opengemini.client.spring.data.core.MeasurementOperations;
2323
import io.opengemini.client.spring.data.core.OpenGeminiTemplate;
2424
import io.opengemini.client.spring.data.sample.TestApplication;
25+
import io.opengemini.client.spring.data.sample.measurement.WeatherFixNameAutoCreate;
2526
import io.opengemini.client.spring.data.sample.measurement.WeatherFixNameNoCreate;
2627
import org.junit.jupiter.api.Assertions;
2728
import org.junit.jupiter.api.Test;
@@ -74,4 +75,35 @@ public void write_with_measurement_operations() throws Exception {
7475
Assertions.assertEquals(weatherForWrite.getTime(), weather1.getTime());
7576
}
7677

78+
@Test
79+
void database_should_auto_created() throws Exception {
80+
String databaseName = "weather_db_auto_create";
81+
String rpName = "weather_rp_auto_create";
82+
83+
Assertions.assertTrue(openGeminiTemplate.isDatabaseExists(databaseName));
84+
Assertions.assertTrue(openGeminiTemplate.isRetentionPolicyExists(databaseName, rpName));
85+
86+
MeasurementOperations<WeatherFixNameAutoCreate> measurementOperations = openGeminiTemplate.opsForMeasurement(
87+
WeatherFixNameAutoCreate.class);
88+
WeatherFixNameAutoCreate weatherForWrite = new WeatherFixNameAutoCreate();
89+
weatherForWrite.setLocation("shenzhen");
90+
weatherForWrite.setTemperature(28.5D);
91+
weatherForWrite.setTime(System.currentTimeMillis());
92+
measurementOperations.write(weatherForWrite);
93+
94+
Thread.sleep(5000);
95+
96+
String measurementName = "weather_ms";
97+
Query selectQuery = new Query("select * from " + measurementName, databaseName, rpName);
98+
List<WeatherFixNameAutoCreate> weatherList = measurementOperations.query(selectQuery);
99+
100+
openGeminiTemplate.dropRetentionPolicy(databaseName, rpName);
101+
openGeminiTemplate.dropDatabase(databaseName);
102+
103+
Assertions.assertEquals(weatherList.size(), 1);
104+
WeatherFixNameAutoCreate weather1 = weatherList.get(0);
105+
Assertions.assertEquals(weatherForWrite.getLocation(), weather1.getLocation());
106+
Assertions.assertEquals(weatherForWrite.getTemperature(), weather1.getTemperature());
107+
Assertions.assertEquals(weatherForWrite.getTime(), weather1.getTime());
108+
}
77109
}

spring/opengemini-spring-boot-starter/src/test/java/io/opengemini/client/spring/data/sample/TestApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package io.opengemini.client.spring.data.sample;
1818

19+
import io.opengemini.client.spring.data.annotation.MeasurementScan;
1920
import org.springframework.boot.SpringApplication;
2021
import org.springframework.boot.autoconfigure.SpringBootApplication;
2122

2223
@SpringBootApplication
24+
@MeasurementScan("io.opengemini.client.spring.data.sample.measurement")
2325
public class TestApplication {
2426

2527
public static void main(String[] args) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2024 openGemini Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opengemini.client.spring.data.sample.measurement;
18+
19+
import io.opengemini.client.api.Precision;
20+
import io.opengemini.client.spring.data.annotation.Database;
21+
import io.opengemini.client.spring.data.annotation.Field;
22+
import io.opengemini.client.spring.data.annotation.Measurement;
23+
import io.opengemini.client.spring.data.annotation.RetentionPolicy;
24+
import io.opengemini.client.spring.data.annotation.Tag;
25+
import io.opengemini.client.spring.data.annotation.Time;
26+
import lombok.Getter;
27+
import lombok.Setter;
28+
29+
@Database(name = "weather_db_auto_create")
30+
@RetentionPolicy(name = "weather_rp_auto_create", duration = "3d", shardGroupDuration = "1h", indexDuration = "7h")
31+
@Measurement(name = "weather_ms")
32+
@Getter
33+
@Setter
34+
public class WeatherFixNameAutoCreate {
35+
36+
@Tag(name = "Location")
37+
private String location;
38+
39+
@Field(name = "Temperature")
40+
private Double temperature;
41+
42+
@Time(precision = Precision.PRECISIONMILLISECOND)
43+
private Long time;
44+
45+
}

spring/opengemini-spring/src/main/java/io/opengemini/client/spring/data/annotation/MeasurementScan.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package io.opengemini.client.spring.data.annotation;
1818

19+
import io.opengemini.client.spring.data.core.MeasurementScanRegistrar;
20+
import org.springframework.context.annotation.Import;
1921
import org.springframework.core.annotation.AliasFor;
2022

2123
import java.lang.annotation.ElementType;
@@ -25,6 +27,7 @@
2527

2628
@Retention(RetentionPolicy.RUNTIME)
2729
@Target(ElementType.TYPE)
30+
@Import(MeasurementScanRegistrar.class)
2831
public @interface MeasurementScan {
2932

3033
/**
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2024 openGemini Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opengemini.client.spring.data.core;
18+
19+
import lombok.Getter;
20+
import lombok.Setter;
21+
import org.springframework.beans.factory.InitializingBean;
22+
import org.springframework.util.Assert;
23+
24+
import java.util.List;
25+
26+
@Setter
27+
@Getter
28+
public class MeasurementScanConfigurer implements InitializingBean {
29+
30+
private List<String> basePackages;
31+
32+
@Override
33+
public void afterPropertiesSet() {
34+
Assert.notEmpty(basePackages, "Property 'basePackage' is required");
35+
}
36+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2024 openGemini Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opengemini.client.spring.data.core;
18+
19+
import io.opengemini.client.api.RpConfig;
20+
import io.opengemini.client.spring.data.annotation.Database;
21+
import io.opengemini.client.spring.data.annotation.Measurement;
22+
import io.opengemini.client.spring.data.annotation.RetentionPolicy;
23+
import org.springframework.beans.factory.InitializingBean;
24+
import org.springframework.beans.factory.config.BeanDefinition;
25+
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
26+
import org.springframework.core.type.filter.AnnotationTypeFilter;
27+
import org.springframework.util.ClassUtils;
28+
import org.springframework.util.StringUtils;
29+
30+
import java.util.HashSet;
31+
import java.util.Set;
32+
33+
public class MeasurementScanInitializer implements InitializingBean {
34+
35+
private final OpenGeminiTemplate openGeminiTemplate;
36+
private final MeasurementScanConfigurer measurementScanConfigurer;
37+
38+
public MeasurementScanInitializer(OpenGeminiTemplate openGeminiTemplate, MeasurementScanConfigurer configurer) {
39+
this.openGeminiTemplate = openGeminiTemplate;
40+
this.measurementScanConfigurer = configurer;
41+
}
42+
43+
@Override
44+
public void afterPropertiesSet() {
45+
Set<Class<?>> measurementClassSet = new HashSet<>();
46+
if (measurementScanConfigurer != null) {
47+
for (String basePackage : measurementScanConfigurer.getBasePackages()) {
48+
scanForMeasurementClass(basePackage, measurementClassSet);
49+
}
50+
}
51+
initDatabase(measurementClassSet);
52+
}
53+
54+
private void scanForMeasurementClass(String basePackage, Set<Class<?>> measurementClassSet) {
55+
if (!StringUtils.hasText(basePackage)) {
56+
return;
57+
}
58+
59+
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
60+
provider.addIncludeFilter(new AnnotationTypeFilter(Measurement.class));
61+
ClassLoader classLoader = this.getClass().getClassLoader();
62+
for (BeanDefinition candidate : provider.findCandidateComponents(basePackage)) {
63+
String beanClassName = candidate.getBeanClassName();
64+
if (beanClassName != null) {
65+
try {
66+
measurementClassSet.add(ClassUtils.forName(beanClassName, classLoader));
67+
} catch (ClassNotFoundException | LinkageError ignored) {
68+
}
69+
}
70+
}
71+
}
72+
73+
private void initDatabase(Set<Class<?>> measurementClassSet) {
74+
for (Class<?> clazz : measurementClassSet) {
75+
Database dbAnnotation = clazz.getAnnotation(Database.class);
76+
if (dbAnnotation == null || !dbAnnotation.create()) {
77+
continue;
78+
}
79+
String database = dbAnnotation.name();
80+
openGeminiTemplate.createDatabaseIfAbsent(database);
81+
82+
RetentionPolicy rpAnnotation = clazz.getAnnotation(RetentionPolicy.class);
83+
if (rpAnnotation == null || !rpAnnotation.create()) {
84+
continue;
85+
}
86+
87+
RpConfig rpConfig = new RpConfig(rpAnnotation.name(), rpAnnotation.duration(),
88+
rpAnnotation.shardGroupDuration(), rpAnnotation.indexDuration());
89+
openGeminiTemplate.createRetentionPolicyIfAbsent(database, rpConfig, rpAnnotation.isDefault());
90+
}
91+
}
92+
93+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024 openGemini Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opengemini.client.spring.data.core;
18+
19+
import io.opengemini.client.spring.data.annotation.MeasurementScan;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
22+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
23+
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
24+
import org.springframework.core.annotation.AnnotationAttributes;
25+
import org.springframework.core.type.AnnotationMetadata;
26+
import org.springframework.util.StringUtils;
27+
28+
import java.util.Arrays;
29+
import java.util.List;
30+
import java.util.stream.Collectors;
31+
32+
public class MeasurementScanRegistrar implements ImportBeanDefinitionRegistrar {
33+
34+
@Override
35+
public void registerBeanDefinitions(@NotNull AnnotationMetadata importingClassMetadata,
36+
@NotNull BeanDefinitionRegistry registry) {
37+
AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(
38+
importingClassMetadata.getAnnotationAttributes(MeasurementScan.class.getName()));
39+
if (mapperScanAttrs != null) {
40+
String beanName = generateBaseBeanName(importingClassMetadata);
41+
registerBeanDefinitions(mapperScanAttrs, registry, beanName);
42+
}
43+
}
44+
45+
private String generateBaseBeanName(AnnotationMetadata importingClassMetadata) {
46+
return importingClassMetadata.getClassName() + "#" + MeasurementScanRegistrar.class.getSimpleName();
47+
}
48+
49+
private void registerBeanDefinitions(AnnotationAttributes annoAttrs,
50+
BeanDefinitionRegistry registry,
51+
String beanName) {
52+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MeasurementScanConfigurer.class);
53+
54+
List<String> basePackages = Arrays.stream(annoAttrs.getStringArray("basePackages"))
55+
.filter(StringUtils::hasText)
56+
.collect(Collectors.toList());
57+
builder.addPropertyValue("basePackages", basePackages);
58+
59+
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
60+
}
61+
62+
}

0 commit comments

Comments
 (0)