Skip to content

Commit

Permalink
feat: add spring pojo serializer (openGemini#111)
Browse files Browse the repository at this point in the history
Signed-off-by: weiping-code <[email protected]>
  • Loading branch information
weiping-code authored Sep 5, 2024
1 parent 9d8f433 commit 965b7a5
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 1 deletion.
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);
}
}
}
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;
}
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());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
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;
Expand Down
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;

}
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;

}

0 comments on commit 965b7a5

Please sign in to comment.