forked from openGemini/opengemini-client-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add spring pojo serializer (openGemini#111)
Signed-off-by: weiping-code <[email protected]>
- Loading branch information
1 parent
9d8f433
commit 965b7a5
Showing
6 changed files
with
289 additions
and
1 deletion.
There are no files selected for viewing
175 changes: 175 additions & 0 deletions
175
...ring/src/main/java/io/opengemini/client/spring/data/core/DefaultOpenGeminiSerializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package io.opengemini.client.spring.data.core; | ||
|
||
import io.opengemini.client.api.OpenGeminiException; | ||
import io.opengemini.client.api.Point; | ||
import io.opengemini.client.api.Precision; | ||
import io.opengemini.client.spring.data.annotation.Measurement; | ||
import io.opengemini.client.spring.data.annotation.Tag; | ||
import io.opengemini.client.spring.data.annotation.Time; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
public class DefaultOpenGeminiSerializer<T> implements OpenGeminiSerializer<T> { | ||
|
||
private final ClassMetaData classMetaData; | ||
|
||
private DefaultOpenGeminiSerializer(ClassMetaData classMetaData) { | ||
this.classMetaData = classMetaData; | ||
} | ||
|
||
public static <T> DefaultOpenGeminiSerializer<T> of(Class<T> clazz) { | ||
ClassMetaData classMetaData = parseClassMetaData(clazz); | ||
return new DefaultOpenGeminiSerializer<>(classMetaData); | ||
} | ||
|
||
private static <T> ClassMetaData parseClassMetaData(Class<T> clazz) { | ||
Measurement msAnnotation = clazz.getAnnotation(Measurement.class); | ||
if (msAnnotation == null) { | ||
throw new IllegalArgumentException("Class " + clazz.getName() + " has no @Measurement annotation"); | ||
} | ||
|
||
ClassMetaData classMetaData = new ClassMetaData(msAnnotation.name()); | ||
for (Field field : clazz.getDeclaredFields()) { | ||
Tag tagAnnotation = field.getAnnotation(Tag.class); | ||
if (tagAnnotation != null) { | ||
if (!String.class.equals(field.getType())) { | ||
throw new IllegalArgumentException( | ||
"The " + field.getName() + " field type annotated with @Tag in Class " + clazz.getName() | ||
+ " should have a data type of String"); | ||
} | ||
classMetaData.putTag(tagAnnotation, field); | ||
continue; | ||
} | ||
|
||
io.opengemini.client.spring.data.annotation.Field fieldAnnotation = field.getAnnotation( | ||
io.opengemini.client.spring.data.annotation.Field.class); | ||
if (fieldAnnotation != null) { | ||
classMetaData.putField(fieldAnnotation, field); | ||
continue; | ||
} | ||
|
||
Time timeAnnotation = field.getAnnotation(Time.class); | ||
if (timeAnnotation != null) { | ||
classMetaData.putTime(timeAnnotation, field); | ||
} | ||
} | ||
return classMetaData; | ||
} | ||
|
||
@Override | ||
public Point serialize(T t) throws OpenGeminiException { | ||
Point point = new Point(); | ||
point.setMeasurement(classMetaData.measurementName); | ||
point.setFields(new HashMap<>()); | ||
point.setTags(new HashMap<>()); | ||
|
||
try { | ||
for (Map.Entry<String, FieldMetaData> entry : classMetaData.fieldMap.entrySet()) { | ||
String columnName = entry.getKey(); | ||
FieldMetaData fieldMetaData = entry.getValue(); | ||
switch (fieldMetaData.fieldType) { | ||
case TAG: | ||
point.getTags().put(columnName, fieldMetaData.parseTagValue(t)); | ||
break; | ||
case FIELD: | ||
point.getFields().put(columnName, fieldMetaData.parseFieldValue(t)); | ||
break; | ||
case TIME: | ||
point.setTime(fieldMetaData.parseTimeValue(t)); | ||
point.setPrecision(fieldMetaData.timePrecision); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
return point; | ||
} catch (IllegalAccessException e) { | ||
throw new OpenGeminiException(e); | ||
} | ||
} | ||
|
||
private enum FieldType { | ||
TAG, FIELD, TIME | ||
} | ||
|
||
private static class ClassMetaData { | ||
private final String measurementName; | ||
private final Map<String, FieldMetaData> fieldMap; | ||
|
||
public ClassMetaData(String measurementName) { | ||
this.measurementName = measurementName; | ||
this.fieldMap = new HashMap<>(); | ||
} | ||
|
||
public void putTag(Tag tagAnnotation, Field field) { | ||
field.setAccessible(true); | ||
fieldMap.put(getTagName(tagAnnotation, field), FieldMetaData.tag(field)); | ||
} | ||
|
||
public void putField(io.opengemini.client.spring.data.annotation.Field fieldAnnotation, Field field) { | ||
field.setAccessible(true); | ||
fieldMap.put(getFieldName(fieldAnnotation, field), FieldMetaData.field(field)); | ||
} | ||
|
||
public void putTime(Time timeAnnotation, Field field) { | ||
field.setAccessible(true); | ||
fieldMap.put("time", FieldMetaData.time(field, timeAnnotation.precision())); | ||
} | ||
|
||
private String getTagName(Tag tagAnnotation, Field field) { | ||
return tagAnnotation.name().isEmpty() ? field.getName() : tagAnnotation.name(); | ||
} | ||
|
||
private String getFieldName(io.opengemini.client.spring.data.annotation.Field fieldAnnotation, Field field) { | ||
return fieldAnnotation.name().isEmpty() ? field.getName() : fieldAnnotation.name(); | ||
} | ||
} | ||
|
||
private static class FieldMetaData { | ||
private Field field; | ||
private FieldType fieldType; | ||
private Precision timePrecision; | ||
|
||
public static FieldMetaData tag(Field field) { | ||
FieldMetaData fieldMetaData = new FieldMetaData(); | ||
fieldMetaData.field = field; | ||
fieldMetaData.fieldType = FieldType.TAG; | ||
return fieldMetaData; | ||
} | ||
|
||
public static FieldMetaData field(Field field) { | ||
FieldMetaData fieldMetaData = new FieldMetaData(); | ||
fieldMetaData.field = field; | ||
fieldMetaData.fieldType = FieldType.FIELD; | ||
return fieldMetaData; | ||
} | ||
|
||
public static FieldMetaData time(Field field, Precision timePrecision) { | ||
FieldMetaData fieldMetaData = new FieldMetaData(); | ||
fieldMetaData.field = field; | ||
fieldMetaData.fieldType = FieldType.TIME; | ||
fieldMetaData.timePrecision = timePrecision; | ||
return fieldMetaData; | ||
} | ||
|
||
public String parseTagValue(Object obj) throws IllegalAccessException { | ||
return Objects.toString(field.get(obj), null); | ||
} | ||
|
||
public Object parseFieldValue(Object obj) throws IllegalAccessException { | ||
return field.get(obj); | ||
} | ||
|
||
public long parseTimeValue(Object obj) throws IllegalAccessException { | ||
Object fieldValue = field.get(obj); | ||
if (fieldValue instanceof Long) { | ||
return (Long) fieldValue; | ||
} | ||
return timePrecision.getTimeUnit().convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...mini-spring/src/main/java/io/opengemini/client/spring/data/core/OpenGeminiSerializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package io.opengemini.client.spring.data.core; | ||
|
||
import io.opengemini.client.api.OpenGeminiException; | ||
import io.opengemini.client.api.Point; | ||
|
||
/** | ||
* The serializer between POJO and Point | ||
* | ||
* @param <T> class type of POJO annotated by Measurement | ||
*/ | ||
public interface OpenGeminiSerializer<T> { | ||
Point serialize(T t) throws OpenGeminiException; | ||
} |
49 changes: 49 additions & 0 deletions
49
.../src/test/java/io/opengemini/client/spring/data/core/DefaultOpenGeminiSerializerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package io.opengemini.client.spring.data.core; | ||
|
||
import io.opengemini.client.api.Point; | ||
import io.opengemini.client.spring.data.sample.measurement.WeatherFixNameNoCreate; | ||
import io.opengemini.client.spring.data.sample.measurement.WeatherNoAnnotation; | ||
import io.opengemini.client.spring.data.sample.measurement.WeatherTagNotString; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.function.Executable; | ||
|
||
class DefaultOpenGeminiSerializerTest { | ||
|
||
@Test | ||
void serializer_should_fail_when_pojo_not_annotated() { | ||
Executable executable = () -> DefaultOpenGeminiSerializer.of(WeatherNoAnnotation.class); | ||
IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, executable); | ||
Assertions.assertEquals( | ||
"Class io.opengemini.client.spring.data.sample.measurement.WeatherNoAnnotation has no @Measurement " | ||
+ "annotation", exception.getMessage()); | ||
} | ||
|
||
@Test | ||
void serializer_should_fail_when_tag_not_string() { | ||
Executable executable = () -> DefaultOpenGeminiSerializer.of(WeatherTagNotString.class); | ||
IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, executable); | ||
Assertions.assertEquals( | ||
"The location field type annotated with @Tag in Class io.opengemini.client.spring.data.sample" | ||
+ ".measurement.WeatherTagNotString should have a data type of String", exception.getMessage()); | ||
} | ||
|
||
@Test | ||
void serialize_should_success_when_pojo_annotated() throws Exception { | ||
OpenGeminiSerializer<WeatherFixNameNoCreate> serializer = DefaultOpenGeminiSerializer.of( | ||
WeatherFixNameNoCreate.class); | ||
|
||
WeatherFixNameNoCreate weather = new WeatherFixNameNoCreate(); | ||
weather.setLocation("shenzhen"); | ||
weather.setTemperature(28.5D); | ||
weather.setTime(1725371248720L); | ||
|
||
Point point = serializer.serialize(weather); | ||
Assertions.assertNotNull(point); | ||
Assertions.assertEquals("testms", point.getMeasurement()); | ||
Assertions.assertEquals("shenzhen", point.getTags().get("Location")); | ||
Assertions.assertEquals(28.5D, point.getFields().get("Temperature")); | ||
Assertions.assertEquals(weather.getTime(), point.getTime()); | ||
Assertions.assertEquals("testms,Location=shenzhen Temperature=28.5 1725371248720000000", point.lineProtocol()); | ||
} | ||
} |
1 change: 0 additions & 1 deletion
1
...e/measurement/WeatherFixNameNoCreate.java → ...e/measurement/WeatherFixNameNoCreate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
...rc/test/java/io/opengemini/client/spring/data/sample/measurement/WeatherNoAnnotation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package io.opengemini.client.spring.data.sample.measurement; | ||
|
||
import io.opengemini.client.api.Precision; | ||
import io.opengemini.client.spring.data.annotation.Field; | ||
import io.opengemini.client.spring.data.annotation.Tag; | ||
import io.opengemini.client.spring.data.annotation.Time; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class WeatherNoAnnotation { | ||
|
||
@Tag(name = "Location") | ||
private String location; | ||
|
||
@Field(name = "Temperature") | ||
private Double temperature; | ||
|
||
@Time(precision = Precision.PRECISIONMILLISECOND) | ||
private Long time; | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
...rc/test/java/io/opengemini/client/spring/data/sample/measurement/WeatherTagNotString.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
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 = "testdb", create = false) | ||
@RetentionPolicy(name = "testrp", create = false) | ||
@Measurement(name = "testms") | ||
@Getter | ||
@Setter | ||
public class WeatherTagNotString { | ||
|
||
@Tag(name = "Location") | ||
private Long location; | ||
|
||
@Field(name = "Temperature") | ||
private Double temperature; | ||
|
||
@Time(precision = Precision.PRECISIONMILLISECOND) | ||
private Long time; | ||
|
||
} |