Skip to content

Commit 29fb085

Browse files
christophstroblmp911de
authored andcommitted
Add support for PropertyValueConverters.
Closes: #3596 Original pull request: #3982.
1 parent 15cac49 commit 29fb085

File tree

13 files changed

+571
-13
lines changed

13 files changed

+571
-13
lines changed

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ protected ConversionContext getConversionContext(ObjectPath path) {
176176

177177
Assert.notNull(path, "ObjectPath must not be null");
178178

179-
return new ConversionContext(conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap,
179+
return new ConversionContext(this, conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap,
180180
this::readDBRef, this::getPotentiallyConvertedSimpleRead);
181181
}
182182

@@ -294,7 +294,7 @@ public <R> R project(EntityProjection<R, ?> projection, Bson bson) {
294294
return (R) read(typeToRead, bson);
295295
}
296296

297-
ProjectingConversionContext context = new ProjectingConversionContext(conversions, ObjectPath.ROOT,
297+
ProjectingConversionContext context = new ProjectingConversionContext(this, conversions, ObjectPath.ROOT,
298298
this::readCollectionOrArray, this::readMap, this::readDBRef, this::getPotentiallyConvertedSimpleRead,
299299
projection);
300300

@@ -377,11 +377,11 @@ class ProjectingConversionContext extends ConversionContext {
377377

378378
private final EntityProjection<?, ?> returnedTypeDescriptor;
379379

380-
ProjectingConversionContext(CustomConversions customConversions, ObjectPath path,
380+
ProjectingConversionContext(MongoConverter sourceConverter, CustomConversions customConversions, ObjectPath path,
381381
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
382382
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter,
383383
EntityProjection<?, ?> projection) {
384-
super(customConversions, path,
384+
super(sourceConverter, customConversions, path,
385385
(context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection),
386386

387387
collectionConverter, mapConverter, dbRefConverter, elementConverter);
@@ -397,13 +397,13 @@ public ConversionContext forProperty(String name) {
397397
mapConverter, dbRefConverter, elementConverter);
398398
}
399399

400-
return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter,
400+
return new ProjectingConversionContext(sourceConverter, conversions, path, collectionConverter, mapConverter, dbRefConverter,
401401
elementConverter, property);
402402
}
403403

404404
@Override
405405
public ConversionContext withPath(ObjectPath currentPath) {
406-
return new ProjectingConversionContext(conversions, currentPath, collectionConverter, mapConverter,
406+
return new ProjectingConversionContext(sourceConverter, conversions, currentPath, collectionConverter, mapConverter,
407407
dbRefConverter, elementConverter, returnedTypeDescriptor);
408408
}
409409
}
@@ -935,6 +935,11 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce
935935
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
936936
TypeInformation<?> type = prop.getTypeInformation();
937937

938+
if(conversions.hasPropertyValueConverter(prop)) {
939+
accessor.put(prop, conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext(prop, this)));
940+
return;
941+
}
942+
938943
if (prop.isUnwrapped()) {
939944

940945
Document target = new Document();
@@ -1264,6 +1269,12 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, String key)
12641269

12651270
private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersistentProperty property) {
12661271
DocumentAccessor accessor = new DocumentAccessor(bson);
1272+
1273+
if(conversions.hasPropertyValueConverter(property)) {
1274+
accessor.put(property, conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext(property, this)));
1275+
return;
1276+
}
1277+
12671278
accessor.put(property, getPotentiallyConvertedSimpleWrite(value,
12681279
property.hasExplicitWriteTarget() ? property.getFieldType() : Object.class));
12691280
}
@@ -1905,6 +1916,11 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
19051916
return null;
19061917
}
19071918

1919+
if(context.conversions.hasPropertyValueConverter(property)) {
1920+
1921+
return (T) context.conversions.getPropertyValueConverter(property).read(value, new MongoConversionContext(property, context.sourceConverter));
1922+
}
1923+
19081924
return (T) context.convert(value, property.getTypeInformation());
19091925
}
19101926

