From 367cd61e35598fb14f336a24df51bbd790f6f61f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 16 May 2023 17:42:24 +0200 Subject: [PATCH] hacking - apply value conversion at query creation time for regex. --- .../core/convert/MappingMongoConverter.java | 33 ++++++++++++++ .../core/convert/MongoCustomConversions.java | 40 +++++++++++++++-- .../mongodb/core/convert/MongoWriter.java | 5 +++ .../query/ConvertingParameterAccessor.java | 9 +++- .../repository/query/MongoQueryCreator.java | 13 +++++- .../convert => }/ReversingValueConverter.java | 6 ++- .../core/convert/QueryMapperUnitTests.java | 11 +++++ .../core/convert/UpdateMapperUnitTests.java | 3 +- ...tractPersonRepositoryIntegrationTests.java | 15 +++++++ .../data/mongodb/repository/Person.java | 13 ++++++ .../mongodb/repository/PersonRepository.java | 1 + .../query/MongoQueryCreatorUnitTests.java | 44 +++++++++++++++++++ .../src/test/resources/logback.xml | 4 ++ 13 files changed, 188 insertions(+), 9 deletions(-) rename spring-data-mongodb/src/test/java/org/springframework/data/mongodb/{core/convert => }/ReversingValueConverter.java (79%) 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 2bfb90150..6ad5dc1a7 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 @@ -1571,6 +1571,39 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return newDocument; } + @Nullable + @Override + public Object convertToMongoType(@Nullable Object obj, MongoPersistentProperty property) { + + PersistentPropertyAccessor accessor = new MapPersistentPropertyAccessor(); + accessor.setProperty(property, obj); + + Document newDocument = new Document(); + DocumentAccessor dbObjectAccessor = new DocumentAccessor(newDocument); + + if (property.isIdProperty() || !property.isWritable()) { + return obj; + } + if (property.isAssociation()) { + + writeAssociation(property.getRequiredAssociation(), accessor, dbObjectAccessor); + return dbObjectAccessor.get(property); + } + + Object value = obj; + + if (value == null) { + if (property.writeNullValues()) { + dbObjectAccessor.put(property, null); + } + } else if (!conversions.isSimpleType(value.getClass())) { + writePropertyInternal(value, dbObjectAccessor, property, accessor); + } else { + writeSimpleInternal(value, newDocument, property, accessor); + } + return dbObjectAccessor.get(property); + } + // TODO: hide in 4.0 public List maybeConvertList(Iterable source, @Nullable TypeInformation typeInformation) { 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 a0c1cbccb..6a9bec65b 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 @@ -42,12 +42,15 @@ 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.ValueConversionContext; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.mapping.PersistentProperty; 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; +import org.springframework.util.ClassUtils; /** * Value object to capture custom conversion. {@link MongoCustomConversions} also act as factory for @@ -331,9 +334,40 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus svc.init(); } + // Move to data-commons? + PropertyValueConversions pvc = new PropertyValueConversions() { + + @Override + public boolean hasValueConverter(PersistentProperty property) { + return propertyValueConversions.hasValueConverter(property); + } + + @Override + public , VCC extends ValueConversionContext

