> configurationAdapter) {
+
+ Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
+ "Configured PropertyValueConversions does not allow setting custom ConverterRegistry.");
+
+ PropertyValueConverterRegistrar propertyValueConverterRegistrar = new PropertyValueConverterRegistrar();
+ configurationAdapter.accept(propertyValueConverterRegistrar);
+
+ ((SimplePropertyValueConversions) valueConversions())
+ .setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry());
+ return this;
+ }
+
/**
* Add a custom {@link ConverterFactory} implementation.
*
@@ -258,10 +287,54 @@ public MongoConverterConfigurationAdapter registerConverters(Collection> conve
return this;
}
+ /**
+ * 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) {
+
+ Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
+ "Configured PropertyValueConversions does not allow setting custom ConverterRegistry.");
+
+ ((SimplePropertyValueConversions) valueConversions()).setConverterFactory(converterFactory);
+ return this;
+ }
+
+ /**
+ * 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;
+ }
+
+ PropertyValueConversions valueConversions() {
+
+ if (this.propertyValueConversions == null) {
+ this.propertyValueConversions = new SimplePropertyValueConversions();
+ }
+
+ return this.propertyValueConversions;
+ }
+
ConverterConfiguration createConverterConfiguration() {
if (!useNativeDriverJavaTimeCodecs) {
- return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters);
+ return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true,
+ this.propertyValueConversions);
}
/*
@@ -286,7 +359,7 @@ ConverterConfiguration createConverterConfiguration() {
}
return true;
- });
+ }, 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
new file mode 100644
index 0000000000..49b9021cc0
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoValueConverter.java
@@ -0,0 +1,28 @@
+/*
+ * 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.data.convert.PropertyValueConverter;
+
+/**
+ * Pre typed {@link PropertyValueConverter} specific for the Data MongoDB module.
+ *
+ * @author Christoph Strobl
+ * @since 3.4
+ */
+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/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..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
@@ -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;
@@ -56,7 +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.PropertyValueConverter;
+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;
@@ -2437,7 +2442,6 @@ void shouldUseMostConcreteCustomConversionTargetOnRead() {
verify(subTypeOfGenericTypeConverter).convert(eq(source));
}
-
@Test // GH-3660
void usesCustomConverterForMapTypesOnWrite() {
@@ -2481,9 +2485,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 +2521,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 +2544,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 +2565,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 +2583,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