@@ -2123,6 +2139,7 @@ public org.springframework.data.util.TypeInformation<? extends S> specialize(Cla
21232139
*/
21242140
protected static class ConversionContext {
21252141

2142+
final MongoConverter sourceConverter;
21262143
final org.springframework.data.convert.CustomConversions conversions;
21272144
final ObjectPath path;
21282145
final ContainerValueConverter<Bson> documentConverter;
@@ -2131,11 +2148,12 @@ protected static class ConversionContext {
21312148
final ContainerValueConverter<DBRef> dbRefConverter;
21322149
final ValueConverter<Object> elementConverter;
21332150

2134-
ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
2151+
ConversionContext(MongoConverter sourceConverter, org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
21352152
ContainerValueConverter<Bson> documentConverter, ContainerValueConverter<Collection<?>> collectionConverter,
21362153
ContainerValueConverter<Bson> mapConverter, ContainerValueConverter<DBRef> dbRefConverter,
21372154
ValueConverter<Object> elementConverter) {
21382155

2156+
this.sourceConverter = sourceConverter;
21392157
this.conversions = customConversions;
21402158
this.path = path;
21412159
this.documentConverter = documentConverter;
@@ -2217,7 +2235,7 @@ public ConversionContext withPath(ObjectPath currentPath) {
22172235

22182236
Assert.notNull(currentPath, "ObjectPath must not be null");
22192237

2220-
return new ConversionContext(conversions, currentPath, documentConverter, collectionConverter, mapConverter,
2238+
return new ConversionContext(sourceConverter, conversions, currentPath, documentConverter, collectionConverter, mapConverter,
22212239
dbRefConverter, elementConverter);
22222240
}
22232241

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

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@
3636
import org.springframework.core.convert.converter.Converter;
3737
import org.springframework.core.convert.converter.ConverterFactory;
3838
import org.springframework.core.convert.converter.GenericConverter;
39+
import org.springframework.data.convert.PropertyValueConversions;
40+
import org.springframework.data.convert.PropertyValueConverter;
41+
import org.springframework.data.convert.PropertyValueConverterFactory;
42+
import org.springframework.data.convert.PropertyValueConverterRegistrar;
43+
import org.springframework.data.convert.SimplePropertyValueConversions;
3944
import org.springframework.data.convert.WritingConverter;
4045
import org.springframework.data.mapping.model.SimpleTypeHolder;
46+
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
4147
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
4248
import org.springframework.lang.Nullable;
4349
import org.springframework.util.Assert;
@@ -149,6 +155,8 @@ public static class MongoConverterConfigurationAdapter {
149155
private boolean useNativeDriverJavaTimeCodecs = false;
150156
private final List<Object> customConverters = new ArrayList<>();
151157

158+
private PropertyValueConversions propertyValueConversions = new SimplePropertyValueConversions();
159+
152160
/**
153161
* Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for
154162
* JSR-310 types.
@@ -220,6 +228,27 @@ public MongoConverterConfigurationAdapter registerConverter(Converter<?, ?> conv
220228
return this;
221229
}
222230

231+
/**
232+
* Gateway to register property specific converters.
233+
*
234+
* @param configurationAdapter must not be {@literal null}.
235+
* @return this.
236+
* @since 3.4
237+
*/
238+
public MongoConverterConfigurationAdapter configurePropertyConversions(
239+
Consumer<PropertyValueConverterRegistrar<MongoPersistentProperty>> configurationAdapter) {
240+
241+
Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
242+
"Configured PropertyValueConversions does not allow setting custom ConverterRegistry.");
243+
244+
PropertyValueConverterRegistrar propertyValueConverterRegistrar = new PropertyValueConverterRegistrar();
245+
configurationAdapter.accept(propertyValueConverterRegistrar);
246+
247+
((SimplePropertyValueConversions) valueConversions())
248+
.setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry());
249+
return this;
250+
}
251+
223252
/**
224253
* Add a custom {@link ConverterFactory} implementation.
225254
*
@@ -248,10 +277,54 @@ public MongoConverterConfigurationAdapter registerConverters(Collection<?> conve
248277
return this;
249278
}
250279

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

253325
if (!useNativeDriverJavaTimeCodecs) {
254-
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters);
326+
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true,
327+
this.propertyValueConversions);
255328
}
256329

257330
/*
@@ -276,7 +349,7 @@ ConverterConfiguration createConverterConfiguration() {
276349
}
277350

278351
return true;
279-
});
352+
}, this.propertyValueConversions);
280353
}
281354

282355
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,10 @@ protected Object getMappedValue(Field documentField, Object sourceValue) {
432432

433433
Object value = applyFieldTargetTypeHintToValue(documentField, sourceValue);
434434

435+
if(documentField.getProperty() != null && converter.getCustomConversions().hasPropertyValueConverter(documentField.getProperty())) {
436+
return converter.getCustomConversions().getPropertyValueConverter(documentField.getProperty()).write(value, new MongoConversionContext(documentField.getProperty(), converter));
437+
}
438+
435439
if (documentField.isIdField() && !documentField.isAssociation()) {
436440

437441
if (isDBObject(value)) {

0 commit comments

Comments
 (0)