> PropertyValueConverter getValueConverter( + P property) { + + return new PropertyValueConverter() { + + @Nullable + @Override + public DV read(SV value, VCC context) { + return (DV) propertyValueConversions.getValueConverter(property).read(value, context); + } + + @Nullable + @Override + public SV write(DV value, VCC context) { + if (ClassUtils.isAssignable(property.getType(), value.getClass())) { + return (SV) propertyValueConversions.getValueConverter(property).write(value, context); + } + return (SV) value; + } + }; + } + }; + if (!useNativeDriverJavaTimeCodecs) { - return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, - this.propertyValueConversions); + return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, pvc); } /* @@ -358,7 +392,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus } return true; - }, this.propertyValueConversions); + }, pvc); } private enum DateToUtcLocalDateTimeConverter implements Converter { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoWriter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoWriter.java index 058bb1c8b..36c9620ff 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoWriter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoWriter.java @@ -59,6 +59,11 @@ public interface MongoWriter extends EntityWriter { @Nullable Object convertToMongoType(@Nullable Object obj, @Nullable TypeInformation typeInformation); + @Nullable + default Object convertToMongoType(@Nullable Object obj, MongoPersistentProperty property) { + return convertToMongoType(obj, property.getTypeInformation()); + } + default Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity entity) { return convertToMongoType(obj, entity.getTypeInformation()); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index 6e7453857..a8c00617f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -36,9 +36,11 @@ import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import com.mongodb.DBRef; +import org.springframework.util.ObjectUtils; /** * Custom {@link ParameterAccessor} that uses a {@link MongoWriter} to serialize parameters into Mongo format. @@ -91,7 +93,7 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { } public Object getBindableValue(int index) { - return getConvertedValue(delegate.getBindableValue(index), null); + return getConvertedValue(delegate.getBindableValue(index), (TypeInformation) null); } @Override @@ -129,6 +131,11 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { return writer.convertToMongoType(value, typeInformation == null ? null : typeInformation.getActualType()); } + + public Object getConvertedValue(Object value, MongoPersistentProperty property) { + return writer.convertToMongoType(value, property); + } + public boolean hasBindableNullValue() { return delegate.hasBindableNullValue(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index f9cc596e6..038834520 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -68,7 +68,7 @@ class MongoQueryCreator extends AbstractQueryCreator { private static final Log LOG = LogFactory.getLog(MongoQueryCreator.class); - private final MongoParameterAccessor accessor; + private final ConvertingParameterAccessor accessor; private final MappingContext context; private final boolean isGeoNearQuery; @@ -345,6 +345,17 @@ class MongoQueryCreator extends AbstractQueryCreator { "Argument for creating $regex pattern for property '%s' must not be null", part.getProperty().getSegment())); } + try { + PersistentPropertyPath persistentPropertyPath = context.getPersistentPropertyPath(part.getProperty()); + MongoPersistentProperty leafProperty = persistentPropertyPath.getLeafProperty();/// maybe a call back here + if (leafProperty != null) { + Object convertedValue = accessor.getConvertedValue(value.toString(), leafProperty); + return criteria.regex(toLikeRegex(convertedValue.toString(), part), toRegexOptions(part)); + } + } catch (Exception ex) { + System.err.print(ex); + } + return criteria.regex(toLikeRegex(value.toString(), part), toRegexOptions(part)); } 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/ReversingValueConverter.java similarity index 79% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReversingValueConverter.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/ReversingValueConverter.java index e594ea9b2..4d2303c81 100644 --- 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/ReversingValueConverter.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.convert; +package org.springframework.data.mongodb; +import org.springframework.data.mongodb.core.convert.MongoConversionContext; +import org.springframework.data.mongodb.core.convert.MongoValueConverter; import org.springframework.lang.Nullable; /** * @author Christoph Strobl */ -class ReversingValueConverter implements MongoValueConverter { +public class ReversingValueConverter implements MongoValueConverter { @Nullable @Override 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 fc0345b3d..e1531dd38 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 @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.bson.BsonRegularExpression; import org.bson.conversions.Bson; import org.bson.types.Code; import org.bson.types.ObjectId; @@ -42,6 +43,7 @@ import org.springframework.data.convert.WritingConverter; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.ReversingValueConverter; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.aggregation.ComparisonOperators; @@ -1455,6 +1457,15 @@ public class QueryMapperUnitTests { assertThat(mappedObject).isEqualTo(new org.bson.Document("text", "eulav")); } + @Test // GH-4346 + void ignoresValueConverterForNonMatchingType() { + + org.bson.Document source = new org.bson.Document("text", new BsonRegularExpression("value")); + org.bson.Document mappedObject = mapper.getMappedObject(source, context.getPersistentEntity(WithPropertyValueConverter.class)); + + assertThat(mappedObject).isEqualTo(source); + } + @Test // GH-2750 void mapsAggregationExpression() { 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 0252bd483..02c6517e1 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 @@ -47,9 +47,8 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.MappingException; +import org.springframework.data.mongodb.ReversingValueConverter; import org.springframework.data.mongodb.core.DocumentTestUtils; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.UpdateMapper; import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 646a268e0..5587e3e6d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1637,4 +1637,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie assertThat(repository.findById(dave.getId()).map(Person::getShippingAddresses)) .contains(Collections.singleton(address)); } + + @Test // GH-4346 + @DirtiesState + void findCreatingRegexWithValueConverterWorks() { + + Person bart = new Person("bart", "simpson"); + bart.setNickName("bartman"); + + operations.save(bart); + + List result = repository.findByNickNameContains("artma"); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getId().equals(bart.getId())); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java index 2e61898ea..85f4cc911 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java @@ -21,7 +21,9 @@ import java.util.List; import java.util.Set; import java.util.UUID; +import org.springframework.data.convert.ValueConverter; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.ReversingValueConverter; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; import org.springframework.data.mongodb.core.index.Indexed; @@ -78,6 +80,9 @@ public class Person extends Contact { @DocumentReference User spiritAnimal; + @ValueConverter(ReversingValueConverter.class) + String nickName; + int visits; public Person() { @@ -325,6 +330,14 @@ public class Person extends Contact { this.spiritAnimal = spiritAnimal; } + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + @Override public int hashCode() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 1e6a37b0a..29471bbc2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -465,4 +465,5 @@ public interface PersonRepository extends MongoRepository, Query List findBySpiritAnimal(User user); + List findByNickNameContains(String nickName); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index f81ff21fe..d64d09fe6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -29,6 +29,7 @@ import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.data.convert.ValueConverter; import org.springframework.data.domain.Range; import org.springframework.data.domain.Range.Bound; import org.springframework.data.geo.Distance; @@ -40,7 +41,9 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConversionContext; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.MongoValueConverter; import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.geo.GeoJsonLineString; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; @@ -57,6 +60,7 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.lang.Nullable; /** * Unit test for {@link MongoQueryCreator}. @@ -658,6 +662,16 @@ class MongoQueryCreatorUnitTests { assertThat(creator.createQuery()).isEqualTo(query(where("location").nearSphere(point).maxDistance(1000.0D))); } + @Test // GH-4346 + void likeQueriesShouldApplyPropertyValueConverterWhenCreatingRegex() { + + PartTree tree = new PartTree("findByTextStartingWith", WithValueConverter.class); + MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "spring"), context); + Query query = creator.createQuery(); + + assertThat(query).isEqualTo(query(where("text").regex("^gnirps"))); + } + interface PersonRepository extends Repository { List findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname); @@ -693,4 +707,34 @@ class MongoQueryCreatorUnitTests { @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo; } + + static class WithValueConverter { + + @ValueConverter(ReversingValueConverter.class) + String text; + } + + static 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/resources/logback.xml b/spring-data-mongodb/src/test/resources/logback.xml index 64550c957..164a3b3d0 100644 --- a/spring-data-mongodb/src/test/resources/logback.xml +++ b/spring-data-mongodb/src/test/resources/logback.xml @@ -9,6 +9,10 @@ + +