diff --git a/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java b/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java new file mode 100644 index 00000000..14ab57fc --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java @@ -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解析器 + * @Author:lqh + * @Date:2024/6/14 10:32 + */ +public class MapCodec implements Codec> { + + private static final Logger LOGGER = Logger.getLogger(MapCodec.class.getName()); + + private final Class> encoderClass; + private final Codec keyCodec; + private final Codec valueCodec; + + MapCodec(final Class> encoderClass, final Codec keyCodec, final Codec valueCodec, + Map, Class> map) { + this.encoderClass = encoderClass; + this.keyCodec = keyCodec; + this.valueCodec = valueCodec; + + map.forEach(PropertyEditorManager::registerEditor); + } + + MapCodec(final Class> encoderClass, final Codec keyCodec, final Codec valueCodec) { + this.encoderClass = encoderClass; + this.keyCodec = keyCodec; + this.valueCodec = valueCodec; + } + + @Override + public void encode(final BsonWriter writer, final Map map, final EncoderContext encoderContext) { + try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) { + dummyWriter.writeStartDocument(); + writer.writeStartDocument(); + + for (final Map.Entry 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 decode(final BsonReader reader, final DecoderContext context) { + reader.readStartDocument(); + Map 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> getEncoderClass() { + return encoderClass; + } + + private Map getInstance() { + if (encoderClass.isInterface()) { + return new HashMap<>(); + } + try { + return encoderClass.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + } +} diff --git a/orm/src/main/java/com/zfoo/orm/codec/MapCodecProvider.java b/orm/src/main/java/com/zfoo/orm/codec/MapCodecProvider.java new file mode 100644 index 00000000..0fba2cbf --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/codec/MapCodecProvider.java @@ -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; + +/** + * + * @Author:lqh + * @Date:2024/6/14 10:23 + */ +public class MapCodecProvider implements PropertyCodecProvider { + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public Codec get(final TypeWithTypeParameters 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; + } +} \ No newline at end of file diff --git a/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java b/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java index e8f71ac3..57e08e73 100644 --- a/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java +++ b/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java @@ -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)) { diff --git a/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java b/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java index 19d61541..02fd27c1 100644 --- a/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java +++ b/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java @@ -60,6 +60,11 @@ public class MapTest { map.put("a", Map.of("b", "b")); entity.setBaseMap(map); + var longStringHashMap = new HashMap(); + 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); diff --git a/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java b/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java index 22ec12bd..fe81b458 100644 --- a/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java +++ b/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java @@ -31,6 +31,8 @@ public class MapEntity implements IEntity { private Map> baseMap = new HashMap<>(); + private Map longStringMap = new HashMap<>(); + @Override public Long id() { return id; @@ -60,6 +62,14 @@ public class MapEntity implements IEntity { this.baseMap = baseMap; } + public Map getLongStringMap() { + return longStringMap; + } + + public void setLongStringMap(Map longStringMap) { + this.longStringMap = longStringMap; + } + @Override public boolean equals(Object o) { if (this == o) return true;