Skip to content

Commit

Permalink
feat: support mongo (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
YongwuHe authored Sep 4, 2023
1 parent a48e754 commit a52b7f4
Show file tree
Hide file tree
Showing 24 changed files with 1,178 additions and 37 deletions.
5 changes: 5 additions & 0 deletions arex-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
<artifactId>arex-database-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>arex-database-mongo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>arex-redis-common</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
package io.arex.inst.runtime.serializer;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


public interface StringSerializable {
List<String> MYBATIS_PLUS_CLASS_LIST = Arrays.asList(
"com.baomidou.mybatisplus.core.conditions.query.QueryWrapper",
"com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper",
"com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper",
"com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper");

List<String> TK_MYBATIS_PLUS_CLASS_LIST = Collections.singletonList(
"tk.mybatis.mapper.entity.EntityColumn");

List<String> MONGO_CLASS_LIST = Arrays.asList(
"com.mongodb.internal.operation.QueryBatchCursor",
"com.mongodb.operation.QueryBatchCursor",
"com.mongodb.internal.operation.QueryBatchCursor$ResourceManager"
);

List<String> MONGO_FIELD_LIST = Arrays.asList(
"nextBatch",
"serverCursor",
"serverAddress",
"resourceManager",
"state"
);

String name();

Expand Down Expand Up @@ -42,4 +67,23 @@ public interface StringSerializable {
default boolean isDefault() {
return false;
}

/**
* Method for adding serializer to handle values of Map type.
*/
default void addMapSerializer(Class<?> clazz) {
addTypeSerializer(clazz, null);
}

/**
* Method for adding serializer to handle values of specific type.
* @param clazz class to handle
* @param typeSerializer serializer to use.
* Gson: com.google.gson.JsonSerializer && com.google.gson.JsonDeserializer ,
* Jackson: com.fasterxml.jackson.databind.JsonSerializer && com.fasterxml.jackson.databind.JsonDeserializer
*/
default void addTypeSerializer(Class<?> clazz, Object typeSerializer) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,12 @@ private static String genericTypeToString(Object result) {
String cacheKey = rawClassName + typeName;
Field field = GENERIC_FIELD_CACHE.get(cacheKey);
if (field == null) {
for (Field declaredField : rawClass.getDeclaredFields()) {
// java.util.List<T> contains T
if (declaredField.getGenericType().getTypeName().contains(typeName)) {
declaredField.setAccessible(true);
GENERIC_FIELD_CACHE.put(cacheKey, declaredField);
field = declaredField;
break;
}
field = getGenericFieldFromClass(rawClass, typeName);
if (field == null) {
return builder.toString();
}
field.setAccessible(true);
GENERIC_FIELD_CACHE.put(cacheKey, field);
}
builder.append(filterRawGenericType(invokeGetFieldType(field, result)));
if (i == typeParameters.length - 1) {
Expand All @@ -140,6 +137,25 @@ private static String genericTypeToString(Object result) {
return builder.toString();
}

private static Field getGenericFieldFromClass(Class<?> rawClass, String typeName) {
if (rawClass == null) {
return null;
}
for (Field declaredField : rawClass.getDeclaredFields()) {
final String fieldGenericType = declaredField.getGenericType().getTypeName();
// equals T
if (fieldGenericType.equals(typeName)) {
return declaredField;
}
// java.util.List<T> contains T && field is collection
if (fieldGenericType.contains(typeName) && isCollection(declaredField.getType().getName())) {
return declaredField;
}
}
// search super class
return getGenericFieldFromClass(rawClass.getSuperclass(), typeName);
}

private static String invokeGetFieldType(Field field, Object result) {
if (field == null || result == null) {
return null;
Expand Down Expand Up @@ -325,22 +341,27 @@ private static String filterRawGenericType(String genericType) {
// this case means that the generic field is not assigned a value
return StringUtil.EMPTY;
}
StringBuilder builder = new StringBuilder();
StringBuilder builder = null;
String[] types = StringUtil.split(genericType, HORIZONTAL_LINE);
for (String type : types) {
if (StringUtil.isNullWord(type) || isCollection(type)) {
continue;
}
if (builder == null) {
builder = new StringBuilder();
}
builder.append(HORIZONTAL_LINE).append(type);
}
return builder.substring(1);
return builder == null ? StringUtil.EMPTY : builder.substring(1);
}

public static boolean isCollection(String genericType) {
if (StringUtil.isEmpty(genericType)) {
return false;
}
switch (genericType) {
case "java.util.List":
case "java.util.Set":
case "java.util.ArrayList":
case "java.util.LinkedList":
case "java.util.HashSet":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Stream;

Expand Down Expand Up @@ -352,10 +353,41 @@ void testMapToString() {
assertEquals(Integer2String.class.getName(), type2.getTypeName());
}

@Test
void testGenericFieldInFather() {
final ChildClass<Object> childClass = new ChildClass<>();
final ArrayList<Object> list = new ArrayList<>();
childClass.setValue(list);
String name = TypeUtil.getName(childClass);
assertEquals("io.arex.inst.runtime.util.TypeUtilTest$ChildClass-", name);
list.add("test");
childClass.setValue(list);
name = TypeUtil.getName(childClass);
assertEquals("io.arex.inst.runtime.util.TypeUtilTest$ChildClass-java.lang.String", name);
}


static class SingleTypeMap<V> extends HashMap<Integer, V> {
}

static class Integer2String extends HashMap<Integer, String> {
}

static class ChildClass<T> extends ParentClass<T> {
private String childValue;
public ChildClass() {

}
}

static class ParentClass<T> {
private List<T> value;
public ParentClass() {

}

public void setValue(List<T> value) {
this.value = value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
import com.google.gson.Gson;
import com.google.gson.JsonDeserializer;

import io.arex.inst.runtime.log.LogManager;
import io.arex.inst.runtime.serializer.StringSerializable;
import io.arex.inst.runtime.util.TypeUtil;
import java.sql.Time;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Map.Entry;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
Expand Down Expand Up @@ -151,11 +153,24 @@ public class GsonSerializer implements StringSerializable {
};

public static final GsonSerializer INSTANCE = new GsonSerializer();
private final Gson serializer;
private Gson serializer;
private GsonBuilder gsonBuilder;

@Override
public void addTypeSerializer(Class<?> clazz, Object typeSerializer) {
if (typeSerializer == null) {
// map<String, Object> custom serializer
this.gsonBuilder.registerTypeAdapter(clazz, new MapSerializer());
} else {
this.gsonBuilder.registerTypeAdapter(clazz, typeSerializer);
}
this.serializer = gsonBuilder.create();
}

public GsonSerializer() {
serializer = new GsonBuilder().registerTypeAdapterFactory(NumberTypeAdaptor.FACTORY)
.registerTypeAdapter(org.joda.time.DateTime.class, DATE_TIME_JSON_SERIALIZER)
.registerTypeAdapter(org.joda.time.DateTime.class, DATE_TIME_JSON_DESERIALIZER)
gsonBuilder = new GsonBuilder().registerTypeAdapterFactory(NumberTypeAdaptor.FACTORY)
.registerTypeAdapter(DateTime.class, DATE_TIME_JSON_SERIALIZER)
.registerTypeAdapter(DateTime.class, DATE_TIME_JSON_DESERIALIZER)
.registerTypeAdapter(org.joda.time.LocalDateTime.class, JODA_LOCAL_DATE_TIME_JSON_SERIALIZER)
.registerTypeAdapter(org.joda.time.LocalDateTime.class, JODA_LOCAL_DATE_TIME_JSON_DESERIALIZER)
.registerTypeAdapter(org.joda.time.LocalDate.class, JODA_LOCAL_DATE_JSON_SERIALIZER)
Expand Down Expand Up @@ -188,7 +203,64 @@ public GsonSerializer() {
.registerTypeAdapter(Class.class, CLASS_JSON_DESERIALIZER)
.enableComplexMapKeySerialization()
.setExclusionStrategies(new ExcludeField())
.disableHtmlEscaping().create();
.disableHtmlEscaping();
serializer = gsonBuilder.create();
}


static class MapSerializer implements JsonSerializer<Map<String, Object>>, JsonDeserializer<Map<String, Object>> {

private static final Character SEPARATOR = '-';

@Override
public JsonElement serialize(Map<String, Object> document, Type type, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
for (Map.Entry<String, Object> entry : document.entrySet()) {
final Object value = entry.getValue();

if (value == null) {
jsonObject.add(entry.getKey(), null);
continue;
}

if (value instanceof String) {
jsonObject.addProperty(entry.getKey(), (String) value);
continue;
}
jsonObject.add(entry.getKey() + SEPARATOR + TypeUtil.getName(value),
context.serialize(value));
}
return jsonObject;
}

@Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {

final JsonObject jsonObject = json.getAsJsonObject();
try {
// only support no-arg constructor
final Map<String, Object> map = (Map<String, Object>) ((Class) typeOfT).getDeclaredConstructor(null).newInstance();
for (Entry<String, JsonElement> entry : jsonObject.entrySet()) {
final String[] split = StringUtil.splitByFirstSeparator(entry.getKey(), SEPARATOR);
if (split.length < 2) {
map.put(entry.getKey(), context.deserialize(entry.getValue(), String.class));
continue;
}
String valueClazz = split[1];
String key = split[0];
final JsonElement valueJson = entry.getValue();
final Type valueType = TypeUtil.forName(valueClazz);
final Object value = context.deserialize(valueJson, valueType);
map.put(key, value);
}
return map;
} catch (Exception e) {
LogManager.warn("MapSerializer.deserialize", e);
return null;
}

}
}

@Override
Expand Down Expand Up @@ -234,6 +306,9 @@ public boolean shouldSkipField(FieldAttributes f) {
return true;
}
String className = f.getDeclaringClass().getName();
if (MONGO_CLASS_LIST.contains(className) && !MONGO_FIELD_LIST.contains(fieldName)) {
return true;
}
List<String> fieldNameList = JacksonSerializer.INSTANCE.getSkipFieldNameList(className);

if (fieldNameList == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ public final class JacksonSerializer implements StringSerializable {
public static final String EXTENSION = "json";

private static final String SKIP_INFO_LIST_TYPE = "java.util.ArrayList-io.arex.inst.runtime.model.SerializeSkipInfo";
private static List<String> MYBATIS_PLUS_CLASS_LIST = Arrays.asList(
"com.baomidou.mybatisplus.core.conditions.query.QueryWrapper",
"com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper",
"com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper",
"com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper");

private static List<String> TK_MYBATIS_PLUS_CLASS_LIST = Arrays.asList(
"tk.mybatis.mapper.entity.EntityColumn");

private static final Logger LOGGER = LoggerFactory.getLogger(JacksonSerializer.class);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
package io.arex.foundation.serializer;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;

import io.arex.foundation.internal.MockEntityBuffer;
import com.google.gson.internal.LinkedTreeMap;
import io.arex.inst.runtime.util.TypeUtil;
import java.sql.Time;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;

import javax.xml.datatype.DatatypeConfigurationException;

class GsonSerializerTest {

@Test
Expand Down Expand Up @@ -116,4 +110,28 @@ void deserializeType() {
String json = GsonSerializer.INSTANCE.serialize(LocalDateTime.now());
assertNotNull(GsonSerializer.INSTANCE.deserialize(json, TypeUtil.forName(TypeUtil.getName(LocalDateTime.now()))));
}

@Test
void testAddCustomSerializer() {
Map<String, Object> map = new LinkedTreeMap<>();
GsonSerializer.INSTANCE.addTypeSerializer(LinkedTreeMap.class, null);
// empty map
String json = GsonSerializer.INSTANCE.serialize(map);
assertEquals("{}", json);
final LinkedTreeMap deserialize = GsonSerializer.INSTANCE.deserialize(json, LinkedTreeMap.class);
assertEquals(map, deserialize);


map.put("key", "value");
map.put("long", 2L);
json = GsonSerializer.INSTANCE.serialize(map);
assertEquals("{\"key\":\"value\",\"long-java.lang.Long\":2}", json);
final LinkedTreeMap deserialize1 = GsonSerializer.INSTANCE.deserialize(json, LinkedTreeMap.class);
assertEquals(map, deserialize1);

// value is null
map.put("null", null);
json = GsonSerializer.INSTANCE.serialize(map);
assertEquals("{\"key\":\"value\",\"long-java.lang.Long\":2}", json);
}
}
Loading

0 comments on commit a52b7f4

Please sign in to comment.