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.
This commit is contained in:
Christoph Strobl
2020-01-07 16:13:23 +01:00
committed by Mark Paluch
parent 5cffaf9d73
commit 8a1750691b
2 changed files with 66 additions and 17 deletions

View File

@@ -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<Object>) 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.
*

View File

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