feat[orm]: Fix Maps MUST have string keys

This commit is contained in:
awake
2024-06-14 11:33:39 +08:00
parent f7e6e51edd
commit 5e1fd3d818
5 changed files with 165 additions and 4 deletions
@@ -0,0 +1,117 @@
package com.zfoo.orm.codec;
import org.bson.*;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.json.JsonReader;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* map解析器
* @Authorlqh
* @Date2024/6/14 10:32
*/
public class MapCodec<K, T> implements Codec<Map<K, T>> {
private static final Logger LOGGER = Logger.getLogger(MapCodec.class.getName());
private final Class<Map<K, T>> encoderClass;
private final Codec<K> keyCodec;
private final Codec<T> valueCodec;
MapCodec(final Class<Map<K, T>> encoderClass, final Codec<K> keyCodec, final Codec<T> valueCodec,
Map<Class<?>, Class<? extends PropertyEditor>> map) {
this.encoderClass = encoderClass;
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
map.forEach(PropertyEditorManager::registerEditor);
}
MapCodec(final Class<Map<K, T>> encoderClass, final Codec<K> keyCodec, final Codec<T> valueCodec) {
this.encoderClass = encoderClass;
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
}
@Override
public void encode(final BsonWriter writer, final Map<K, T> map, final EncoderContext encoderContext) {
try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) {
dummyWriter.writeStartDocument();
writer.writeStartDocument();
for (final Map.Entry<K, T> entry : map.entrySet()) {
PropertyEditor editor = PropertyEditorManager.findEditor(keyCodec.getEncoderClass());
if (editor != null) {
LOGGER.fine("Found PropertyEditor for class: " + keyCodec.getEncoderClass().getName());
editor.setValue(entry.getKey());
writer.writeName(editor.getAsText());
} else {
String dummyId = UUID.randomUUID().toString();
dummyWriter.writeName(dummyId);
keyCodec.encode(dummyWriter, entry.getKey(), encoderContext);
writer.writeName(dummyWriter.getDocument().asDocument().get(dummyId).asString().getValue());
}
valueCodec.encode(writer, entry.getValue(), encoderContext);
}
dummyWriter.writeEndDocument();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failed to encode map: " + map, e);
throw new IllegalArgumentException(e);
}
writer.writeEndDocument();
}
@Override
@SuppressWarnings("unchecked")
public Map<K, T> decode(final BsonReader reader, final DecoderContext context) {
reader.readStartDocument();
Map<K, T> map = getInstance();
while (!BsonType.END_OF_DOCUMENT.equals(reader.readBsonType())) {
K key;
PropertyEditor editor = PropertyEditorManager.findEditor(keyCodec.getEncoderClass());
if (editor != null) {
LOGGER.fine("Found PropertyEditor for class: " + keyCodec.getEncoderClass().getName());
editor.setAsText(reader.readName());
key = (K) editor.getValue();
} else {
var dummyReader = new JsonReader(String.format("\"key\": \"%s\"", reader.readName()));
key = keyCodec.decode(dummyReader, context);
}
map.put(key,
(!BsonType.NULL.equals(reader.getCurrentBsonType()))
? valueCodec.decode(reader, context)
: null);
}
reader.readEndDocument();
return map;
}
@Override
public Class<Map<K, T>> getEncoderClass() {
return encoderClass;
}
private Map<K, T> getInstance() {
if (encoderClass.isInterface()) {
return new HashMap<>();
}
try {
return encoderClass.getDeclaredConstructor().newInstance();
} catch (final Exception e) {
throw new CodecConfigurationException(e.getMessage(), e);
}
}
}
@@ -0,0 +1,27 @@
package com.zfoo.orm.codec;
import org.bson.codecs.Codec;
import org.bson.codecs.pojo.PropertyCodecProvider;
import org.bson.codecs.pojo.PropertyCodecRegistry;
import org.bson.codecs.pojo.TypeWithTypeParameters;
import java.util.Map;
/**
*
* @Authorlqh
* @Date2024/6/14 10:23
*/
public class MapCodecProvider implements PropertyCodecProvider {
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) {
if (Map.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 2) {
return new MapCodec(
type.getType(),
registry.get(type.getTypeParameters().get(0)),
registry.get(type.getTypeParameters().get(1)));
}
return null;
}
}
@@ -26,6 +26,7 @@ import com.zfoo.orm.OrmContext;
import com.zfoo.orm.anno.*;
import com.zfoo.orm.cache.EntityCache;
import com.zfoo.orm.cache.IEntityCache;
import com.zfoo.orm.codec.MapCodecProvider;
import com.zfoo.orm.config.OrmConfig;
import com.zfoo.orm.model.EntityDef;
import com.zfoo.orm.model.IEntity;
@@ -97,7 +98,8 @@ public class OrmManager implements IOrmManager {
CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build()));
CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true)
.register(new MapCodecProvider()).build()));
var mongoBuilder = MongoClientSettings
.builder()
@@ -541,9 +543,9 @@ public class OrmManager implements IOrmManager {
var keyType = types[0];
var valueType = types[1];
if (!String.class.isAssignableFrom((Class<?>) keyType)) {
throw new RunException("[class:{}] type declaration is incorrect, and the key type of the Map must be the String type", clazz.getCanonicalName());
}
// if (!String.class.isAssignableFrom((Class<?>) keyType)) {
// throw new RunException("[class:{}] type declaration is incorrect, and the key type of the Map must be the String type", clazz.getCanonicalName());
// }
checkSubEntity(clazz, valueType);
} else if (ObjectId.class.isAssignableFrom(fieldType)) {
@@ -60,6 +60,11 @@ public class MapTest {
map.put("a", Map.of("b", "b"));
entity.setBaseMap(map);
var longStringHashMap = new HashMap<Long, String>();
longStringHashMap.put(100L,"hello map1");
longStringHashMap.put(101L,"hello map2");
entity.setLongStringMap(longStringHashMap);
OrmContext.getAccessor().insert(entity);
var myEntity = OrmContext.getAccessor().load(1, MapEntity.class);
@@ -31,6 +31,8 @@ public class MapEntity implements IEntity<Long> {
private Map<String, Map<String, String>> baseMap = new HashMap<>();
private Map<Long, String> longStringMap = new HashMap<>();
@Override
public Long id() {
return id;
@@ -60,6 +62,14 @@ public class MapEntity implements IEntity<Long> {
this.baseMap = baseMap;
}
public Map<Long, String> getLongStringMap() {
return longStringMap;
}
public void setLongStringMap(Map<Long, String> longStringMap) {
this.longStringMap = longStringMap;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;