Skip to content

Commit f58e462

Browse files
christophstroblmp911de
authored andcommitted
Add support for PropertyValueConverters.
Closes: #3596 Original pull request: #3982.
1 parent d133ef1 commit f58e462

13 files changed

+571
-14
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

+26-9
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import org.bson.conversions.Bson;
3939
import org.bson.json.JsonReader;
4040
import org.bson.types.ObjectId;
41-
4241
import org.springframework.beans.BeansException;
4342
import org.springframework.beans.factory.BeanClassLoaderAware;
4443
import org.springframework.context.ApplicationContext;
@@ -186,7 +185,7 @@ protected ConversionContext getConversionContext(ObjectPath path) {
186185

187186
Assert.notNull(path, "ObjectPath must not be null");
188187

189-
return new ConversionContext(conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap,
188+
return new ConversionContext(this, conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap,
190189
this::readDBRef, this::getPotentiallyConvertedSimpleRead);
191190
}
192191

@@ -316,7 +315,7 @@ public <R> R project(EntityProjection<R, ?> projection, Bson bson) {
316315
return (R) read(typeToRead, bson);
317316
}
318317

319-
ProjectingConversionContext context = new ProjectingConversionContext(conversions, ObjectPath.ROOT,
318+
ProjectingConversionContext context = new ProjectingConversionContext(this, conversions, ObjectPath.ROOT,
320319
this::readCollectionOrArray, this::readMap, this::readDBRef, this::getPotentiallyConvertedSimpleRead,
321320
projection);
322321

@@ -399,11 +398,11 @@ class ProjectingConversionContext extends ConversionContext {
399398

400399
private final EntityProjection<?, ?> returnedTypeDescriptor;
401400

402-
ProjectingConversionContext(CustomConversions customConversions, ObjectPath path,
401+
ProjectingConversionContext(MongoConverter sourceConverter, CustomConversions customConversions, ObjectPath path,
403402
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
404403
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter,
405404
EntityProjection<?, ?> projection) {
406-
super(customConversions, path,
405+
super(sourceConverter, customConversions, path,
407406
(context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection),
408407

409408
collectionConverter, mapConverter, dbRefConverter, elementConverter);
@@ -419,13 +418,13 @@ public ConversionContext forProperty(String name) {
419418
mapConverter, dbRefConverter, elementConverter);
420419
}
421420

422-
return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter,
421+
return new ProjectingConversionContext(sourceConverter, conversions, path, collectionConverter, mapConverter, dbRefConverter,
423422
elementConverter, property);
424423
}
425424

426425
@Override
427426
public ConversionContext withPath(ObjectPath currentPath) {
428-
return new ProjectingConversionContext(conversions, currentPath, collectionConverter, mapConverter,
427+
return new ProjectingConversionContext(sourceConverter, conversions, currentPath, collectionConverter, mapConverter,
429428
dbRefConverter, elementConverter, returnedTypeDescriptor);
430429
}
431430
}
@@ -965,6 +964,11 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce
965964
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
966965
TypeInformation<?> type = prop.getTypeInformation();
967966

967+
if(conversions.hasPropertyValueConverter(prop)) {
968+
accessor.put(prop, conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext(prop, this)));
969+
return;
970+
}
971+
968972
if (prop.isUnwrapped()) {
969973

970974
Document target = new Document();
@@ -1294,6 +1298,12 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, String key)
12941298

12951299
private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersistentProperty property) {
12961300
DocumentAccessor accessor = new DocumentAccessor(bson);
1301+
1302+
if(conversions.hasPropertyValueConverter(property)) {
1303+
accessor.put(property, conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext(property, this)));
1304+
return;
1305+
}
1306+
12971307
accessor.put(property, getPotentiallyConvertedSimpleWrite(value,
12981308
property.hasExplicitWriteTarget() ? property.getFieldType() : Object.class));
12991309
}
@@ -1957,6 +1967,11 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
19571967
return null;
19581968
}
19591969

1970+
if(context.conversions.hasPropertyValueConverter(property)) {
1971+
1972+
return (T) context.conversions.getPropertyValueConverter(property).read(value, new MongoConversionContext(property, context.sourceConverter));
1973+
}
1974+
19601975
return (T) context.convert(value, property.getTypeInformation());
19611976
}
19621977

