From 860f742f68fbe1c99a355bf3c3c27b2039692bc3 Mon Sep 17 00:00:00 2001 From: nexx512 Date: Sat, 11 Dec 2021 00:49:33 +0100 Subject: [PATCH 1/5] Handling collections and map types before running an element converter When the custom conversions are checked before collection and map converters, it is not possible to use custom converters for lists and maps. If a custom converter for a list or a map is defined, it is executed here and not from within the `collectionConverter` where it should be executed. So I think collections and maps should be handled beforehand. Maybe lines 2064-2066 might not be needed ad all as this case is covered with line 2081 --- .../data/mongodb/core/convert/MappingMongoConverter.java | 8 ++++---- 1 file changed, 4 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 54d0abc4b4..b2b8164287 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 @@ -2032,10 +2032,6 @@ public S convert(Object source, TypeInformation Assert.notNull(source, "Source must not be null"); 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(); @@ -2065,6 +2061,10 @@ public S convert(Object source, TypeInformation String.format("Expected map like structure but found %s", source.getClass())); } + if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) { + return (S) elementConverter.convert(source, typeHint); + } + if (source instanceof DBRef) { return (S) dbRefConverter.convert(this, (DBRef) source, typeHint); } From da1ab9abe757ad09d0f4e37b65f12ab3447cb999 Mon Sep 17 00:00:00 2001 From: nexx512 <> Date: Sun, 12 Dec 2021 13:29:48 +0100 Subject: [PATCH 2/5] Added tests --- .../core/convert/MappingMongoConverter.java | 8 +- .../CustomListReadingConvertersUnitTests.java | 90 +++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java 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 b2b8164287..658dd05b03 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 @@ -2047,6 +2047,10 @@ public S convert(Object source, TypeInformation } } + if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) { + return (S) elementConverter.convert(source, typeHint); + } + if (typeHint.isMap()) { if (ClassUtils.isAssignable(Document.class, typeHint.getType())) { @@ -2061,10 +2065,6 @@ public S convert(Object source, TypeInformation String.format("Expected map like structure but found %s", source.getClass())); } - if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) { - return (S) elementConverter.convert(source, typeHint); - } - if (source instanceof DBRef) { return (S) dbRefConverter.convert(this, (DBRef) source, typeHint); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java new file mode 100644 index 0000000000..48d4c763df --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.convert; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +/** + * Test case to verify correct usage of custom {@link Converter} implementations for Lists to be used. + * + * @author Jürgen Diez + */ +@ExtendWith(MockitoExtension.class) +class CustomListReadingConvertersUnitTests { + + private MappingMongoConverter converter; + + @Mock ListReadingConverter listReadingConverter; + @Captor ArgumentCaptor> enumListCaptor; + + private MongoMappingContext context; + + @BeforeEach + void setUp() { + CustomConversions conversions = new MongoCustomConversions( + Collections.singletonList(listReadingConverter)); + + context = new MongoMappingContext(); + context.setInitialEntitySet(new HashSet<>(Collections.singletonList(TestList.class))); + context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); + context.initialize(); + + converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); + converter.setCustomConversions(conversions); + converter.afterPropertiesSet(); + } + + @Test + void invokeCustomListConverterForEnumsAfterResolvingTheListTypes() { + Document document = new Document(); + document.append("list", Arrays.asList("ENUM_VALUE1", "ENUM_VALUE2")); + + converter.read(TestList.class, document); + + verify(listReadingConverter).convert(enumListCaptor.capture()); + assertThat(enumListCaptor.getValue()).containsExactly(TestEnum.ENUM_VALUE1, TestEnum.ENUM_VALUE2); + } + + + @ReadingConverter + private interface ListReadingConverter extends Converter, List> {} + + private static class TestList { + @SuppressWarnings("unused") + List list; + } + + private enum TestEnum { + ENUM_VALUE1, + ENUM_VALUE2 + } +} From eab7b679afb0984f511d95ba6c6e99f25e54ecc5 Mon Sep 17 00:00:00 2001 From: nexx512 <> Date: Sun, 12 Dec 2021 14:18:34 +0100 Subject: [PATCH 3/5] Tests extended --- .../CustomListReadingConvertersUnitTests.java | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java index 48d4c763df..1055202944 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java @@ -28,13 +28,16 @@ import org.springframework.data.convert.ReadingConverter; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; /** - * Test case to verify correct usage of custom {@link Converter} implementations for Lists to be used. + * Test case to verify correct usage of custom {@link Converter} implementations for different List implementations to be used. * * @author Jürgen Diez */ @@ -43,18 +46,19 @@ class CustomListReadingConvertersUnitTests { private MappingMongoConverter converter; - @Mock ListReadingConverter listReadingConverter; - @Captor ArgumentCaptor> enumListCaptor; + @Mock JavaListReadingConverter javaListReadingConverter; + @Mock OtherListReadingConverter otherListReadingConverter; + @Captor ArgumentCaptor> enumListCaptor; private MongoMappingContext context; @BeforeEach void setUp() { CustomConversions conversions = new MongoCustomConversions( - Collections.singletonList(listReadingConverter)); + Arrays.asList(javaListReadingConverter, otherListReadingConverter)); context = new MongoMappingContext(); - context.setInitialEntitySet(new HashSet<>(Collections.singletonList(TestList.class))); + context.setInitialEntitySet(new HashSet<>(Collections.singletonList(TestJavaList.class))); context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); context.initialize(); @@ -64,27 +68,47 @@ void setUp() { } @Test - void invokeCustomListConverterForEnumsAfterResolvingTheListTypes() { + void invokeCustomListConverterForEnumsInJavaListAfterResolvingTheListTypes() { Document document = new Document(); document.append("list", Arrays.asList("ENUM_VALUE1", "ENUM_VALUE2")); - converter.read(TestList.class, document); + converter.read(TestJavaList.class, document); - verify(listReadingConverter).convert(enumListCaptor.capture()); + verify(javaListReadingConverter).convert(enumListCaptor.capture()); assertThat(enumListCaptor.getValue()).containsExactly(TestEnum.ENUM_VALUE1, TestEnum.ENUM_VALUE2); } + @Test + void invokeCustomListConverterForEnumsInIterableListAfterResolvingTheListTypes() { + Document document = new Document(); + document.append("list", Arrays.asList("ENUM_VALUE1", "ENUM_VALUE2")); - @ReadingConverter - private interface ListReadingConverter extends Converter, List> {} + converter.read(TestIterableList.class, document); - private static class TestList { - @SuppressWarnings("unused") - List list; + verify(otherListReadingConverter).convert(enumListCaptor.capture()); + assertThat(enumListCaptor.getValue()).containsExactly(TestEnum.ENUM_VALUE1, TestEnum.ENUM_VALUE2); } + + @ReadingConverter + private interface JavaListReadingConverter extends Converter, List> {} + + @ReadingConverter + private interface OtherListReadingConverter extends Converter, Iterable> {} + private enum TestEnum { ENUM_VALUE1, ENUM_VALUE2 } + + private static class TestJavaList { + @SuppressWarnings("unused") + List list; + } + + private static class TestIterableList { + @SuppressWarnings("unused") + Iterable list; + } + } From b35cf7a4a89a36de50ededc3388fbfb31da57a83 Mon Sep 17 00:00:00 2001 From: nexx512 <> Date: Fri, 17 Dec 2021 16:41:15 +0100 Subject: [PATCH 4/5] Added support to convert to custom list and map implementations after the elements of a list or map have been converted to be able to use `Converter, List>` and `Converter, Map>` Converters. --- .../core/convert/MappingMongoConverter.java | 43 ++- .../CustomListReadingConvertersUnitTests.java | 63 +++-- .../CustomMapReadingConvertersUnitTests.java | 205 ++++++++++++++ .../MappingMongoConverterUnitTests.java | 250 ++---------------- 4 files changed, 293 insertions(+), 268 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomMapReadingConvertersUnitTests.java 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 658dd05b03..2e312f94d5 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 @@ -1295,7 +1295,8 @@ protected Object readCollectionOrArray(ConversionContext context, Collection */ @Deprecated protected Map readMap(TypeInformation type, Bson bson, ObjectPath path) { - return readMap(getConversionContext(path), bson, type); + //noinspection unchecked + return (Map) readMap(getConversionContext(path), bson, type); } /** @@ -1308,15 +1309,19 @@ protected Map readMap(TypeInformation type, Bson bson, Object * @return the converted {@link Map}, will never be {@literal null}. * @since 3.2 */ - protected Map readMap(ConversionContext context, Bson bson, TypeInformation targetType) { + protected Object readMap(ConversionContext context, Bson bson, TypeInformation targetType) { Assert.notNull(bson, "Document must not be null!"); Assert.notNull(targetType, "TypeInformation must not be null!"); - Class mapType = getTypeMapper().readType(bson, targetType).getType(); + TypeInformation typeInformation = getTypeMapper().readType(bson, targetType); + Class mapType = typeInformation.isSubTypeOf(Map.class) + ? targetType.getType() + : Map.class; TypeInformation keyType = targetType.getComponentType(); - TypeInformation valueType = targetType.getMapValueType() == null ? ClassTypeInformation.OBJECT + TypeInformation valueType = targetType.getMapValueType() == null + ? ClassTypeInformation.OBJECT : targetType.getRequiredMapValueType(); Class rawKeyType = keyType != null ? keyType.getType() : Object.class; @@ -1347,7 +1352,7 @@ protected Map readMap(ConversionContext context, Bson bson, Type }); - return map; + return getPotentiallyConvertedSimpleRead(map, targetType.getType()); } /* @@ -2032,27 +2037,7 @@ public S convert(Object source, TypeInformation Assert.notNull(source, "Source must not be null"); Assert.notNull(typeHint, "TypeInformation must not be null"); - if (source instanceof Collection) { - - Class rawType = typeHint.getType(); - if (!Object.class.equals(rawType)) { - if (!rawType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawType)) { - throw new MappingException( - String.format(INCOMPATIBLE_TYPES, source, source.getClass(), rawType, getPath())); - } - } - - if (typeHint.isCollectionLike() || typeHint.getType().isAssignableFrom(Collection.class)) { - return (S) collectionConverter.convert(this, (Collection) source, typeHint); - } - } - - if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) { - return (S) elementConverter.convert(source, typeHint); - } - if (typeHint.isMap()) { - if (ClassUtils.isAssignable(Document.class, typeHint.getType())) { return (S) documentConverter.convert(this, BsonUtils.asBson(source), typeHint); } @@ -2065,6 +2050,14 @@ public S convert(Object source, TypeInformation String.format("Expected map like structure but found %s", source.getClass())); } + if (source instanceof Collection) { + return (S) collectionConverter.convert(this, (Collection) source, typeHint); + } + + if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) { + return (S) elementConverter.convert(source, typeHint); + } + if (source instanceof DBRef) { return (S) dbRefConverter.convert(this, (DBRef) source, typeHint); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java index 1055202944..db24027973 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomListReadingConvertersUnitTests.java @@ -23,17 +23,17 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.verify; /** @@ -47,30 +47,31 @@ class CustomListReadingConvertersUnitTests { private MappingMongoConverter converter; @Mock JavaListReadingConverter javaListReadingConverter; - @Mock OtherListReadingConverter otherListReadingConverter; + @Mock IterableListReadingConverter iterableListReadingConverter; + @Mock CustomListReadingConverter customListReadingConverter; @Captor ArgumentCaptor> enumListCaptor; - private MongoMappingContext context; + private Document document; @BeforeEach void setUp() { CustomConversions conversions = new MongoCustomConversions( - Arrays.asList(javaListReadingConverter, otherListReadingConverter)); + Arrays.asList(javaListReadingConverter, iterableListReadingConverter, customListReadingConverter)); - context = new MongoMappingContext(); - context.setInitialEntitySet(new HashSet<>(Collections.singletonList(TestJavaList.class))); + MongoMappingContext context = new MongoMappingContext(); context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); context.initialize(); converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); converter.setCustomConversions(conversions); converter.afterPropertiesSet(); + + document = new Document(); + document.append("list", Arrays.asList("ENUM_VALUE1", "ENUM_VALUE2")); } @Test - void invokeCustomListConverterForEnumsInJavaListAfterResolvingTheListTypes() { - Document document = new Document(); - document.append("list", Arrays.asList("ENUM_VALUE1", "ENUM_VALUE2")); + void invokeJavaListConverterForEnumsAfterResolvingTheListTypes() { converter.read(TestJavaList.class, document); @@ -79,22 +80,38 @@ void invokeCustomListConverterForEnumsInJavaListAfterResolvingTheListTypes() { } @Test - void invokeCustomListConverterForEnumsInIterableListAfterResolvingTheListTypes() { - Document document = new Document(); - document.append("list", Arrays.asList("ENUM_VALUE1", "ENUM_VALUE2")); + void invokeExtendedIterableListConverterForEnumsAfterResolvingTheListTypes() { converter.read(TestIterableList.class, document); - verify(otherListReadingConverter).convert(enumListCaptor.capture()); + verify(iterableListReadingConverter).convert(enumListCaptor.capture()); assertThat(enumListCaptor.getValue()).containsExactly(TestEnum.ENUM_VALUE1, TestEnum.ENUM_VALUE2); } + @Test + void invokeOtherListConverterForEnumsAfterResolvingTheListTypes() { + + converter.read(TestOtherList.class, document); + + verify(customListReadingConverter).convert(enumListCaptor.capture()); + assertThat(enumListCaptor.getValue()).containsExactly(TestEnum.ENUM_VALUE1, TestEnum.ENUM_VALUE2); + } + + @Test + void throwExceptionIfNoConverterIsGivenForACustomListImplementation() { + + assertThrows(ConversionFailedException.class, () -> converter.read(TestNoConverterList.class, document)); + } + @ReadingConverter private interface JavaListReadingConverter extends Converter, List> {} @ReadingConverter - private interface OtherListReadingConverter extends Converter, Iterable> {} + private interface IterableListReadingConverter extends Converter, Iterable> {} + + @ReadingConverter + private interface CustomListReadingConverter extends Converter, OtherList> {} private enum TestEnum { ENUM_VALUE1, @@ -111,4 +128,20 @@ private static class TestIterableList { Iterable list; } + private static class TestOtherList { + @SuppressWarnings("unused") + OtherList list; + } + + private interface OtherList { + } + + private static class TestNoConverterList { + @SuppressWarnings("unused") + NoConverterList list; + } + + private interface NoConverterList { + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomMapReadingConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomMapReadingConvertersUnitTests.java new file mode 100644 index 0000000000..12b286a0ed --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomMapReadingConvertersUnitTests.java @@ -0,0 +1,205 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.convert; + +import org.bson.Document; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.MapEntry.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; + +/** + * Test case to verify correct usage of custom {@link Converter} implementations for different Map implementations to be used. + * + * @author Jürgen Diez + */ +@ExtendWith(MockitoExtension.class) +class CustomMapReadingConvertersUnitTests { + + private MappingMongoConverter converter; + + @Mock JavaMapReadingConverter javaMapReadingConverter; + @Mock ExtendedMapReadingConverter extendedMapReadingConverter; + @Mock VavrMapReadingConverter vavrMapReadingConverter; + @Captor ArgumentCaptor> enumMapCaptor; + + private Document source; + + @BeforeEach + void setUp() { + CustomConversions conversions = new MongoCustomConversions( + Arrays.asList(javaMapReadingConverter, extendedMapReadingConverter, vavrMapReadingConverter)); + + MongoMappingContext context = new MongoMappingContext(); + context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); + context.initialize(); + + converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); + converter.setCustomConversions(conversions); + converter.afterPropertiesSet(); + + source = new Document(); + Document map = new Document(); + map.append("key1", "ENUM_VALUE1"); + map.append("key2", "ENUM_VALUE2"); + source.append("map", map); + } + + @Test + void invokeJavaMapConverterForEnumsAfterResolvingTheMapTypes() { + + converter.read(TestJavaMap.class, source); + + verify(javaMapReadingConverter).convert(enumMapCaptor.capture()); + assertThat(enumMapCaptor.getValue()).containsOnly(entry("key1", TestEnum.ENUM_VALUE1), entry("key2", TestEnum.ENUM_VALUE2)); + } + + @Test + void invokeExtendedMapConverterForEnumsAfterResolvingTheMapTypes() { + + converter.read(TestExtendedMap.class, source); + + verify(extendedMapReadingConverter).convert(enumMapCaptor.capture()); + assertThat(enumMapCaptor.getValue()).containsOnly(entry("key1", TestEnum.ENUM_VALUE1), entry("key2", TestEnum.ENUM_VALUE2)); + } + + @Test + void invokeVavrMapConverterForEnumsAfterResolvingTheMapTypes() { + + converter.read(TestVavrMap.class, source); + + verify(vavrMapReadingConverter).convert(enumMapCaptor.capture()); + assertThat(enumMapCaptor.getValue()).containsOnly(entry("key1", TestEnum.ENUM_VALUE1), entry("key2", TestEnum.ENUM_VALUE2)); + } + + + @ReadingConverter + private interface JavaMapReadingConverter extends Converter, Map> { + } + + @ReadingConverter + private interface ExtendedMapReadingConverter extends Converter, ExtendedMap> { + } + + @ReadingConverter + private interface VavrMapReadingConverter extends Converter, io.vavr.collection.Map> { + } + + private enum TestEnum { + ENUM_VALUE1, + ENUM_VALUE2 + } + + private static class TestJavaMap { + @SuppressWarnings("unused") + Map map; + } + + private static class TestExtendedMap { + @SuppressWarnings("unused") + ExtendedMap map; + } + + private static class TestVavrMap { + @SuppressWarnings("unused") + io.vavr.collection.Map map; + } + + private static class ExtendedMap implements Map { + HashMap internalMap = new HashMap<>(); + + @Override + public int size() { + return internalMap.size(); + } + + @Override + public boolean isEmpty() { + return internalMap.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + return internalMap.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + return internalMap.containsValue(o); + } + + @Override + public V get(Object o) { + return internalMap.get(o); + } + + @Nullable + @Override + public V put(K k, V v) { + return internalMap.put(k, v); + } + + @Override + public V remove(Object o) { + return internalMap.remove(o); + } + + @Override + public void putAll(@NotNull Map map) { + internalMap.putAll(map); + } + + @Override + public void clear() { + internalMap.clear(); + } + + @NotNull + @Override + public Set keySet() { + return internalMap.keySet(); + } + + @NotNull + @Override + public Collection values() { + return internalMap.values(); + } + + @NotNull + @Override + public Set> entrySet() { + return internalMap.entrySet(); + } + } +} \ No newline at end of file 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 d4c735fd23..ff80cc3bef 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 @@ -15,21 +15,12 @@ */ package org.springframework.data.mongodb.core.convert; -import static java.time.ZoneId.*; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; -import static org.springframework.data.mongodb.core.DocumentTestUtils.*; - +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.DBRef; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.URL; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.*; - import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.Decimal128; @@ -42,12 +33,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; - import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.ConversionNotSupportedException; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; @@ -57,13 +48,7 @@ import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; -import org.springframework.data.geo.Box; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Metrics; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; -import org.springframework.data.geo.Shape; +import org.springframework.data.geo.*; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.model.MappingInstantiationException; @@ -72,24 +57,23 @@ import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.ProjectingType; import org.springframework.data.mongodb.core.convert.MappingMongoConverterUnitTests.ClassWithMapUsingEnumAsKey.FooBarEnum; import org.springframework.data.mongodb.core.geo.Sphere; -import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.data.mongodb.core.mapping.Field; -import org.springframework.data.mongodb.core.mapping.FieldType; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.data.mongodb.core.mapping.PersonPojoStringId; -import org.springframework.data.mongodb.core.mapping.TextScore; -import org.springframework.data.mongodb.core.mapping.Unwrapped; +import org.springframework.data.mongodb.core.mapping.*; 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; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; -import com.mongodb.DBRef; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; + +import static java.time.ZoneId.systemDefault; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.springframework.data.mongodb.core.DocumentTestUtils.assertTypeHint; +import static org.springframework.data.mongodb.core.DocumentTestUtils.getAsDocument; /** * Unit tests for {@link MappingMongoConverter}. @@ -1614,7 +1598,7 @@ void rejectsBasicDbListToBeConvertedIntoComplexType() { org.bson.Document source = new org.bson.Document("attributes", outer); - assertThatExceptionOfType(MappingException.class).isThrownBy(() -> converter.read(Item.class, source)); + assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() -> converter.read(Item.class, source)); } @Test // DATAMONGO-1058 @@ -2170,6 +2154,7 @@ void resolveDBRefMapValueShouldInvokeCallbacks() { verify(afterConvertCallback).onAfterConvert(eq(result.personMap.get("foo")), eq(new org.bson.Document()), any()); } + @SuppressWarnings("unchecked") @Test // DATAMONGO-2300 void readAndConvertDBRefNestedByMapCorrectly() { @@ -2182,7 +2167,7 @@ void readAndConvertDBRefNestedByMapCorrectly() { MappingMongoConverter spyConverter = spy(converter); Mockito.doReturn(cluster).when(spyConverter).readRef(dbRef); - Map result = spyConverter.readMap(spyConverter.getConversionContext(ObjectPath.ROOT), data, + Map result = (Map) spyConverter.readMap(spyConverter.getConversionContext(ObjectPath.ROOT), data, ClassTypeInformation.MAP); assertThat(((Map) result.get("cluster")).get("_id")).isEqualTo(100L); @@ -2434,95 +2419,6 @@ void shouldUseMostConcreteCustomConversionTargetOnRead() { } - @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)); - } - @Test // GH-3407 void shouldWriteNullPropertyCorrectly() { @@ -3184,26 +3080,6 @@ public SubTypeOfGenericType convert(org.bson.Document source) { } } - @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")); - } - } - @ReadingConverter public static class MongoSimpleTypeConverter implements Converter { @@ -3213,88 +3089,6 @@ public byte[] convert(Binary source) { } } - static class TypeWrappingTypeImplementingMap { - - String id; - TypeImplementingMap typeImplementingMap; - } - - @EqualsAndHashCode - static class TypeImplementingMap implements Map { - - String val1; - int val2; - - 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; - } - } - static class WithRawDocumentProperties { String id; From 9990280a9e8fc0a66a7d9d44504b1e2f830d9573 Mon Sep 17 00:00:00 2001 From: nexx512 <> Date: Sat, 18 Dec 2021 09:26:19 +0100 Subject: [PATCH 5/5] Added missing dependency for tests --- spring-data-mongodb/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index ca96626cc9..31a2cd72bb 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -18,6 +18,7 @@ 1.3 1.7.8 + 0.10.4 spring.data.mongodb ${basedir}/.. 1.01 @@ -224,6 +225,13 @@ test + + io.vavr + vavr + ${vavr} + test + + org.threeten threetenbp