Skip to content

Introduce support for property-based converters #3982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3596-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand All @@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
<springdata.commons>2.7.0-GH-1484-SNAPSHOT</springdata.commons>
<mongo>4.5.0</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3596-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3596-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3596-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -177,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);
}

Expand Down Expand Up @@ -307,7 +306,7 @@ public <R> R project(EntityProjection<R, ?> 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);

Expand Down Expand Up @@ -391,11 +390,11 @@ class ProjectingConversionContext extends ConversionContext {

private final EntityProjection<?, ?> returnedTypeDescriptor;

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

collectionConverter, mapConverter, dbRefConverter, elementConverter);
Expand All @@ -410,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);
}
}
Expand Down Expand Up @@ -958,6 +957,11 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
TypeInformation<?> type = prop.getTypeInformation();

if(conversions.hasPropertyValueConverter(prop)) {
accessor.put(prop, conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext(prop, this)));
return;
}

if (prop.isUnwrapped()) {

Document target = new Document();
Expand Down Expand Up @@ -1287,6 +1291,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(conversions.hasPropertyValueConverter(property)) {
accessor.put(property, conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext(property, this)));
return;
}

accessor.put(property, getPotentiallyConvertedSimpleWrite(value,
property.hasExplicitWriteTarget() ? property.getFieldType() : Object.class));
}
Expand Down Expand Up @@ -1950,6 +1960,11 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
return null;
}

if(context.conversions.hasPropertyValueConverter(property)) {

return (T) context.conversions.getPropertyValueConverter(property).read(value, new MongoConversionContext(property, context.sourceConverter));
}

return (T) context.convert(value, property.getTypeInformation());
}

Expand Down Expand Up @@ -2179,6 +2194,7 @@ public org.springframework.data.util.TypeInformation<? extends S> specialize(Cla
*/
protected static class ConversionContext {

final MongoConverter sourceConverter;
final org.springframework.data.convert.CustomConversions conversions;
final ObjectPath path;
final ContainerValueConverter<Bson> documentConverter;
Expand All @@ -2187,11 +2203,12 @@ protected static class ConversionContext {
final ContainerValueConverter<DBRef> dbRefConverter;
final ValueConverter<Object> elementConverter;

ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
ConversionContext(MongoConverter sourceConverter, org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
ContainerValueConverter<Bson> documentConverter, ContainerValueConverter<Collection<?>> collectionConverter,
ContainerValueConverter<Bson> mapConverter, ContainerValueConverter<DBRef> dbRefConverter,
ValueConverter<Object> elementConverter) {

this.sourceConverter = sourceConverter;
this.conversions = customConversions;
this.path = path;
this.documentConverter = documentConverter;
Expand Down Expand Up @@ -2273,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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.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 3.4
*/
public class MongoConversionContext implements ValueConversionContext<MongoPersistentProperty> {

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 persistentProperty;
}

@Override
public <T> T write(@Nullable Object value, TypeInformation<T> target) {
return (T) mongoConverter.convertToMongoType(value, target);
}

@Override
public <T> T read(@Nullable Object value, TypeInformation<T> target) {

if (!(value instanceof Bson)) {
return ValueConversionContext.super.read(value, target);
}

return mongoConverter.read(target.getType(), (Bson) value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@
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.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;
Expand Down Expand Up @@ -159,6 +165,8 @@ public static class MongoConverterConfigurationAdapter {
private boolean useNativeDriverJavaTimeCodecs = false;
private final List<Object> customConverters = new ArrayList<>();

private PropertyValueConversions propertyValueConversions = new SimplePropertyValueConversions();

/**
* Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for
* JSR-310 types.
Expand Down Expand Up @@ -230,6 +238,27 @@ public MongoConverterConfigurationAdapter registerConverter(Converter<?, ?> conv
return this;
}

/**
* Gateway to register property specific converters.
*
* @param configurationAdapter must not be {@literal null}.
* @return this.
* @since 3.4
*/
public MongoConverterConfigurationAdapter configurePropertyConversions(
Consumer<PropertyValueConverterRegistrar<MongoPersistentProperty>> 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.
*
Expand Down Expand Up @@ -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.
* <p>
* 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);
}

/*
Expand All @@ -286,7 +359,7 @@ ConverterConfiguration createConverterConfiguration() {
}

return true;
});
}, this.propertyValueConversions);
}

private enum DateToUtcLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<S, T> extends PropertyValueConverter<S, T, MongoConversionContext> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Loading