From 8a1750691bbbaddae04b33295f96fd67f4a59bfc Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 7 Jan 2020 16:13:23 +0100 Subject: [PATCH] DATAMONGO-2440 - Fix field target type conversion for collection values. We now preserve the collection nature of the source type when applying custom target type conversions. Prior to this change collection values had been changed to single element causing query errors in MongoDB when using $in queries. Original pull request: #817. --- .../mongodb/core/convert/QueryMapper.java | 51 ++++++++++++------- .../core/convert/QueryMapperUnitTests.java | 32 ++++++++++++ 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index c542c5c42..6013ea806 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -15,19 +15,11 @@ */ package org.springframework.data.mongodb.core.convert; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.bson.BsonValue; import org.bson.Document; @@ -328,18 +320,14 @@ public class QueryMapper { * {@link MongoPersistentProperty}. * * @param documentField the key the value will be bound to eventually - * @param value the source object to be mapped + * @param sourceValue the source object to be mapped * @return */ @Nullable @SuppressWarnings("unchecked") - protected Object getMappedValue(Field documentField, Object value) { + protected Object getMappedValue(Field documentField, Object sourceValue) { - if (documentField.getProperty() != null && documentField.getProperty().hasExplicitWriteTarget()) { - if (conversionService.canConvert(value.getClass(), documentField.getProperty().getFieldType())) { - value = conversionService.convert(value, documentField.getProperty().getFieldType()); - } - } + Object value = applyFieldTargetTypeHintToValue(documentField, sourceValue); if (documentField.isIdField() && !documentField.isAssociation()) { @@ -680,6 +668,35 @@ public class QueryMapper { return candidate.startsWith("$"); } + /** + * Convert the given field value into its desired + * {@link org.springframework.data.mongodb.core.mapping.Field#targetType() target type} before applying further + * conversions. In case of a {@link Collection} (used eg. for {@code $in} queries) the individual values will be + * converted one by one. + * + * @param documentField the field and its meta data + * @param value the actual value + * @return the potentially converted target value. + */ + private Object applyFieldTargetTypeHintToValue(Field documentField, Object value) { + + if (documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) { + return value; + } + + if (!conversionService.canConvert(value.getClass(), documentField.getProperty().getFieldType())) { + return value; + } + + if (value instanceof Collection) { + + return ((Collection) value).stream() + .map(it -> conversionService.convert(it, documentField.getProperty().getFieldType())) + .collect(Collectors.toList()); + } + return conversionService.convert(value, documentField.getProperty().getFieldType()); + } + /** * Value object to capture a query keyword representation. * 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 593b45352..26fae390b 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 @@ -901,6 +901,32 @@ public class QueryMapperUnitTests { assertThat(document).containsEntry("geoJsonPoint.$near.$maxDistance", 1000.0D); } + @Test // DATAMONGO-2440 + public void convertsInWithNonIdFieldAndObjectIdTypeHintCorrectly() { + + String id = new ObjectId().toHexString(); + NonIdFieldWithObjectIdTargetType source = new NonIdFieldWithObjectIdTargetType(); + + source.stringAsOid = id; + + org.bson.Document target = mapper.getMappedObject(query(where("stringAsOid").in(id)).getQueryObject(), + context.getPersistentEntity(NonIdFieldWithObjectIdTargetType.class)); + assertThat(target).isEqualTo(org.bson.Document.parse("{\"stringAsOid\": {\"$in\": [{\"$oid\": \"" + id + "\"}]}}")); + } + + @Test // DATAMONGO-2440 + public void convertsInWithIdFieldAndObjectIdTypeHintCorrectly() { + + String id = new ObjectId().toHexString(); + NonIdFieldWithObjectIdTargetType source = new NonIdFieldWithObjectIdTargetType(); + + source.id = id; + + org.bson.Document target = mapper.getMappedObject(query(where("id").in(id)).getQueryObject(), + context.getPersistentEntity(NonIdFieldWithObjectIdTargetType.class)); + assertThat(target).isEqualTo(org.bson.Document.parse("{\"_id\": {\"$in\": [{\"$oid\": \"" + id + "\"}]}}")); + } + @Document public class Foo { @Id private ObjectId id; @@ -1037,4 +1063,10 @@ public class QueryMapperUnitTests { static class WithIdPropertyContainingUnderscore { @Id String with_underscore; } + + static class NonIdFieldWithObjectIdTargetType { + + String id; + @Field(targetType = FieldType.OBJECT_ID) String stringAsOid; + } }