From f671ffd777c1483e8bd8364a7a5b6175ef7db289 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 10 Dec 2021 10:13:11 +0100 Subject: [PATCH 1/7] Prepare issue branch. --- pom.xml | 4 ++-- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0e9257f0f0..a85511590e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 3.4.0-SNAPSHOT + 3.4.0-GH-3596-SNAPSHOT pom Spring Data MongoDB @@ -26,7 +26,7 @@ multi spring-data-mongodb - 2.7.0-SNAPSHOT + 2.7.0-GH-1484-SNAPSHOT 4.5.0 ${mongo} 1.19 diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index e2704a6753..c35e5f996e 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.4.0-SNAPSHOT + 3.4.0-GH-3596-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index b75f8bf624..6b698467b7 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.4.0-SNAPSHOT + 3.4.0-GH-3596-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index ca96626cc9..e0562c6ccd 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.4.0-SNAPSHOT + 3.4.0-GH-3596-SNAPSHOT ../pom.xml From 847e968b8ab0f4e10bdb2eaa33deb504e37296c3 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 13 Dec 2021 08:22:06 +0100 Subject: [PATCH 2/7] hacking based on the commons branch. --- .../mapping/BasicMongoPersistentEntity.java | 3 + .../mapping/BasicMongoPersistentProperty.java | 12 +++ .../core/mapping/MongoMappingContext.java | 4 +- .../core/mapping/MongoPersistentProperty.java | 4 + .../UnwrappedMongoPersistentProperty.java | 6 ++ ...BasicMongoPersistentPropertyUnitTests.java | 95 +++++++++++++++++++ 6 files changed, 123 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java index ee900c25e7..f3f24e4b22 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; +import org.springframework.beans.factory.BeanFactory; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; @@ -73,6 +74,8 @@ public class BasicMongoPersistentEntity extends BasicPersistentEntity } } } + + } /** @@ -326,6 +329,15 @@ public EvaluationContext getEvaluationContext(@Nullable Object rootObject) { return rootObject != null ? new StandardEvaluationContext(rootObject) : new StandardEvaluationContext(); } + @Override + public PropertyValueConverter getValueConverter() { + + if (getOwner() instanceof BasicMongoPersistentEntity) { + return resolveConverter(((BasicMongoPersistentEntity) getOwner()).beanFactory); + } + return resolveConverter(null); + } + @Override public Collection getEncryptionKeyIds() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java index f796a554e4..a34d73297b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java @@ -97,7 +97,9 @@ public MongoPersistentProperty createPersistentProperty(Property property, Mongo */ @Override protected BasicMongoPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new BasicMongoPersistentEntity<>(typeInformation); + BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity<>(typeInformation); + entity.beanFactory = applicationContext; + return entity; } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java index b2836e85fb..7773d6fbb3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java @@ -19,6 +19,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; +import org.springframework.data.convert.PropertyConverter; +import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; @@ -176,6 +178,8 @@ default boolean isUnwrapped() { */ Collection getEncryptionKeyIds(); + PropertyValueConverter getValueConverter(); + /** * Simple {@link Converter} implementation to transform a {@link MongoPersistentProperty} into its field name. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java index d163f23989..5f27f94265 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.Collection; +import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; @@ -280,6 +281,11 @@ public Collection getEncryptionKeyIds() { return delegate.getEncryptionKeyIds(); } + @Override + public PropertyValueConverter getValueConverter() { + return delegate.getValueConverter(); + } + @Override @Nullable public Class getComponentType() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index a92a1ab891..b58846fb97 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -31,8 +31,16 @@ import org.jmolecules.ddd.annotation.Identity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.annotation.AliasFor; import org.springframework.data.annotation.Id; +import org.springframework.data.convert.PropertyConverter; +import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.model.FieldNamingStrategy; @@ -250,6 +258,48 @@ void considersJMoleculesIdentityExplicitlyAnnotatedIdentifier() { assertThat(property.isExplicitIdProperty()).isTrue(); } + @Test + void xxx () { + + MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value"); + PropertyValueConverter valueConverter = property.getValueConverter(); + + assertThat(valueConverter).isInstanceOf(MyPropertyConverter.class); + } + + @Test + void xxx2 () { + + MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value"); + ((BasicMongoPersistentEntity)property.getOwner()).beanFactory = new DefaultListableBeanFactory(); + PropertyValueConverter valueConverter = property.getValueConverter(); + + assertThat(valueConverter).isInstanceOf(MyPropertyConverter.class); + } + + @Test + void xxx3 () { + + MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value2"); + ((BasicMongoPersistentEntity)property.getOwner()).beanFactory = new DefaultListableBeanFactory(); + assertThatExceptionOfType(BeanCreationException.class).isThrownBy(()-> property.getValueConverter()); + } + + @Test + void xxx4 () { + + DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); + defaultListableBeanFactory.registerBeanDefinition("someDependency", BeanDefinitionBuilder + .rootBeanDefinition(SomeDependency.class) + .getBeanDefinition()); + + MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value2"); + ((BasicMongoPersistentEntity)property.getOwner()).beanFactory = defaultListableBeanFactory ; + PropertyValueConverter valueConverter = property.getValueConverter(); + + assertThat(valueConverter).isInstanceOf(MyPropertyConverterThatRequiresComponents.class); + } + private MongoPersistentProperty getPropertyFor(Field field) { return getPropertyFor(entity, field); } @@ -381,4 +431,49 @@ static class WithComplexId { static class WithJMoleculesIdentity { @Identity ObjectId identifier; } + + static class WithPropertyConverter { + + @PropertyConverter(MyPropertyConverter.class) + String value; + + @PropertyConverter(MyPropertyConverterThatRequiresComponents.class) + String value2; + } + + static class MyPropertyConverter implements PropertyValueConverter { + + @Override + public Object read(Object value) { + return null; + } + + @Override + public Object write(Object value) { + return null; + } + } + + static class MyPropertyConverterThatRequiresComponents implements PropertyValueConverter { + + private final SomeDependency someDependency; + + public MyPropertyConverterThatRequiresComponents(@Autowired SomeDependency someDependency) { + this.someDependency = someDependency; + } + + @Override + public Object read(Object value) { + return null; + } + + @Override + public Object write(Object value) { + return null; + } + } + + static class SomeDependency { + + } } From d62f6b71313edd03db2aeca5a8ac16ab39029c06 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 20 Dec 2021 14:56:36 +0100 Subject: [PATCH 3/7] seems to work nicely - query mapping might still be an issue, but the commons API seems to do so far --- .../core/convert/MappingMongoConverter.java | 15 ++ .../core/convert/MongoCustomConversions.java | 13 +- .../mapping/BasicMongoPersistentProperty.java | 9 - .../core/mapping/MongoPersistentProperty.java | 2 - .../UnwrappedMongoPersistentProperty.java | 5 - .../MappingMongoConverterUnitTests.java | 195 +++++++++++++++--- ...BasicMongoPersistentPropertyUnitTests.java | 95 --------- 7 files changed, 188 insertions(+), 146 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 c58f8d1e41..4708436ee6 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 @@ -958,6 +958,11 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce TypeInformation valueType = ClassTypeInformation.from(obj.getClass()); TypeInformation type = prop.getTypeInformation(); + if(prop.hasValueConverter()) { + accessor.put(prop, conversions.getPropertyValueConverterFactory().getConverter(prop).domainToNative(obj)); + return; + } + if (prop.isUnwrapped()) { Document target = new Document(); @@ -1287,6 +1292,12 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, String key) private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersistentProperty property) { DocumentAccessor accessor = new DocumentAccessor(bson); + + if(property.hasValueConverter()) { + accessor.put(property, conversions.getPropertyValueConverterFactory().getConverter(property).domainToNative(value)); + return; + } + accessor.put(property, getPotentiallyConvertedSimpleWrite(value, property.hasExplicitWriteTarget() ? property.getFieldType() : Object.class)); } @@ -1950,6 +1961,10 @@ public T getPropertyValue(MongoPersistentProperty property) { return null; } + if(property.hasValueConverter()) { + return (T) context.conversions.getPropertyValueConverterFactory().getConverter(property).nativeToDomain(value); + } + return (T) context.convert(value, property.getTypeInformation()); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java index 394d71984a..5881f8f66f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java @@ -37,6 +37,7 @@ import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.data.convert.JodaTimeConverters; +import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; @@ -159,6 +160,8 @@ public static class MongoConverterConfigurationAdapter { private boolean useNativeDriverJavaTimeCodecs = false; private final List customConverters = new ArrayList<>(); + private @Nullable PropertyValueConverterFactory propertyValueConverterFactory; + /** * Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for * JSR-310 types. @@ -258,10 +261,16 @@ public MongoConverterConfigurationAdapter registerConverters(Collection conve return this; } + public MongoConverterConfigurationAdapter registerPropertyValueConverterFactory(PropertyValueConverterFactory converterFactory) { + + this.propertyValueConverterFactory = converterFactory; + return this; + } + ConverterConfiguration createConverterConfiguration() { if (!useNativeDriverJavaTimeCodecs) { - return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters); + return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, propertyValueConverterFactory); } /* @@ -286,7 +295,7 @@ ConverterConfiguration createConverterConfiguration() { } return true; - }); + }, propertyValueConverterFactory); } private enum DateToUtcLocalDateTimeConverter implements Converter { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java index 3d49db9ed9..ef31a032e9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java @@ -329,15 +329,6 @@ public EvaluationContext getEvaluationContext(@Nullable Object rootObject) { return rootObject != null ? new StandardEvaluationContext(rootObject) : new StandardEvaluationContext(); } - @Override - public PropertyValueConverter getValueConverter() { - - if (getOwner() instanceof BasicMongoPersistentEntity) { - return resolveConverter(((BasicMongoPersistentEntity) getOwner()).beanFactory); - } - return resolveConverter(null); - } - @Override public Collection getEncryptionKeyIds() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java index 7773d6fbb3..46909b3670 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java @@ -178,8 +178,6 @@ default boolean isUnwrapped() { */ Collection getEncryptionKeyIds(); - PropertyValueConverter getValueConverter(); - /** * Simple {@link Converter} implementation to transform a {@link MongoPersistentProperty} into its field name. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java index 5f27f94265..342c08f3bf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java @@ -281,11 +281,6 @@ public Collection getEncryptionKeyIds() { return delegate.getEncryptionKeyIds(); } - @Override - public PropertyValueConverter getValueConverter() { - return delegate.getValueConverter(); - } - @Override @Nullable public Class getComponentType() { 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 40ab7aa11f..db9fb19d3e 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 @@ -43,10 +43,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.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.convert.ConverterNotFoundException; @@ -55,7 +57,11 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.convert.BeanFactoryAwarePropertyValueConverterFactory; import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.PropertyConverter; +import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.geo.Box; @@ -2437,7 +2443,6 @@ void shouldUseMostConcreteCustomConversionTargetOnRead() { verify(subTypeOfGenericTypeConverter).convert(eq(source)); } - @Test // GH-3660 void usesCustomConverterForMapTypesOnWrite() { @@ -2481,9 +2486,8 @@ void usesCustomConverterForTypesImplementingMapOnRead() { })); converter.afterPropertiesSet(); - org.bson.Document source = new org.bson.Document("1st", "one") - .append("2nd", 2) - .append("_class", TypeImplementingMap.class.getName()); + 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); @@ -2518,9 +2522,8 @@ void usesCustomConverterForPropertiesUsingTypesImplementingMapOnRead() { 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()); + new org.bson.Document("1st", "one").append("2nd", 2)).append("_class", + TypeWrappingTypeImplementingMap.class.getName()); TypeWrappingTypeImplementingMap target = converter.read(TypeWrappingTypeImplementingMap.class, source); @@ -2542,13 +2545,12 @@ void shouldWriteNullPropertyCorrectly() { @Test // GH-3686 void readsCollectionContainingNullValue() { - org.bson.Document source = new org.bson.Document("items", Arrays.asList(new org.bson.Document("itemKey", "i1"), null, new org.bson.Document("itemKey", "i3"))); + org.bson.Document source = new org.bson.Document("items", + Arrays.asList(new org.bson.Document("itemKey", "i1"), null, new org.bson.Document("itemKey", "i3"))); Order target = converter.read(Order.class, source); - assertThat(target.items) - .map(it -> it != null ? it.itemKey : null) - .containsExactly("i1", null, "i3"); + assertThat(target.items).map(it -> it != null ? it.itemKey : null).containsExactly("i1", null, "i3"); } @Test // GH-3686 @@ -2564,14 +2566,13 @@ void readsArrayContainingNullValue() { @Test // GH-3686 void readsMapContainingNullValue() { - org.bson.Document source = new org.bson.Document("mapOfObjects", new org.bson.Document("item1", "i1").append("item2", null).append("item3", "i3")); + org.bson.Document source = new org.bson.Document("mapOfObjects", + new org.bson.Document("item1", "i1").append("item2", null).append("item3", "i3")); ClassWithMapProperty target = converter.read(ClassWithMapProperty.class, source); - assertThat(target.mapOfObjects) - .containsEntry("item1", "i1") - .containsEntry("item2", null) - .containsEntry("item3", "i3"); + assertThat(target.mapOfObjects).containsEntry("item1", "i1").containsEntry("item2", null).containsEntry("item3", + "i3"); } @Test // GH-3670 @@ -2583,7 +2584,7 @@ void appliesCustomConverterEvenToSimpleTypes() { })); converter.afterPropertiesSet(); - org.bson.Document source = new org.bson.Document("content", new Binary(new byte[] {0x00, 0x42})); + org.bson.Document source = new org.bson.Document("content", new Binary(new byte[] { 0x00, 0x42 })); GenericType target = converter.read(GenericType.class, source); assertThat(target.content).isInstanceOf(byte[].class); @@ -2592,17 +2593,20 @@ void appliesCustomConverterEvenToSimpleTypes() { @Test // GH-3702 void readsRawDocument() { - org.bson.Document source = new org.bson.Document("_id", "id-1").append("raw", new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1))); + org.bson.Document source = new org.bson.Document("_id", "id-1").append("raw", + new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1))); WithRawDocumentProperties target = converter.read(WithRawDocumentProperties.class, source); - assertThat(target.raw).isInstanceOf(org.bson.Document.class).isEqualTo( new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1))); + assertThat(target.raw).isInstanceOf(org.bson.Document.class) + .isEqualTo(new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1))); } @Test // GH-3702 void readsListOfRawDocument() { - org.bson.Document source = new org.bson.Document("_id", "id-1").append("listOfRaw", Arrays.asList(new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1)))); + org.bson.Document source = new org.bson.Document("_id", "id-1").append("listOfRaw", + Arrays.asList(new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1)))); WithRawDocumentProperties target = converter.read(WithRawDocumentProperties.class, source); @@ -2613,11 +2617,12 @@ void readsListOfRawDocument() { @Test // GH-3692 void readsMapThatDoesNotComeAsDocument() { - org.bson.Document source = new org.bson.Document("_id", "id-1").append("mapOfObjects", Collections.singletonMap("simple", 1)); + org.bson.Document source = new org.bson.Document("_id", "id-1").append("mapOfObjects", + Collections.singletonMap("simple", 1)); ClassWithMapProperty target = converter.read(ClassWithMapProperty.class, source); - assertThat(target.mapOfObjects).containsEntry("simple",1); + assertThat(target.mapOfObjects).containsEntry("simple", 1); } @Test // GH-3851 @@ -2637,7 +2642,8 @@ void readsMapThatDoesNotComeAsDocument() { converter.writePropertyInternal(sourceValue, accessor, persistentProperty); - assertThat(accessor.getDocument()).isEqualTo(new org.bson.Document("pName", new org.bson.Document("_id", id.toString()))); + assertThat(accessor.getDocument()) + .isEqualTo(new org.bson.Document("pName", new org.bson.Document("_id", id.toString()))); } @Test // GH-2860 @@ -2651,8 +2657,7 @@ void projectShouldReadSimpleInterfaceProjection() { .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), mappingContext); - EntityProjection projection = discoverer - .introspect(PersonProjection.class, Person.class); + EntityProjection projection = discoverer.introspect(PersonProjection.class, Person.class); PersonProjection person = converter.project(projection, source); assertThat(person.getBirthDate()).isEqualTo(new LocalDate(1999, 12, 1)); @@ -2670,8 +2675,7 @@ void projectShouldReadSimpleDtoProjection() { .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), mappingContext); - EntityProjection projection = introspector - .introspect(PersonDto.class, Person.class); + EntityProjection projection = introspector.introspect(PersonDto.class, Person.class); PersonDto person = converter.project(projection, source); assertThat(person.getBirthDate()).isEqualTo(new LocalDate(1999, 12, 1)); @@ -2689,8 +2693,8 @@ void projectShouldReadNestedProjection() { .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), mappingContext); - EntityProjection projection = introspector - .introspect(WithNestedProjection.class, Person.class); + EntityProjection projection = introspector.introspect(WithNestedProjection.class, + Person.class); WithNestedProjection person = converter.project(projection, source); assertThat(person.getAddresses()).extracting(AddressProjection::getStreet).hasSize(1).containsOnly("hwy"); @@ -2746,8 +2750,7 @@ interface InterfaceType { @EqualsAndHashCode @Getter static class Address implements InterfaceType { - @Field("s") - String street; + @Field("s") String street; String city; } @@ -3348,7 +3351,7 @@ static class TypeWrappingTypeImplementingMap { } @EqualsAndHashCode - static class TypeImplementingMap implements Map { + static class TypeImplementingMap implements Map { String val1; int val2; @@ -3445,4 +3448,130 @@ static class WithFieldWrite { write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Person writeAlwaysPerson; } + + @Test // GH-3596 + void simpleConverter() { + + WithValueConverters wvc = new WithValueConverters(); + wvc.converterWithDefaultCtor = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target).containsEntry("converterWithDefaultCtor", new org.bson.Document("foo", "spring")); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.converterWithDefaultCtor).startsWith("spring"); + } + + @Test // GH-3596 + void enumConverter() { + + WithValueConverters wvc = new WithValueConverters(); + wvc.converterEnum = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target).containsEntry("converterEnum", new org.bson.Document("bar", "spring")); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.converterEnum).isEqualTo("spring"); + } + + @Test // GH-3596 + void beanConverter() { + + DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); + defaultListableBeanFactory.registerBeanDefinition("someDependency", + BeanDefinitionBuilder.rootBeanDefinition(SomeDependency.class).getBeanDefinition()); + + BeanFactoryAwarePropertyValueConverterFactory beanFactoryAwareConverterFactory = new BeanFactoryAwarePropertyValueConverterFactory(); + beanFactoryAwareConverterFactory.setBeanFactory(defaultListableBeanFactory); + + converter = new MappingMongoConverter(resolver, mappingContext); + + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerPropertyValueConverterFactory(PropertyValueConverterFactory.caching(beanFactoryAwareConverterFactory)); + })); + converter.afterPropertiesSet(); + + WithValueConverters wvc = new WithValueConverters(); + wvc.converterBean = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target.get("converterBean", org.bson.Document.class)).satisfies(it -> { + assertThat(it).containsKey("ooo"); + assertThat((String) it.get("ooo")).startsWith("spring - "); + }); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.converterBean).startsWith("spring -"); + } + + static class WithValueConverters { + + @PropertyConverter(Converter1.class) String converterWithDefaultCtor; + + @PropertyConverter(Converter2.class) String converterEnum; + + @PropertyConverter(Converter3.class) String converterBean; + } + + static class Converter3 implements PropertyValueConverter { + + private final SomeDependency someDependency; + + public Converter3(@Autowired SomeDependency someDependency) { + this.someDependency = someDependency; + } + + @Override + public Object nativeToDomain(org.bson.Document value) { + return value.get("ooo"); + } + + @Override + public org.bson.Document domainToNative(Object value) { + return new org.bson.Document("ooo", value + " - " + someDependency.toString()); + } + } + + static class SomeDependency { + + } + + enum Converter2 implements PropertyValueConverter { + + INSTANCE; + + @Nullable + @Override + public String nativeToDomain(@Nullable org.bson.Document value) { + return value.getString("bar"); + } + + @Nullable + @Override + public org.bson.Document domainToNative(@Nullable String value) { + return new org.bson.Document("bar", value); + } + } + + static class Converter1 implements PropertyValueConverter { + + @Nullable + @Override + public String nativeToDomain(@Nullable org.bson.Document value) { + return value.getString("foo"); + } + + @Nullable + @Override + public org.bson.Document domainToNative(@Nullable String value) { + return new org.bson.Document("foo", value); + } + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index b58846fb97..a92a1ab891 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -31,16 +31,8 @@ import org.jmolecules.ddd.annotation.Identity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.annotation.AliasFor; import org.springframework.data.annotation.Id; -import org.springframework.data.convert.PropertyConverter; -import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.model.FieldNamingStrategy; @@ -258,48 +250,6 @@ void considersJMoleculesIdentityExplicitlyAnnotatedIdentifier() { assertThat(property.isExplicitIdProperty()).isTrue(); } - @Test - void xxx () { - - MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value"); - PropertyValueConverter valueConverter = property.getValueConverter(); - - assertThat(valueConverter).isInstanceOf(MyPropertyConverter.class); - } - - @Test - void xxx2 () { - - MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value"); - ((BasicMongoPersistentEntity)property.getOwner()).beanFactory = new DefaultListableBeanFactory(); - PropertyValueConverter valueConverter = property.getValueConverter(); - - assertThat(valueConverter).isInstanceOf(MyPropertyConverter.class); - } - - @Test - void xxx3 () { - - MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value2"); - ((BasicMongoPersistentEntity)property.getOwner()).beanFactory = new DefaultListableBeanFactory(); - assertThatExceptionOfType(BeanCreationException.class).isThrownBy(()-> property.getValueConverter()); - } - - @Test - void xxx4 () { - - DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); - defaultListableBeanFactory.registerBeanDefinition("someDependency", BeanDefinitionBuilder - .rootBeanDefinition(SomeDependency.class) - .getBeanDefinition()); - - MongoPersistentProperty property = getPropertyFor(WithPropertyConverter.class, "value2"); - ((BasicMongoPersistentEntity)property.getOwner()).beanFactory = defaultListableBeanFactory ; - PropertyValueConverter valueConverter = property.getValueConverter(); - - assertThat(valueConverter).isInstanceOf(MyPropertyConverterThatRequiresComponents.class); - } - private MongoPersistentProperty getPropertyFor(Field field) { return getPropertyFor(entity, field); } @@ -431,49 +381,4 @@ static class WithComplexId { static class WithJMoleculesIdentity { @Identity ObjectId identifier; } - - static class WithPropertyConverter { - - @PropertyConverter(MyPropertyConverter.class) - String value; - - @PropertyConverter(MyPropertyConverterThatRequiresComponents.class) - String value2; - } - - static class MyPropertyConverter implements PropertyValueConverter { - - @Override - public Object read(Object value) { - return null; - } - - @Override - public Object write(Object value) { - return null; - } - } - - static class MyPropertyConverterThatRequiresComponents implements PropertyValueConverter { - - private final SomeDependency someDependency; - - public MyPropertyConverterThatRequiresComponents(@Autowired SomeDependency someDependency) { - this.someDependency = someDependency; - } - - @Override - public Object read(Object value) { - return null; - } - - @Override - public Object write(Object value) { - return null; - } - } - - static class SomeDependency { - - } } From b3cc73a643698671630d8df2e746fc805445f062 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 18 Jan 2022 15:23:55 +0100 Subject: [PATCH 4/7] Follow recent changes ValueConversionContext and Configuration --- .../core/convert/MappingMongoConverter.java | 29 +++++++-- .../core/convert/MongoConversionContext.java | 63 ++++++++++++++++++ .../core/convert/MongoCustomConversions.java | 25 ++++++- .../core/convert/MongoValueConverter.java | 43 ++++++++++++ .../MappingMongoConverterUnitTests.java | 65 ++++++++++++++----- .../MongoCustomConversionsUnitTests.java | 47 ++++++++++++++ 6 files changed, 248 insertions(+), 24 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.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 4708436ee6..2f65e86ced 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 @@ -38,7 +38,6 @@ import org.bson.conversions.Bson; import org.bson.json.JsonReader; import org.bson.types.ObjectId; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.context.ApplicationContext; @@ -958,8 +957,13 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce TypeInformation valueType = ClassTypeInformation.from(obj.getClass()); TypeInformation type = prop.getTypeInformation(); - if(prop.hasValueConverter()) { - accessor.put(prop, conversions.getPropertyValueConverterFactory().getConverter(prop).domainToNative(obj)); + if(conversions.hasPropertyValueConverter(prop)) { + accessor.put(prop, conversions.getPropertyValueConverter(prop).domainToNative(obj, new MongoConversionContext() { + @Override + public MongoPersistentProperty getProperty() { + return prop; + } + })); return; } @@ -1293,8 +1297,13 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, String key) private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersistentProperty property) { DocumentAccessor accessor = new DocumentAccessor(bson); - if(property.hasValueConverter()) { - accessor.put(property, conversions.getPropertyValueConverterFactory().getConverter(property).domainToNative(value)); + if(conversions.hasPropertyValueConverter(property)) { + accessor.put(property, conversions.getPropertyValueConverter(property).domainToNative(value, new MongoConversionContext() { + @Override + public MongoPersistentProperty getProperty() { + return property; + } + })); return; } @@ -1961,8 +1970,14 @@ public T getPropertyValue(MongoPersistentProperty property) { return null; } - if(property.hasValueConverter()) { - return (T) context.conversions.getPropertyValueConverterFactory().getConverter(property).nativeToDomain(value); + if(context.conversions.hasPropertyValueConverter(property)) { + + return (T) context.conversions.getPropertyValueConverter(property).nativeToDomain(value, new MongoConversionContext() { + @Override + public MongoPersistentProperty getProperty() { + return property; + } + }); } return (T) context.convert(value, property.getTypeInformation()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java new file mode 100644 index 0000000000..0aa53bfb32 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022. 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 + * + * http://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. + */ + +/* + * Copyright 2022. 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 + * + * http://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. + */ + +/* + * Copyright 2022 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 + * + * http://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.springframework.data.convert.PropertyValueConverter.ValueConversionContext; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +/** + * @author Christoph Strobl + * @since 2022/01 + */ +class MongoConversionContext implements ValueConversionContext { + + @Override + public MongoPersistentProperty getProperty() { + return null; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java index 5881f8f66f..a241fdfb3b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java @@ -37,7 +37,11 @@ import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.data.convert.JodaTimeConverters; +import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactory; +import org.springframework.data.convert.PropertyValueConverterRegistrar; +import org.springframework.data.convert.SimplePropertyValueConversions; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; @@ -161,6 +165,7 @@ public static class MongoConverterConfigurationAdapter { private final List customConverters = new ArrayList<>(); private @Nullable PropertyValueConverterFactory propertyValueConverterFactory; + private PropertyValueConverterRegistrar propertyValueConverterRegistrar; /** * Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for @@ -233,6 +238,17 @@ public MongoConverterConfigurationAdapter registerConverter(Converter conv return this; } + public MongoConverterConfigurationAdapter registerConverter(Class type, String path, PropertyValueConverter converter) { + + if(propertyValueConverterRegistrar == null) { + propertyValueConverterRegistrar = new PropertyValueConverterRegistrar(); + } + + propertyValueConverterRegistrar.register(type, path, converter); + //TODO: create a property path for it + return this; + } + /** * Add a custom {@link ConverterFactory} implementation. * @@ -269,8 +285,13 @@ public MongoConverterConfigurationAdapter registerPropertyValueConverterFactory( ConverterConfiguration createConverterConfiguration() { + SimplePropertyValueConversions pvc = new SimplePropertyValueConversions(); + pvc.setConverterFactory(propertyValueConverterFactory); + pvc.setConverterRegistrar(propertyValueConverterRegistrar); + pvc.init(); + if (!useNativeDriverJavaTimeCodecs) { - return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, propertyValueConverterFactory); + return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, pvc); } /* @@ -295,7 +316,7 @@ ConverterConfiguration createConverterConfiguration() { } return true; - }, propertyValueConverterFactory); + }, pvc); } private enum DateToUtcLocalDateTimeConverter implements Converter { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java new file mode 100644 index 0000000000..6c56e3f567 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022. 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 + * + * http://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. + */ + +/* + * Copyright 2022 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 + * + * http://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.springframework.data.convert.PropertyValueConverter; + +/** + * @author Christoph Strobl + * @since 2022/01 + */ +interface MongoValueConverter extends PropertyValueConverter { + + +} 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 db9fb19d3e..51380bdc4f 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 @@ -57,10 +57,10 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.TypeAlias; -import org.springframework.data.convert.BeanFactoryAwarePropertyValueConverterFactory; import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.PropertyConverter; import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; @@ -3485,14 +3485,12 @@ void beanConverter() { DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); defaultListableBeanFactory.registerBeanDefinition("someDependency", BeanDefinitionBuilder.rootBeanDefinition(SomeDependency.class).getBeanDefinition()); - - BeanFactoryAwarePropertyValueConverterFactory beanFactoryAwareConverterFactory = new BeanFactoryAwarePropertyValueConverterFactory(); - beanFactoryAwareConverterFactory.setBeanFactory(defaultListableBeanFactory); - + ; converter = new MappingMongoConverter(resolver, mappingContext); converter.setCustomConversions(MongoCustomConversions.create(it -> { - it.registerPropertyValueConverterFactory(PropertyValueConverterFactory.caching(beanFactoryAwareConverterFactory)); + it.registerPropertyValueConverterFactory( + PropertyValueConverterFactory.beanFactoryAware(defaultListableBeanFactory)); })); converter.afterPropertiesSet(); @@ -3511,6 +3509,41 @@ void beanConverter() { assertThat(read.converterBean).startsWith("spring -"); } + @Test // GH-3596 + void pathConfiguredConverter/*no annotation required*/() { + + converter = new MappingMongoConverter(resolver, mappingContext); + + converter.setCustomConversions(MongoCustomConversions.create(it -> { + + it.registerConverter(WithValueConverters.class, "viaRegisteredConverter", + new PropertyValueConverter() { + @Nullable + @Override + public String nativeToDomain(@Nullable org.bson.Document nativeValue, ValueConversionContext context) { + return nativeValue.getString("bar"); + } + + @Nullable + @Override + public org.bson.Document domainToNative(@Nullable String domainValue, ValueConversionContext context) { + return new org.bson.Document("bar", domainValue); + } + }); + })); + + WithValueConverters wvc = new WithValueConverters(); + wvc.viaRegisteredConverter = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target).containsEntry("viaRegisteredConverter", new org.bson.Document("bar", "spring")); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.viaRegisteredConverter).isEqualTo("spring"); + } + static class WithValueConverters { @PropertyConverter(Converter1.class) String converterWithDefaultCtor; @@ -3518,9 +3551,11 @@ static class WithValueConverters { @PropertyConverter(Converter2.class) String converterEnum; @PropertyConverter(Converter3.class) String converterBean; + + String viaRegisteredConverter; } - static class Converter3 implements PropertyValueConverter { + static class Converter3 implements MongoValueConverter { private final SomeDependency someDependency; @@ -3529,12 +3564,12 @@ public Converter3(@Autowired SomeDependency someDependency) { } @Override - public Object nativeToDomain(org.bson.Document value) { + public Object nativeToDomain(org.bson.Document value, MongoConversionContext context) { return value.get("ooo"); } @Override - public org.bson.Document domainToNative(Object value) { + public org.bson.Document domainToNative(Object value, MongoConversionContext context) { return new org.bson.Document("ooo", value + " - " + someDependency.toString()); } } @@ -3543,34 +3578,34 @@ static class SomeDependency { } - enum Converter2 implements PropertyValueConverter { + enum Converter2 implements MongoValueConverter { INSTANCE; @Nullable @Override - public String nativeToDomain(@Nullable org.bson.Document value) { + public String nativeToDomain(@Nullable org.bson.Document value, MongoConversionContext context) { return value.getString("bar"); } @Nullable @Override - public org.bson.Document domainToNative(@Nullable String value) { + public org.bson.Document domainToNative(@Nullable String value, MongoConversionContext context) { return new org.bson.Document("bar", value); } } - static class Converter1 implements PropertyValueConverter { + static class Converter1 implements MongoValueConverter { @Nullable @Override - public String nativeToDomain(@Nullable org.bson.Document value) { + public String nativeToDomain(@Nullable org.bson.Document value, MongoConversionContext context) { return value.getString("foo"); } @Nullable @Override - public org.bson.Document domainToNative(@Nullable String value) { + public org.bson.Document domainToNative(@Nullable String value, MongoConversionContext context) { return new org.bson.Document("foo", value); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java index 1509a8df77..27f7d466fe 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java @@ -24,6 +24,10 @@ import org.junit.jupiter.api.Test; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; +import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.Foo; +import org.springframework.lang.Nullable; /** * Unit tests for {@link MongoCustomConversions}. @@ -49,4 +53,47 @@ public ZonedDateTime convert(Date source) { return ZonedDateTime.now(); } } + + @Test + void howToConfigure() { + + + MongoCustomConversions.create(config -> { + + config.registerConverter(Foo.class, "name", new PropertyValueConverter() { + + @Nullable + @Override + public Object nativeToDomain(@Nullable Object nativeValue, ValueConversionContext context) { + return null; + } + + @Nullable + @Override + public Object domainToNative(@Nullable Object domainValue, ValueConversionContext context) { + return null; + } + }); + + + // lambda variant - generics + + // SD-Rest - LookupInformation/Lookup - Invocation Recorder -> BiFunction mit target type + +// config.registerConverter(Foo.class, Foo::getName, (in, ctx) -> {}, (out, ctx) -> {}); .as() { + +// @Nullable +// @Override +// public Object nativeToDomain(@Nullable Object nativeValue, ValueConversionContext context) { +// return null; +// } +// +// @Nullable +// @Override +// public Object domainToNative(@Nullable Object domainValue, ValueConversionContext context) { +// return null; +// } +// }); + }); + } } From ef3fd84f26904aa6a01de1867f4e476e78aa9d14 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 25 Feb 2022 08:34:02 +0100 Subject: [PATCH 5/7] Adat to changes in commons --- .../core/convert/MappingMongoConverter.java | 6 +-- .../core/convert/MongoConversionContext.java | 38 ++----------------- .../core/convert/MongoCustomConversions.java | 23 ++++++++--- .../core/convert/MongoValueConverter.java | 18 +-------- .../core/mapping/MongoPersistentProperty.java | 2 - .../MappingMongoConverterUnitTests.java | 28 +++++++------- .../MongoCustomConversionsUnitTests.java | 7 ++-- 7 files changed, 42 insertions(+), 80 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 2f65e86ced..32439d7d6b 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 @@ -958,7 +958,7 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce TypeInformation type = prop.getTypeInformation(); if(conversions.hasPropertyValueConverter(prop)) { - accessor.put(prop, conversions.getPropertyValueConverter(prop).domainToNative(obj, new MongoConversionContext() { + accessor.put(prop, conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext() { @Override public MongoPersistentProperty getProperty() { return prop; @@ -1298,7 +1298,7 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersist DocumentAccessor accessor = new DocumentAccessor(bson); if(conversions.hasPropertyValueConverter(property)) { - accessor.put(property, conversions.getPropertyValueConverter(property).domainToNative(value, new MongoConversionContext() { + accessor.put(property, conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext() { @Override public MongoPersistentProperty getProperty() { return property; @@ -1972,7 +1972,7 @@ public T getPropertyValue(MongoPersistentProperty property) { if(context.conversions.hasPropertyValueConverter(property)) { - return (T) context.conversions.getPropertyValueConverter(property).nativeToDomain(value, new MongoConversionContext() { + return (T) context.conversions.getPropertyValueConverter(property).read(value, new MongoConversionContext() { @Override public MongoPersistentProperty getProperty() { return property; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java index 0aa53bfb32..05b8d1a722 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java @@ -1,35 +1,3 @@ -/* - * Copyright 2022. 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 - * - * http://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. - */ - -/* - * Copyright 2022. 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 - * - * http://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. - */ - /* * Copyright 2022 the original author or authors. * @@ -37,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -47,14 +15,14 @@ */ package org.springframework.data.mongodb.core.convert; -import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; +import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; /** * @author Christoph Strobl * @since 2022/01 */ -class MongoConversionContext implements ValueConversionContext { +class MongoConversionContext implements ValueConversionContext { @Override public MongoPersistentProperty getProperty() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java index a241fdfb3b..ffa1b55e7b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java @@ -38,12 +38,13 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.data.convert.JodaTimeConverters; import org.springframework.data.convert.PropertyValueConverter; -import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; +import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.PropertyValueConverterRegistrar; import org.springframework.data.convert.SimplePropertyValueConversions; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -165,7 +166,7 @@ public static class MongoConverterConfigurationAdapter { private final List customConverters = new ArrayList<>(); private @Nullable PropertyValueConverterFactory propertyValueConverterFactory; - private PropertyValueConverterRegistrar propertyValueConverterRegistrar; + private PropertyValueConverterRegistrar propertyValueConverterRegistrar; /** * Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for @@ -238,17 +239,27 @@ public MongoConverterConfigurationAdapter registerConverter(Converter conv return this; } - public MongoConverterConfigurationAdapter registerConverter(Class type, String path, PropertyValueConverter converter) { + public MongoConverterConfigurationAdapter registerConverter(Class type, String path, PropertyValueConverter converter) { if(propertyValueConverterRegistrar == null) { propertyValueConverterRegistrar = new PropertyValueConverterRegistrar(); } - propertyValueConverterRegistrar.register(type, path, converter); + propertyValueConverterRegistrar.registerConverter(type, path, converter); //TODO: create a property path for it return this; } + public MongoConverterConfigurationAdapter propertyConversions(Consumer> config) { + + if(propertyValueConverterRegistrar == null) { + propertyValueConverterRegistrar = new PropertyValueConverterRegistrar(); + } + + config.accept(propertyValueConverterRegistrar); + return this; + } + /** * Add a custom {@link ConverterFactory} implementation. * @@ -287,7 +298,9 @@ ConverterConfiguration createConverterConfiguration() { SimplePropertyValueConversions pvc = new SimplePropertyValueConversions(); pvc.setConverterFactory(propertyValueConverterFactory); - pvc.setConverterRegistrar(propertyValueConverterRegistrar); + if(propertyValueConverterRegistrar != null) { + pvc.setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry()); + } pvc.init(); if (!useNativeDriverJavaTimeCodecs) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java index 6c56e3f567..a0b5d2c182 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java @@ -1,19 +1,3 @@ -/* - * Copyright 2022. 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 - * - * http://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. - */ - /* * Copyright 2022 the original author or authors. * @@ -21,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java index 46909b3670..b2836e85fb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java @@ -19,8 +19,6 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; -import org.springframework.data.convert.PropertyConverter; -import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; 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 51380bdc4f..113b89fbf7 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 @@ -58,9 +58,9 @@ import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.convert.CustomConversions; -import org.springframework.data.convert.PropertyConverter; +import org.springframework.data.convert.ValueConverter; import org.springframework.data.convert.PropertyValueConverter; -import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; +import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; @@ -3517,16 +3517,16 @@ void beanConverter() { converter.setCustomConversions(MongoCustomConversions.create(it -> { it.registerConverter(WithValueConverters.class, "viaRegisteredConverter", - new PropertyValueConverter() { + new PropertyValueConverter() { @Nullable @Override - public String nativeToDomain(@Nullable org.bson.Document nativeValue, ValueConversionContext context) { + public String read(@Nullable org.bson.Document nativeValue, MongoConversionContext context) { return nativeValue.getString("bar"); } @Nullable @Override - public org.bson.Document domainToNative(@Nullable String domainValue, ValueConversionContext context) { + public org.bson.Document write(@Nullable String domainValue, MongoConversionContext context) { return new org.bson.Document("bar", domainValue); } }); @@ -3546,11 +3546,11 @@ public org.bson.Document domainToNative(@Nullable String domainValue, ValueConve static class WithValueConverters { - @PropertyConverter(Converter1.class) String converterWithDefaultCtor; + @ValueConverter(Converter1.class) String converterWithDefaultCtor; - @PropertyConverter(Converter2.class) String converterEnum; + @ValueConverter(Converter2.class) String converterEnum; - @PropertyConverter(Converter3.class) String converterBean; + @ValueConverter(Converter3.class) String converterBean; String viaRegisteredConverter; } @@ -3564,12 +3564,12 @@ public Converter3(@Autowired SomeDependency someDependency) { } @Override - public Object nativeToDomain(org.bson.Document value, MongoConversionContext context) { + public Object read(org.bson.Document value, MongoConversionContext context) { return value.get("ooo"); } @Override - public org.bson.Document domainToNative(Object value, MongoConversionContext context) { + public org.bson.Document write(Object value, MongoConversionContext context) { return new org.bson.Document("ooo", value + " - " + someDependency.toString()); } } @@ -3584,13 +3584,13 @@ enum Converter2 implements MongoValueConverter { @Nullable @Override - public String nativeToDomain(@Nullable org.bson.Document value, MongoConversionContext context) { + public String read(@Nullable org.bson.Document value, MongoConversionContext context) { return value.getString("bar"); } @Nullable @Override - public org.bson.Document domainToNative(@Nullable String value, MongoConversionContext context) { + public org.bson.Document write(@Nullable String value, MongoConversionContext context) { return new org.bson.Document("bar", value); } } @@ -3599,13 +3599,13 @@ static class Converter1 implements MongoValueConverter { - config.registerConverter(Foo.class, "name", new PropertyValueConverter() { + config.registerConverter(Foo.class, "name", new MongoValueConverter() { @Nullable @Override - public Object nativeToDomain(@Nullable Object nativeValue, ValueConversionContext context) { + public Object read(@Nullable Object nativeValue, MongoConversionContext context) { return null; } @Nullable @Override - public Object domainToNative(@Nullable Object domainValue, ValueConversionContext context) { + public Object write(@Nullable Object domainValue, MongoConversionContext context) { return null; } }); From c10b19b2d19138e230ae6a3555556f9d093031f2 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 2 Mar 2022 13:07:21 +0100 Subject: [PATCH 6/7] Make sure query and update mappers consider PropertyValueConverters. --- .../core/convert/MappingMongoConverter.java | 39 ++-- .../core/convert/MongoConversionContext.java | 35 +++- .../core/convert/MongoCustomConversions.java | 86 +++++--- .../core/convert/MongoValueConverter.java | 7 +- .../mongodb/core/convert/QueryMapper.java | 4 + .../mapping/BasicMongoPersistentEntity.java | 3 - .../mapping/BasicMongoPersistentProperty.java | 3 - .../core/mapping/MongoMappingContext.java | 4 +- .../UnwrappedMongoPersistentProperty.java | 1 - .../MappingMongoConverterUnitTests.java | 196 +++++++++--------- .../MongoCustomConversionsUnitTests.java | 66 ++---- .../core/convert/QueryMapperUnitTests.java | 15 +- .../core/convert/ReversingValueConverter.java | 45 ++++ .../core/convert/UpdateMapperUnitTests.java | 16 ++ 14 files changed, 307 insertions(+), 213 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReversingValueConverter.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 32439d7d6b..2e2d9fc6fd 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 @@ -176,7 +176,7 @@ protected ConversionContext getConversionContext(ObjectPath path) { Assert.notNull(path, "ObjectPath must not be null"); - return new ConversionContext(conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap, + return new ConversionContext(this, conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap, this::readDBRef, this::getPotentiallyConvertedSimpleRead); } @@ -306,7 +306,7 @@ public R project(EntityProjection projection, Bson bson) { return (R) read(typeToRead, bson); } - ProjectingConversionContext context = new ProjectingConversionContext(conversions, ObjectPath.ROOT, + ProjectingConversionContext context = new ProjectingConversionContext(this, conversions, ObjectPath.ROOT, this::readCollectionOrArray, this::readMap, this::readDBRef, this::getPotentiallyConvertedSimpleRead, projection); @@ -390,11 +390,11 @@ class ProjectingConversionContext extends ConversionContext { private final EntityProjection returnedTypeDescriptor; - ProjectingConversionContext(CustomConversions customConversions, ObjectPath path, + ProjectingConversionContext(MongoConverter sourceConverter, CustomConversions customConversions, ObjectPath path, ContainerValueConverter> collectionConverter, ContainerValueConverter mapConverter, ContainerValueConverter dbRefConverter, ValueConverter elementConverter, EntityProjection projection) { - super(customConversions, path, + super(sourceConverter, customConversions, path, (context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection), collectionConverter, mapConverter, dbRefConverter, elementConverter); @@ -409,13 +409,13 @@ public ConversionContext forProperty(String name) { return super.forProperty(name); } - return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter, + return new ProjectingConversionContext(sourceConverter, conversions, path, collectionConverter, mapConverter, dbRefConverter, elementConverter, property); } @Override public ConversionContext withPath(ObjectPath currentPath) { - return new ProjectingConversionContext(conversions, currentPath, collectionConverter, mapConverter, + return new ProjectingConversionContext(sourceConverter, conversions, currentPath, collectionConverter, mapConverter, dbRefConverter, elementConverter, returnedTypeDescriptor); } } @@ -958,12 +958,7 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce TypeInformation type = prop.getTypeInformation(); if(conversions.hasPropertyValueConverter(prop)) { - accessor.put(prop, conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext() { - @Override - public MongoPersistentProperty getProperty() { - return prop; - } - })); + accessor.put(prop, conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext(prop, this))); return; } @@ -1298,12 +1293,7 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersist DocumentAccessor accessor = new DocumentAccessor(bson); if(conversions.hasPropertyValueConverter(property)) { - accessor.put(property, conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext() { - @Override - public MongoPersistentProperty getProperty() { - return property; - } - })); + accessor.put(property, conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext(property, this))); return; } @@ -1972,12 +1962,7 @@ public T getPropertyValue(MongoPersistentProperty property) { if(context.conversions.hasPropertyValueConverter(property)) { - return (T) context.conversions.getPropertyValueConverter(property).read(value, new MongoConversionContext() { - @Override - public MongoPersistentProperty getProperty() { - return property; - } - }); + return (T) context.conversions.getPropertyValueConverter(property).read(value, new MongoConversionContext(property, context.sourceConverter)); } return (T) context.convert(value, property.getTypeInformation()); @@ -2209,6 +2194,7 @@ public org.springframework.data.util.TypeInformation specialize(Cla */ protected static class ConversionContext { + final MongoConverter sourceConverter; final org.springframework.data.convert.CustomConversions conversions; final ObjectPath path; final ContainerValueConverter documentConverter; @@ -2217,11 +2203,12 @@ protected static class ConversionContext { final ContainerValueConverter dbRefConverter; final ValueConverter elementConverter; - ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path, + ConversionContext(MongoConverter sourceConverter, org.springframework.data.convert.CustomConversions customConversions, ObjectPath path, ContainerValueConverter documentConverter, ContainerValueConverter> collectionConverter, ContainerValueConverter mapConverter, ContainerValueConverter dbRefConverter, ValueConverter elementConverter) { + this.sourceConverter = sourceConverter; this.conversions = customConversions; this.path = path; this.documentConverter = documentConverter; @@ -2303,7 +2290,7 @@ public ConversionContext withPath(ObjectPath currentPath) { Assert.notNull(currentPath, "ObjectPath must not be null"); - return new ConversionContext(conversions, currentPath, documentConverter, collectionConverter, mapConverter, + return new ConversionContext(sourceConverter, conversions, currentPath, documentConverter, collectionConverter, mapConverter, dbRefConverter, elementConverter); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java index 05b8d1a722..9a83832d4c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java @@ -15,17 +15,46 @@ */ package org.springframework.data.mongodb.core.convert; +import org.bson.conversions.Bson; import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** + * {@link ValueConversionContext} that allows to delegate read/write to an underlying {@link MongoConverter}. + * * @author Christoph Strobl - * @since 2022/01 + * @since 3.4 */ -class MongoConversionContext implements ValueConversionContext { +public class MongoConversionContext implements ValueConversionContext { + + private final MongoPersistentProperty persistentProperty; + private final MongoConverter mongoConverter; + + public MongoConversionContext(MongoPersistentProperty persistentProperty, MongoConverter mongoConverter) { + + this.persistentProperty = persistentProperty; + this.mongoConverter = mongoConverter; + } @Override public MongoPersistentProperty getProperty() { - return null; + return persistentProperty; + } + + @Override + public T write(@Nullable Object value, TypeInformation target) { + return (T) mongoConverter.convertToMongoType(value, target); + } + + @Override + public T read(@Nullable Object value, TypeInformation target) { + + if (!(value instanceof Bson)) { + return ValueConversionContext.super.read(value, target); + } + + return mongoConverter.read(target.getType(), (Bson) value); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java index ffa1b55e7b..52927d21de 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java @@ -37,8 +37,8 @@ import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.data.convert.JodaTimeConverters; +import org.springframework.data.convert.PropertyValueConversions; import org.springframework.data.convert.PropertyValueConverter; -import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.PropertyValueConverterRegistrar; import org.springframework.data.convert.SimplePropertyValueConversions; @@ -165,8 +165,7 @@ public static class MongoConverterConfigurationAdapter { private boolean useNativeDriverJavaTimeCodecs = false; private final List customConverters = new ArrayList<>(); - private @Nullable PropertyValueConverterFactory propertyValueConverterFactory; - private PropertyValueConverterRegistrar propertyValueConverterRegistrar; + private PropertyValueConversions propertyValueConversions = new SimplePropertyValueConversions(); /** * Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for @@ -239,24 +238,24 @@ public MongoConverterConfigurationAdapter registerConverter(Converter conv return this; } - public MongoConverterConfigurationAdapter registerConverter(Class type, String path, PropertyValueConverter converter) { - - if(propertyValueConverterRegistrar == null) { - propertyValueConverterRegistrar = new PropertyValueConverterRegistrar(); - } - - propertyValueConverterRegistrar.registerConverter(type, path, converter); - //TODO: create a property path for it - return this; - } + /** + * Gateway to register property specific converters. + * + * @param configurationAdapter must not be {@literal null}. + * @return this. + * @since 3.4 + */ + public MongoConverterConfigurationAdapter configurePropertyConversions( + Consumer> configurationAdapter) { - public MongoConverterConfigurationAdapter propertyConversions(Consumer> config) { + Assert.state(valueConversions() instanceof SimplePropertyValueConversions, + "Configured PropertyValueConversions does not allow setting custom ConverterRegistry."); - if(propertyValueConverterRegistrar == null) { - propertyValueConverterRegistrar = new PropertyValueConverterRegistrar(); - } + PropertyValueConverterRegistrar propertyValueConverterRegistrar = new PropertyValueConverterRegistrar(); + configurationAdapter.accept(propertyValueConverterRegistrar); - config.accept(propertyValueConverterRegistrar); + ((SimplePropertyValueConversions) valueConversions()) + .setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry()); return this; } @@ -288,23 +287,54 @@ public MongoConverterConfigurationAdapter registerConverters(Collection conve return this; } - public MongoConverterConfigurationAdapter registerPropertyValueConverterFactory(PropertyValueConverterFactory converterFactory) { + /** + * Add a custom/default {@link PropertyValueConverterFactory} implementation used to serve + * {@link PropertyValueConverter}. + * + * @param converterFactory must not be {@literal null}. + * @return this. + * @since 3.4 + */ + public MongoConverterConfigurationAdapter registerPropertyValueConverterFactory( + PropertyValueConverterFactory converterFactory) { - this.propertyValueConverterFactory = converterFactory; + Assert.state(valueConversions() instanceof SimplePropertyValueConversions, + "Configured PropertyValueConversions does not allow setting custom ConverterRegistry."); + + ((SimplePropertyValueConversions) valueConversions()).setConverterFactory(converterFactory); return this; } - ConverterConfiguration createConverterConfiguration() { + /** + * Optionally set the {@link PropertyValueConversions} to be applied during mapping. + *

+ * Use this method if {@link #configurePropertyConversions(Consumer)} and + * {@link #registerPropertyValueConverterFactory(PropertyValueConverterFactory)} are not sufficient. + * + * @param valueConversions must not be {@literal null}. + * @return this. + * @since 3.4 + */ + public MongoConverterConfigurationAdapter setPropertyValueConversions(PropertyValueConversions valueConversions) { + + this.propertyValueConversions = valueConversions; + return this; + } - SimplePropertyValueConversions pvc = new SimplePropertyValueConversions(); - pvc.setConverterFactory(propertyValueConverterFactory); - if(propertyValueConverterRegistrar != null) { - pvc.setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry()); + PropertyValueConversions valueConversions() { + + if (this.propertyValueConversions == null) { + this.propertyValueConversions = new SimplePropertyValueConversions(); } - pvc.init(); + + return this.propertyValueConversions; + } + + ConverterConfiguration createConverterConfiguration() { if (!useNativeDriverJavaTimeCodecs) { - return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, pvc); + return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, + this.propertyValueConversions); } /* @@ -329,7 +359,7 @@ ConverterConfiguration createConverterConfiguration() { } return true; - }, pvc); + }, this.propertyValueConversions); } private enum DateToUtcLocalDateTimeConverter implements Converter { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java index a0b5d2c182..49b9021cc0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java @@ -18,10 +18,11 @@ import org.springframework.data.convert.PropertyValueConverter; /** + * Pre typed {@link PropertyValueConverter} specific for the Data MongoDB module. + * * @author Christoph Strobl - * @since 2022/01 + * @since 3.4 */ -interface MongoValueConverter extends PropertyValueConverter { - +public interface MongoValueConverter extends PropertyValueConverter { } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index ac1794b87d..174667435a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -434,6 +434,10 @@ protected Object getMappedValue(Field documentField, Object sourceValue) { Object value = applyFieldTargetTypeHintToValue(documentField, sourceValue); + if(documentField.getProperty() != null && converter.getCustomConversions().hasPropertyValueConverter(documentField.getProperty())) { + return converter.getCustomConversions().getPropertyValueConverter(documentField.getProperty()).write(value, new MongoConversionContext(documentField.getProperty(), converter)); + } + if (documentField.isIdField() && !documentField.isAssociation()) { if (isDBObject(value)) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java index f3f24e4b22..ee900c25e7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; -import org.springframework.beans.factory.BeanFactory; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; @@ -74,8 +73,6 @@ public class BasicMongoPersistentEntity extends BasicPersistentEntity } } } - - } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java index a34d73297b..f796a554e4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java @@ -97,9 +97,7 @@ public MongoPersistentProperty createPersistentProperty(Property property, Mongo */ @Override protected BasicMongoPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity<>(typeInformation); - entity.beanFactory = applicationContext; - return entity; + return new BasicMongoPersistentEntity<>(typeInformation); } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java index 342c08f3bf..d163f23989 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java @@ -20,7 +20,6 @@ import java.lang.reflect.Method; import java.util.Collection; -import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; 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 113b89fbf7..99797db9a4 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 @@ -58,11 +58,10 @@ import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.convert.CustomConversions; -import org.springframework.data.convert.ValueConverter; import org.springframework.data.convert.PropertyValueConverter; -import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.ValueConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.geo.Box; import org.springframework.data.geo.Circle; @@ -2718,6 +2717,104 @@ void projectShouldReadProjectionWithNestedEntity() { assertThat(person.getAddresses()).extracting(Address::getStreet).hasSize(1).containsOnly("hwy"); } + @Test // GH-3596 + void simpleConverter() { + + WithValueConverters wvc = new WithValueConverters(); + wvc.converterWithDefaultCtor = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target).containsEntry("converterWithDefaultCtor", new org.bson.Document("foo", "spring")); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.converterWithDefaultCtor).startsWith("spring"); + } + + @Test // GH-3596 + void enumConverter() { + + WithValueConverters wvc = new WithValueConverters(); + wvc.converterEnum = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target).containsEntry("converterEnum", new org.bson.Document("bar", "spring")); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.converterEnum).isEqualTo("spring"); + } + + @Test // GH-3596 + void beanConverter() { + + DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); + defaultListableBeanFactory.registerBeanDefinition("someDependency", + BeanDefinitionBuilder.rootBeanDefinition(SomeDependency.class).getBeanDefinition()); + + converter = new MappingMongoConverter(resolver, mappingContext); + + converter.setCustomConversions(MongoCustomConversions.create(it -> { + it.registerPropertyValueConverterFactory( + PropertyValueConverterFactory.beanFactoryAware(defaultListableBeanFactory)); + })); + converter.afterPropertiesSet(); + + WithValueConverters wvc = new WithValueConverters(); + wvc.converterBean = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target.get("converterBean", org.bson.Document.class)).satisfies(it -> { + assertThat(it).containsKey("ooo"); + assertThat((String) it.get("ooo")).startsWith("spring - "); + }); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.converterBean).startsWith("spring -"); + } + + @Test // GH-3596 + void pathConfiguredConverter/*no annotation required*/() { + + converter = new MappingMongoConverter(resolver, mappingContext); + + converter.setCustomConversions(MongoCustomConversions.create(it -> { + + it.configurePropertyConversions(registrar -> { + registrar.registerConverter(WithValueConverters.class, "viaRegisteredConverter", + new PropertyValueConverter() { + + @Nullable + @Override + public String read(@Nullable org.bson.Document nativeValue, MongoConversionContext context) { + return nativeValue.getString("bar"); + } + + @Nullable + @Override + public org.bson.Document write(@Nullable String domainValue, MongoConversionContext context) { + return new org.bson.Document("bar", domainValue); + } + }); + }); + })); + + WithValueConverters wvc = new WithValueConverters(); + wvc.viaRegisteredConverter = "spring"; + + org.bson.Document target = new org.bson.Document(); + converter.write(wvc, target); + + assertThat(target).containsEntry("viaRegisteredConverter", new org.bson.Document("bar", "spring")); + + WithValueConverters read = converter.read(WithValueConverters.class, target); + assertThat(read.viaRegisteredConverter).isEqualTo("spring"); + } + static class GenericType { T content; } @@ -3449,101 +3546,6 @@ static class WithFieldWrite { } - @Test // GH-3596 - void simpleConverter() { - - WithValueConverters wvc = new WithValueConverters(); - wvc.converterWithDefaultCtor = "spring"; - - org.bson.Document target = new org.bson.Document(); - converter.write(wvc, target); - - assertThat(target).containsEntry("converterWithDefaultCtor", new org.bson.Document("foo", "spring")); - - WithValueConverters read = converter.read(WithValueConverters.class, target); - assertThat(read.converterWithDefaultCtor).startsWith("spring"); - } - - @Test // GH-3596 - void enumConverter() { - - WithValueConverters wvc = new WithValueConverters(); - wvc.converterEnum = "spring"; - - org.bson.Document target = new org.bson.Document(); - converter.write(wvc, target); - - assertThat(target).containsEntry("converterEnum", new org.bson.Document("bar", "spring")); - - WithValueConverters read = converter.read(WithValueConverters.class, target); - assertThat(read.converterEnum).isEqualTo("spring"); - } - - @Test // GH-3596 - void beanConverter() { - - DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); - defaultListableBeanFactory.registerBeanDefinition("someDependency", - BeanDefinitionBuilder.rootBeanDefinition(SomeDependency.class).getBeanDefinition()); - ; - converter = new MappingMongoConverter(resolver, mappingContext); - - converter.setCustomConversions(MongoCustomConversions.create(it -> { - it.registerPropertyValueConverterFactory( - PropertyValueConverterFactory.beanFactoryAware(defaultListableBeanFactory)); - })); - converter.afterPropertiesSet(); - - WithValueConverters wvc = new WithValueConverters(); - wvc.converterBean = "spring"; - - org.bson.Document target = new org.bson.Document(); - converter.write(wvc, target); - - assertThat(target.get("converterBean", org.bson.Document.class)).satisfies(it -> { - assertThat(it).containsKey("ooo"); - assertThat((String) it.get("ooo")).startsWith("spring - "); - }); - - WithValueConverters read = converter.read(WithValueConverters.class, target); - assertThat(read.converterBean).startsWith("spring -"); - } - - @Test // GH-3596 - void pathConfiguredConverter/*no annotation required*/() { - - converter = new MappingMongoConverter(resolver, mappingContext); - - converter.setCustomConversions(MongoCustomConversions.create(it -> { - - it.registerConverter(WithValueConverters.class, "viaRegisteredConverter", - new PropertyValueConverter() { - @Nullable - @Override - public String read(@Nullable org.bson.Document nativeValue, MongoConversionContext context) { - return nativeValue.getString("bar"); - } - - @Nullable - @Override - public org.bson.Document write(@Nullable String domainValue, MongoConversionContext context) { - return new org.bson.Document("bar", domainValue); - } - }); - })); - - WithValueConverters wvc = new WithValueConverters(); - wvc.viaRegisteredConverter = "spring"; - - org.bson.Document target = new org.bson.Document(); - converter.write(wvc, target); - - assertThat(target).containsEntry("viaRegisteredConverter", new org.bson.Document("bar", "spring")); - - WithValueConverters read = converter.read(WithValueConverters.class, target); - assertThat(read.viaRegisteredConverter).isEqualTo("spring"); - } - static class WithValueConverters { @ValueConverter(Converter1.class) String converterWithDefaultCtor; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java index 6c8af2d39e..493eabfdf0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java @@ -16,17 +16,18 @@ package org.springframework.data.mongodb.core.convert; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.time.ZonedDateTime; import java.util.Collections; import java.util.Date; import org.junit.jupiter.api.Test; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.Foo; -import org.springframework.lang.Nullable; /** * Unit tests for {@link MongoCustomConversions}. @@ -45,54 +46,29 @@ void nonAnnotatedConverterForJavaTimeTypeShouldOnlyBeRegisteredAsReadingConverte assertThat(conversions.hasCustomWriteTarget(Date.class)).isFalse(); } - static class DateToZonedDateTimeConverter implements Converter { - - @Override - public ZonedDateTime convert(Date source) { - return ZonedDateTime.now(); - } - } - - @Test - void howToConfigure() { - - - MongoCustomConversions.create(config -> { + @Test // GH-3596 + void propertyValueConverterRegistrationWorksAsExpected() { - config.registerConverter(Foo.class, "name", new MongoValueConverter() { + PersistentProperty persistentProperty = mock(PersistentProperty.class); + PersistentEntity owner = mock(PersistentEntity.class); + when(persistentProperty.getName()).thenReturn("name"); + when(persistentProperty.getOwner()).thenReturn(owner); + when(owner.getType()).thenReturn(Foo.class); - @Nullable - @Override - public Object read(@Nullable Object nativeValue, MongoConversionContext context) { - return null; - } + MongoCustomConversions conversions = MongoCustomConversions.create(config -> { - @Nullable - @Override - public Object write(@Nullable Object domainValue, MongoConversionContext context) { - return null; - } - }); - - - // lambda variant - generics + config.configurePropertyConversions( + registry -> registry.registerConverter(Foo.class, "name", mock(PropertyValueConverter.class))); + }); - // SD-Rest - LookupInformation/Lookup - Invocation Recorder -> BiFunction mit target type + assertThat(conversions.hasPropertyValueConverter(persistentProperty)).isTrue(); + } -// config.registerConverter(Foo.class, Foo::getName, (in, ctx) -> {}, (out, ctx) -> {}); .as() { + static class DateToZonedDateTimeConverter implements Converter { -// @Nullable -// @Override -// public Object nativeToDomain(@Nullable Object nativeValue, ValueConversionContext context) { -// return null; -// } -// -// @Nullable -// @Override -// public Object domainToNative(@Nullable Object domainValue, ValueConversionContext context) { -// return null; -// } -// }); - }); + @Override + public ZonedDateTime convert(Date source) { + return ZonedDateTime.now(); + } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index b2de941355..d3b73379b2 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -33,10 +33,10 @@ import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; +import org.springframework.data.convert.ValueConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -1428,6 +1428,13 @@ void mapStringIdFieldProjection() { assertThat(mappedQuery.get("_id")) .isEqualTo(org.bson.Document.parse("{ $in: [ {$oid: \"5b8bedceb1e0bfc07b008828\" } ]}")); } + + @Test // GH-3596 + void considersValueConverterWhenPresent() { + + org.bson.Document mappedObject = mapper.getMappedObject(new org.bson.Document("text", "value"), context.getPersistentEntity(WithPropertyValueConverter.class)); + assertThat(mappedObject).isEqualTo(new org.bson.Document("text", "eulav")); + } class WithDeepArrayNesting { @@ -1707,6 +1714,12 @@ static class Customer { static class MyAddress { private String street; } + + static class WithPropertyValueConverter { + + @ValueConverter(ReversingValueConverter.class) + String text; + } @WritingConverter public static class MyAddressToDocumentConverter implements Converter { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReversingValueConverter.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReversingValueConverter.java new file mode 100644 index 0000000000..9a90acf633 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReversingValueConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 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.springframework.lang.Nullable; + +/** + * @author Christoph Strobl + */ +class ReversingValueConverter implements MongoValueConverter { + + @Nullable + @Override + public String read(@Nullable String value, MongoConversionContext context) { + return reverse(value); + } + + @Nullable + @Override + public String write(@Nullable String value, MongoConversionContext context) { + return reverse(value); + } + + private String reverse(String source) { + + if (source == null) { + return null; + } + + return new StringBuilder(source).reverse().toString(); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index 3e744f675b..5cc3276d87 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -42,6 +42,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.ValueConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -1341,6 +1342,15 @@ void mapNumericKeyInPathPartiallyMatchingExistingProperties() { assertThat(mappedUpdate).isEqualTo("{ $set: { 'testInnerData.testMap.1.nonExistingProperty.2.someValue': '4' }}"); } + @Test // GH-3596 + void updateConsidersValueConverterWhenPresent() { + + Update update = new Update().set("text", "value"); + Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(WithPropertyValueConverter.class)); + + assertThat(mappedUpdate).isEqualTo("{ $set : { 'text' : 'eulav' } }"); + } + static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes { ListModelWrapper concreteTypeWithListAttributeOfInterfaceType; } @@ -1752,4 +1762,10 @@ private static class TestInnerData { private static class TestValue { private int intValue; } + + static class WithPropertyValueConverter { + + @ValueConverter(ReversingValueConverter.class) + String text; + } } From 200ef15da15ef4c856a216d5dcc1aea262f325d1 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 11 Mar 2022 14:17:01 +0100 Subject: [PATCH 7/7] Update Documentation --- src/main/asciidoc/new-features.adoc | 1 + src/main/asciidoc/reference/mapping.adoc | 1 + .../reference/mongo-property-converters.adoc | 108 ++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/main/asciidoc/reference/mongo-property-converters.adoc diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 91b4708b0f..e80f2ba290 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -5,6 +5,7 @@ == What's New in Spring Data MongoDB 3.4 * Find and update ``Document``s via <>. +* Property specific <>. [[new-features.3.3]] == What's New in Spring Data MongoDB 3.3 diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index ab074ba574..c94c6e64d3 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -900,3 +900,4 @@ Declaring these beans in your Spring ApplicationContext causes them to be invoke include::unwrapping-entities.adoc[] include::mongo-custom-conversions.adoc[] +include::mongo-property-converters.adoc[] diff --git a/src/main/asciidoc/reference/mongo-property-converters.adoc b/src/main/asciidoc/reference/mongo-property-converters.adoc new file mode 100644 index 0000000000..66b0e13742 --- /dev/null +++ b/src/main/asciidoc/reference/mongo-property-converters.adoc @@ -0,0 +1,108 @@ +[[mongo.property-converters]] +== Property Converters - Mapping specific fields + +Although to the <> already offers means to influence the representation of certain types within the target store it has its limitations when not all potential values of that type should be considered as a conversion targets. +Property based converters allow to specify conversion instructions on a per property basis either declarative, via `@ValueConverter`, or programmatic by registering a `PropertyValueConverter` for a specific field. + +A `PropertyValueConverter` is responsible of transforming a given value into its store representation (write) and back (read) as shown in the snippet below. +Please mind the presence of the `ValueConversionContext` providing additional information, such as mapping metadata. + +.PropertyValueConverter +==== +[source,java] +---- +class ReversingValueConverter implements PropertyValueConverter { + + @Override + public String read(String value, ValueConversionContext context) { + return reverse(value); + } + + @Override + public String write(String value, ValueConversionContext context) { + return reverse(value); + } +} +---- +==== + +`PropertyValueConverter` instances can be obtained via `CustomConversions#getPropertyValueConverter(...)` delegating to `PropertyValueConversions` typically using a `PropertyValueConverterFactory` to provide the actual converter. +Depending on the applications needs multiple instances of `PropertyValueConverterFactory` can be chained or decorated (eg. for caching). +By default a caching implementation is used that is capable of serving types with a default constructor or enum values. +A set of predefined factories is available via `PropertyValueConverterFactory`. +To obtain a `PropertyValueConverter` from an `ApplicationContext` make sure to use the `PropertyValueConverterFactory.beanFactoryAware(...)` factory. + +Changing the default behavior can be done via the `ConverterConfiguration`. + +=== Declarative Value Converter + +The most straight forward usage of a `PropertyValueConverter` is via the `@ValueConverter` annotation referring to the target converter type. + +.Declarative PropertyValueConverter +==== +[source,java] +---- +public class Person { + // ... + @ValueConverter(ReversingValueConverter.class) + String ssn; +} +---- +==== + +=== Programmatic Value Converter + +Following the programmatic approach does not require to put additional annotations on the domain model but registers `PropertyValueConverter` instances for certain paths in a `PropertyValueConverterRegistrar` as shown below. + +.Programmatic PropertyValueConverter +==== +[source,java] +---- +PropertyValueConverterRegistrar registrar = new PropertyValueConverterRegistrar(); + +registrar.registerConverter(Address.class, "street", new PropertyValueConverter() { ... }); <1> + +// type safe registration +registrar.registerConverter(Person.class, Person::getSsn()) <2> + .writing(value -> encrypt(value)) + .reading(value -> decrypt(value)); +---- +<1> Register a converter for the field identified by its name. +<2> Type safe variant that allows to register a converter and its conversion functions. +==== + +[WARNING] +==== +Dot notation (eg. `registerConverter(Person.class, "address.street", ...)`) is *not* supported when registering converters. +==== + +=== MongoDB property value conversions + +The above sections outlined the purpose an overall structure of `PropertyValueConverters`. +This section will focus on MongoDB specific aspects. + +==== MongoValueConverter & MongoConversionContext + +The `MongoValueConverter` offers a pre typed `PropertyValueConverter` interface leveraging the `MongoConversionContext`. + +==== MongoCustomConversions configuration + +`MongoCustomConversions` are by default capable of dealing with declarative value converters depending on the configured `PropertyValueConverterFactory`. +The `MongoConverterConfigurationAdapter` is there to help set up programmatic value conversions or define the `PropertyValueConverterFactory` to be used. + +.Configuration Sample +==== +[source,java] +---- +MongoCustomConversions.create(configurationAdapter -> { + + SimplePropertyValueConversions valueConversions = new SimplePropertyValueConversions(); + valueConversions.setConverterFactory(...); + valueConversions.setValueConverterRegistry(new PropertyValueConverterRegistrar() + .registerConverter(...) + .buildRegistry()); + + configurationAdapter.setPropertyValueConversions(valueConversions); +}); +---- +====