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 81b425b9a..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,25 +15,16 @@ */ 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; import org.bson.conversions.Bson; import org.bson.types.ObjectId; - import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.data.domain.Example; @@ -329,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()) { @@ -681,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 d02e37e43..88a35ca22 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 + 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 + 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; + } }