Browse Source

hacking - apply value conversion at query creation time for regex.

issue/4346
Christoph Strobl 3 years ago
parent
commit
367cd61e35
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 33
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  2. 40
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java
  3. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoWriter.java
  4. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java
  5. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  6. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/ReversingValueConverter.java
  7. 11
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java
  8. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java
  9. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  10. 13
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java
  11. 1
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  12. 44
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java
  13. 4
      spring-data-mongodb/src/test/resources/logback.xml

33
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; 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 // TODO: hide in 4.0
public List<Object> maybeConvertList(Iterable<?> source, @Nullable TypeInformation<?> typeInformation) { public List<Object> maybeConvertList(Iterable<?> source, @Nullable TypeInformation<?> typeInformation) {

40
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.PropertyValueConverterFactory;
import org.springframework.data.convert.PropertyValueConverterRegistrar; import org.springframework.data.convert.PropertyValueConverterRegistrar;
import org.springframework.data.convert.SimplePropertyValueConversions; import org.springframework.data.convert.SimplePropertyValueConversions;
import org.springframework.data.convert.ValueConversionContext;
import org.springframework.data.convert.WritingConverter; import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/** /**
* Value object to capture custom conversion. {@link MongoCustomConversions} also act as factory for * 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(); svc.init();
} }
// Move to data-commons?
PropertyValueConversions pvc = new PropertyValueConversions() {
@Override
public boolean hasValueConverter(PersistentProperty<?> property) {
return propertyValueConversions.hasValueConverter(property);
}
@Override
public <DV, SV, P extends PersistentProperty<P>, VCC extends ValueConversionContext<P>> PropertyValueConverter<DV, SV, VCC> getValueConverter(
P property) {
return new PropertyValueConverter<DV, SV, VCC>() {
@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) { if (!useNativeDriverJavaTimeCodecs) {
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true, pvc);
this.propertyValueConversions);
} }
/* /*
@ -358,7 +392,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
} }
return true; return true;
}, this.propertyValueConversions); }, pvc);
} }
private enum DateToUtcLocalDateTimeConverter implements Converter<Date, LocalDateTime> { private enum DateToUtcLocalDateTimeConverter implements Converter<Date, LocalDateTime> {

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

@ -59,6 +59,11 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
@Nullable @Nullable
Object convertToMongoType(@Nullable Object obj, @Nullable TypeInformation<?> typeInformation); 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) { default Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity<?> entity) {
return convertToMongoType(obj, entity.getTypeInformation()); return convertToMongoType(obj, entity.getTypeInformation());
} }

9
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.data.util.TypeInformation;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.mongodb.DBRef; import com.mongodb.DBRef;
import org.springframework.util.ObjectUtils;
/** /**
* Custom {@link ParameterAccessor} that uses a {@link MongoWriter} to serialize parameters into Mongo format. * 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) { public Object getBindableValue(int index) {
return getConvertedValue(delegate.getBindableValue(index), null); return getConvertedValue(delegate.getBindableValue(index), (TypeInformation<?>) null);
} }
@Override @Override
@ -129,6 +131,11 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
return writer.convertToMongoType(value, typeInformation == null ? null : typeInformation.getActualType()); return writer.convertToMongoType(value, typeInformation == null ? null : typeInformation.getActualType());
} }
public Object getConvertedValue(Object value, MongoPersistentProperty property) {
return writer.convertToMongoType(value, property);
}
public boolean hasBindableNullValue() { public boolean hasBindableNullValue() {
return delegate.hasBindableNullValue(); return delegate.hasBindableNullValue();
} }

13
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java

@ -68,7 +68,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
private static final Log LOG = LogFactory.getLog(MongoQueryCreator.class); private static final Log LOG = LogFactory.getLog(MongoQueryCreator.class);
private final MongoParameterAccessor accessor; private final ConvertingParameterAccessor accessor;
private final MappingContext<?, MongoPersistentProperty> context; private final MappingContext<?, MongoPersistentProperty> context;
private final boolean isGeoNearQuery; private final boolean isGeoNearQuery;
@ -345,6 +345,17 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
"Argument for creating $regex pattern for property '%s' must not be null", part.getProperty().getSegment())); "Argument for creating $regex pattern for property '%s' must not be null", part.getProperty().getSegment()));
} }
try {
PersistentPropertyPath<MongoPersistentProperty> 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)); return criteria.regex(toLikeRegex(value.toString(), part), toRegexOptions(part));
} }

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReversingValueConverter.java → 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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; import org.springframework.lang.Nullable;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
*/ */
class ReversingValueConverter implements MongoValueConverter<String, String> { public class ReversingValueConverter implements MongoValueConverter<String, String> {
@Nullable @Nullable
@Override @Override

11
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.Map;
import java.util.Optional; import java.util.Optional;
import org.bson.BsonRegularExpression;
import org.bson.conversions.Bson; import org.bson.conversions.Bson;
import org.bson.types.Code; import org.bson.types.Code;
import org.bson.types.ObjectId; 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;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.geo.Point; 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.DocumentTestUtils;
import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.aggregation.ComparisonOperators; import org.springframework.data.mongodb.core.aggregation.ComparisonOperators;
@ -1455,6 +1457,15 @@ public class QueryMapperUnitTests {
assertThat(mappedObject).isEqualTo(new org.bson.Document("text", "eulav")); 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 @Test // GH-2750
void mapsAggregationExpression() { void mapsAggregationExpression() {

3
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.Direction;
import org.springframework.data.domain.Sort.Order; import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.MappingException; 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.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.DocumentReference;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

15
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)) assertThat(repository.findById(dave.getId()).map(Person::getShippingAddresses))
.contains(Collections.singleton(address)); .contains(Collections.singleton(address));
} }
@Test // GH-4346
@DirtiesState
void findCreatingRegexWithValueConverterWorks() {
Person bart = new Person("bart", "simpson");
bart.setNickName("bartman");
operations.save(bart);
List<Person> result = repository.findByNickNameContains("artma");
assertThat(result).hasSize(1);
assertThat(result.get(0).getId().equals(bart.getId()));
}
} }

13
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.Set;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.convert.ValueConverter;
import org.springframework.data.geo.Point; 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.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
@ -78,6 +80,9 @@ public class Person extends Contact {
@DocumentReference User spiritAnimal; @DocumentReference User spiritAnimal;
@ValueConverter(ReversingValueConverter.class)
String nickName;
int visits; int visits;
public Person() { public Person() {
@ -325,6 +330,14 @@ public class Person extends Contact {
this.spiritAnimal = spiritAnimal; this.spiritAnimal = spiritAnimal;
} }
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Override @Override
public int hashCode() { public int hashCode() {

1
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -465,4 +465,5 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
List<Person> findBySpiritAnimal(User user); List<Person> findBySpiritAnimal(User user);
List<Person> findByNickNameContains(String nickName);
} }

44
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.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.convert.ValueConverter;
import org.springframework.data.domain.Range; import org.springframework.data.domain.Range;
import org.springframework.data.domain.Range.Bound; import org.springframework.data.domain.Range.Bound;
import org.springframework.data.geo.Distance; 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.Person;
import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.Venue;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter; 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.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoValueConverter;
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
import org.springframework.data.mongodb.core.geo.GeoJsonLineString; import org.springframework.data.mongodb.core.geo.GeoJsonLineString;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint; 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.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.lang.Nullable;
/** /**
* Unit test for {@link MongoQueryCreator}. * Unit test for {@link MongoQueryCreator}.
@ -658,6 +662,16 @@ class MongoQueryCreatorUnitTests {
assertThat(creator.createQuery()).isEqualTo(query(where("location").nearSphere(point).maxDistance(1000.0D))); 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<Person, Long> { interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname); List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);
@ -693,4 +707,34 @@ class MongoQueryCreatorUnitTests {
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo; @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo;
} }
static class WithValueConverter {
@ValueConverter(ReversingValueConverter.class)
String text;
}
static class ReversingValueConverter implements MongoValueConverter<String, String> {
@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();
}
}
} }

4
spring-data-mongodb/src/test/resources/logback.xml

@ -9,6 +9,10 @@
<appender name="no-op" class="ch.qos.logback.core.helpers.NOPAppender" /> <appender name="no-op" class="ch.qos.logback.core.helpers.NOPAppender" />
<!--
<logger name="org.mongodb.driver.protocol" level="DEBUG" />
-->
<!-- <!--
<logger name="org.springframework" level="debug" /> <logger name="org.springframework" level="debug" />
--> -->

Loading…
Cancel
Save