@@ -2182,6 +2197,7 @@ public org.springframework.data.util.TypeInformation<? extends S> specialize(Cla
21822197
*/
21832198
protected static class ConversionContext {
21842199

2200+
final MongoConverter sourceConverter;
21852201
final org.springframework.data.convert.CustomConversions conversions;
21862202
final ObjectPath path;
21872203
final ContainerValueConverter<Bson> documentConverter;
@@ -2190,11 +2206,12 @@ protected static class ConversionContext {
21902206
final ContainerValueConverter<DBRef> dbRefConverter;
21912207
final ValueConverter<Object> elementConverter;
21922208

2193-
ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
2209+
ConversionContext(MongoConverter sourceConverter, org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
21942210
ContainerValueConverter<Bson> documentConverter, ContainerValueConverter<Collection<?>> collectionConverter,
21952211
ContainerValueConverter<Bson> mapConverter, ContainerValueConverter<DBRef> dbRefConverter,
21962212
ValueConverter<Object> elementConverter) {
21972213

2214+
this.sourceConverter = sourceConverter;
21982215
this.conversions = customConversions;
21992216
this.path = path;
22002217
this.documentConverter = documentConverter;
@@ -2276,7 +2293,7 @@ public ConversionContext withPath(ObjectPath currentPath) {
22762293

22772294
Assert.notNull(currentPath, "ObjectPath must not be null");
22782295

2279-
return new ConversionContext(conversions, currentPath, documentConverter, collectionConverter, mapConverter,
2296+
return new ConversionContext(sourceConverter, conversions, currentPath, documentConverter, collectionConverter, mapConverter,
22802297
dbRefConverter, elementConverter);
22812298
}
22822299

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.convert;
17+
18+
import org.bson.conversions.Bson;
19+
import org.springframework.data.convert.ValueConversionContext;
20+
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
21+
import org.springframework.data.util.TypeInformation;
22+
import org.springframework.lang.Nullable;
23+
24+
/**
25+
* {@link ValueConversionContext} that allows to delegate read/write to an underlying {@link MongoConverter}.
26+
*
27+
* @author Christoph Strobl
28+
* @since 3.4
29+
*/
30+
public class MongoConversionContext implements ValueConversionContext<MongoPersistentProperty> {
31+
32+
private final MongoPersistentProperty persistentProperty;
33+
private final MongoConverter mongoConverter;
34+
35+
public MongoConversionContext(MongoPersistentProperty persistentProperty, MongoConverter mongoConverter) {
36+
37+
this.persistentProperty = persistentProperty;
38+
this.mongoConverter = mongoConverter;
39+
}
40+
41+
@Override
42+
public MongoPersistentProperty getProperty() {
43+
return persistentProperty;
44+
}
45+
46+
@Override
47+
public <T> T write(@Nullable Object value, TypeInformation<T> target) {
48+
return (T) mongoConverter.convertToMongoType(value, target);
49+
}
50+
51+
@Override
52+
public <T> T read(@Nullable Object value, TypeInformation<T> target) {
53+
54+
if (!(value instanceof Bson)) {
55+
return ValueConversionContext.super.read(value, target);
56+
}
57+
58+
return mongoConverter.read(target.getType(), (Bson) value);
59+
}
60+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java

+75-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@
3737
import org.springframework.core.convert.converter.ConverterFactory;
3838
import org.springframework.core.convert.converter.GenericConverter;
3939
import org.springframework.data.convert.JodaTimeConverters;
40+
import org.springframework.data.convert.PropertyValueConversions;
41+
import org.springframework.data.convert.PropertyValueConverter;
42+
import org.springframework.data.convert.PropertyValueConverterFactory;
43+
import org.springframework.data.convert.PropertyValueConverterRegistrar;
44+
import org.springframework.data.convert.SimplePropertyValueConversions;
4045
import org.springframework.data.convert.WritingConverter;
4146
import org.springframework.data.mapping.model.SimpleTypeHolder;
47+
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
4248
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
4349
import org.springframework.lang.Nullable;
4450
import org.springframework.util.Assert;
@@ -159,6 +165,8 @@ public static class MongoConverterConfigurationAdapter {
159165
private boolean useNativeDriverJavaTimeCodecs = false;
160166
private final List<Object> customConverters = new ArrayList<>();
161167

168+
private PropertyValueConversions propertyValueConversions = new SimplePropertyValueConversions();
169+
162170
/**
163171
* Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for
164172
* JSR-310 types.
@@ -230,6 +238,27 @@ public MongoConverterConfigurationAdapter registerConverter(Converter<?, ?> conv
230238
return this;
231239
}
232240

241+
/**
242+
* Gateway to register property specific converters.
243+
*
244+
* @param configurationAdapter must not be {@literal null}.
245+
* @return this.
246+
* @since 3.4
247+
*/
248+
public MongoConverterConfigurationAdapter configurePropertyConversions(
249+
Consumer<PropertyValueConverterRegistrar<MongoPersistentProperty>> configurationAdapter) {
250+
251+
Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
252+
"Configured PropertyValueConversions does not allow setting custom ConverterRegistry.");
253+
254+
PropertyValueConverterRegistrar propertyValueConverterRegistrar = new PropertyValueConverterRegistrar();
255+
configurationAdapter.accept(propertyValueConverterRegistrar);
256+
257+
((SimplePropertyValueConversions) valueConversions())
258+
.setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry());
259+
return this;
260+
}
261+
233262
/**
234263
* Add a custom {@link ConverterFactory} implementation.
235264
*
@@ -258,10 +287,54 @@ public MongoConverterConfigurationAdapter registerConverters(Collection<?> conve
258287
return this;
259288
}
260289

290+
/**
291+
* Add a custom/default {@link PropertyValueConverterFactory} implementation used to serve
292+
* {@link PropertyValueConverter}.
293+
*
294+
* @param converterFactory must not be {@literal null}.
295+
* @return this.
296+
* @since 3.4
297+
*/
298+
public MongoConverterConfigurationAdapter registerPropertyValueConverterFactory(
299+
PropertyValueConverterFactory converterFactory) {
300+
301+
Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
302+
"Configured PropertyValueConversions does not allow setting custom ConverterRegistry.");
303+
304+
((SimplePropertyValueConversions) valueConversions()).setConverterFactory(converterFactory);
305+
return this;
306+
}
307+
308+
/**
309+
* Optionally set the {@link PropertyValueConversions} to be applied during mapping.
310+
* <p>
311+
* Use this method if {@link #configurePropertyConversions(Consumer)} and
312+
* {@link #registerPropertyValueConverterFactory(PropertyValueConverterFactory)} are not sufficient.
313+
*
314+
* @param valueConversions must not be {@literal null}.
315+
* @return this.
316+
* @since 3.4
317+
*/
318+
public MongoConverterConfigurationAdapter setPropertyValueConversions(PropertyValueConversions valueConversions) {
319+
320+
this.propertyValueConversions = valueConversions;
321+
return this;
322+
}
323+
324+
PropertyValueConversions valueConversions() {
325+
326+
if (this.propertyValueConversions == null) {
327+
this.propertyValueConversions = new SimplePropertyValueConversions();
328+
}
329+
330+
return this.propertyValueConversions;
331+
}
332+
261333
ConverterConfiguration createConverterConfiguration() {
262334

263335
if (!useNativeDriverJavaTimeCodecs) {
264-
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters);
336+
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true,
337+
this.propertyValueConversions);
265338
}
266339

267340
/*
@@ -286,7 +359,7 @@ ConverterConfiguration createConverterConfiguration() {
286359
}
287360

288361
return true;
289-
});
362+
}, this.propertyValueConversions);
290363
}
291364

292365
private enum DateToUtcLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.convert;
17+
18+
import org.springframework.data.convert.PropertyValueConverter;
19+
20+
/**
21+
* Pre typed {@link PropertyValueConverter} specific for the Data MongoDB module.
22+
*
23+
* @author Christoph Strobl
24+
* @since 3.4
25+
*/
26+
public interface MongoValueConverter<S, T> extends PropertyValueConverter<S, T, MongoConversionContext> {
27+
28+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ protected Object getMappedValue(Field documentField, Object sourceValue) {
434434

435435
Object value = applyFieldTargetTypeHintToValue(documentField, sourceValue);
436436

437+
if(documentField.getProperty() != null && converter.getCustomConversions().hasPropertyValueConverter(documentField.getProperty())) {
438+
return converter.getCustomConversions().getPropertyValueConverter(documentField.getProperty()).write(value, new MongoConversionContext(documentField.getProperty(), converter));
439+
}
440+
437441
if (documentField.isIdField() && !documentField.isAssociation()) {
438442

439443
if (isDBObject(value)) {

0 commit comments

Comments
 (0)