From 032ac8fbdd84cffd14129e3a3a74fa49669c2063 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 31 May 2021 09:54:55 +0200 Subject: [PATCH 1/2] Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index a6d5da9170..286a2d6caf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-GH-3660-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index 0033bd11d5..9d97629e3b 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-GH-3660-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index f62c8dc7f4..3e6f31cffd 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-GH-3660-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index c1efaea420..6a2d99b3e0 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-GH-3660-SNAPSHOT ../pom.xml From 538ba27b2260759307ae761376cfd6e29442a44a Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 31 May 2021 09:58:30 +0200 Subject: [PATCH 2/2] Fix conversion for types having a converter registered. Fixes: #3660 --- .../core/convert/MappingMongoConverter.java | 14 +- .../MappingMongoConverterUnitTests.java | 196 ++++++++++++++++++ 2 files changed, 206 insertions(+), 4 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 413ce2ce44..d41decf3ed 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -171,7 +171,7 @@ protected ConversionContext getConversionContext(ObjectPath path) { Assert.notNull(path, "ObjectPath must not be null"); - return new ConversionContext(path, this::readDocument, this::readCollectionOrArray, this::readMap, this::readDBRef, + return new ConversionContext(conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap, this::readDBRef, this::getPotentiallyConvertedSimpleRead); } @@ -1323,7 +1323,7 @@ protected Map readMap(ConversionContext context, Bson bson, Type } Object value = entry.getValue(); - map.put(key, context.convert(value, valueType)); + map.put(key, value == null ? value : context.convert(value, valueType)); } return map; @@ -1970,6 +1970,7 @@ public org.springframework.data.util.TypeInformation specialize(Cla */ protected static class ConversionContext { + private final org.springframework.data.convert.CustomConversions conversions; private final ObjectPath path; private final ContainerValueConverter documentConverter; private final ContainerValueConverter> collectionConverter; @@ -1977,10 +1978,11 @@ protected static class ConversionContext { private final ContainerValueConverter dbRefConverter; private final ValueConverter elementConverter; - ConversionContext(ObjectPath path, ContainerValueConverter documentConverter, + ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path, ContainerValueConverter documentConverter, ContainerValueConverter> collectionConverter, ContainerValueConverter mapConverter, ContainerValueConverter dbRefConverter, ValueConverter elementConverter) { + this.conversions = customConversions; this.path = path; this.documentConverter = documentConverter; this.collectionConverter = collectionConverter; @@ -2001,6 +2003,10 @@ public S convert(Object source, TypeInformation Assert.notNull(typeHint, "TypeInformation must not be null"); + if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) { + return (S) elementConverter.convert(source, typeHint); + } + if (source instanceof Collection) { Class rawType = typeHint.getType(); @@ -2046,7 +2052,7 @@ public ConversionContext withPath(ObjectPath currentPath) { Assert.notNull(currentPath, "ObjectPath must not be null"); - return new ConversionContext(currentPath, documentConverter, collectionConverter, mapConverter, dbRefConverter, + return new ConversionContext(conversions, currentPath, documentConverter, collectionConverter, mapConverter, dbRefConverter, elementConverter); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index bd3e98788f..441b8e3347 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -81,6 +81,8 @@ import org.springframework.data.mongodb.core.mapping.Unwrapped; import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.test.util.ReflectionTestUtils; import com.mongodb.BasicDBList; @@ -2428,6 +2430,98 @@ void shouldUseMostConcreteCustomConversionTargetOnRead() { verify(subTypeOfGenericTypeConverter).convert(eq(source)); } + + @Test // GH-3660 + void usesCustomConverterForMapTypesOnWrite() { + + + converter = new MappingMongoConverter(resolver, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerConverter(new TypeImplementingMapToDocumentConverter()); + })); + converter.afterPropertiesSet(); + + TypeImplementingMap source = new TypeImplementingMap("one", 2); + org.bson.Document target = new org.bson.Document(); + + converter.write(source, target); + + assertThat(target).containsEntry("1st", "one").containsEntry("2nd", 2); + } + + @Test // GH-3660 + void usesCustomConverterForTypesImplementingMapOnWrite() { + + converter = new MappingMongoConverter(resolver, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerConverter(new TypeImplementingMapToDocumentConverter()); + })); + converter.afterPropertiesSet(); + + TypeImplementingMap source = new TypeImplementingMap("one", 2); + org.bson.Document target = new org.bson.Document(); + + converter.write(source, target); + + assertThat(target).containsEntry("1st", "one").containsEntry("2nd", 2); + } + + @Test // GH-3660 + void usesCustomConverterForTypesImplementingMapOnRead() { + + converter = new MappingMongoConverter(resolver, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerConverter(new DocumentToTypeImplementingMapConverter()); + })); + converter.afterPropertiesSet(); + + org.bson.Document source = new org.bson.Document("1st", "one") + .append("2nd", 2) + .append("_class", TypeImplementingMap.class.getName()); + + TypeImplementingMap target = converter.read(TypeImplementingMap.class, source); + + assertThat(target).isEqualTo(new TypeImplementingMap("one", 2)); + } + + @Test // GH-3660 + void usesCustomConverterForPropertiesUsingTypesThatImplementMapOnWrite() { + + converter = new MappingMongoConverter(resolver, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerConverter(new TypeImplementingMapToDocumentConverter()); + })); + converter.afterPropertiesSet(); + + TypeWrappingTypeImplementingMap source = new TypeWrappingTypeImplementingMap(); + source.typeImplementingMap = new TypeImplementingMap("one", 2); + org.bson.Document target = new org.bson.Document(); + + converter.write(source, target); + + assertThat(target).containsEntry("typeImplementingMap", new org.bson.Document("1st", "one").append("2nd", 2)); + } + + @Test // GH-3660 + void usesCustomConverterForPropertiesUsingTypesImplementingMapOnRead() { + + converter = new MappingMongoConverter(resolver, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerConverter(new DocumentToTypeImplementingMapConverter()); + })); + converter.afterPropertiesSet(); + + org.bson.Document source = new org.bson.Document("typeImplementingMap", + new org.bson.Document("1st", "one") + .append("2nd", 2)) + .append("_class", TypeWrappingTypeImplementingMap.class.getName()); + + TypeWrappingTypeImplementingMap target = converter.read(TypeWrappingTypeImplementingMap.class, source); + + assertThat(target.typeImplementingMap).isEqualTo(new TypeImplementingMap("one", 2)); + } + + static class GenericType { T content; } @@ -2971,4 +3065,106 @@ public SubTypeOfGenericType convert(org.bson.Document source) { return target; } } + + @WritingConverter + static class TypeImplementingMapToDocumentConverter implements Converter { + + @Nullable + @Override + public org.bson.Document convert(TypeImplementingMap source) { + return new org.bson.Document("1st", source.val1).append("2nd", source.val2); + } + } + + @ReadingConverter + static class DocumentToTypeImplementingMapConverter implements Converter { + + @Nullable + @Override + public TypeImplementingMap convert(org.bson.Document source) { + return new TypeImplementingMap(source.getString("1st"), source.getInteger("2nd")); + } + } + + static class TypeWrappingTypeImplementingMap { + + String id; + TypeImplementingMap typeImplementingMap; + } + + @EqualsAndHashCode + static class TypeImplementingMap implements Map { + + String val1; + int val2; + + public TypeImplementingMap(String val1, int val2) { + this.val1 = val1; + this.val2 = val2; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object key) { + return false; + } + + @Override + public boolean containsValue(Object value) { + return false; + } + + @Override + public String get(Object key) { + return null; + } + + @Nullable + @Override + public String put(String key, String value) { + return null; + } + + @Override + public String remove(Object key) { + return null; + } + + @Override + public void putAll(@NonNull Map m) { + + } + + @Override + public void clear() { + + } + + @NonNull + @Override + public Set keySet() { + return null; + } + + @NonNull + @Override + public Collection values() { + return null; + } + + @NonNull + @Override + public Set> entrySet() { + return null; + } + } }