From 7f7be5e47d2bd5c180ef2709fc2e556fc8a8d8dd Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 11 Mar 2020 10:30:28 +0100 Subject: [PATCH] DATAMONGO-2488 - Fix nested array path field name mapping. Original pull request: #841. --- .../mongodb/core/convert/QueryMapper.java | 33 ++++++++-- .../mongodb/core/convert/UpdateMapper.java | 10 +-- .../core/convert/QueryMapperUnitTests.java | 65 ++++++++++++++++++- 3 files changed, 97 insertions(+), 11 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 713d50b6b..f7ec113cf 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 @@ -24,7 +24,6 @@ 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; @@ -175,7 +174,7 @@ public class QueryMapper { } Document mappedSort = new Document(); - for(Map.Entry entry : BsonUtils.asMap(sortObject).entrySet()) { + for (Map.Entry entry : BsonUtils.asMap(sortObject).entrySet()) { Field field = createPropertyField(entity, entry.getKey(), mappingContext); mappedSort.put(field.getMappedKey(), entry.getValue()); @@ -1158,7 +1157,7 @@ public class QueryMapper { * @return */ protected Converter getPropertyConverter() { - return new PositionParameterRetainingPropertyKeyConverter(name); + return new PositionParameterRetainingPropertyKeyConverter(name, mappingContext); } /** @@ -1172,6 +1171,10 @@ public class QueryMapper { return new AssociationConverter(getAssociation()); } + protected MappingContext, MongoPersistentProperty> getMappingContext() { + return mappingContext; + } + /** * @author Christoph Strobl * @since 1.8 @@ -1180,8 +1183,9 @@ public class QueryMapper { private final KeyMapper keyMapper; - public PositionParameterRetainingPropertyKeyConverter(String rawKey) { - this.keyMapper = new KeyMapper(rawKey); + public PositionParameterRetainingPropertyKeyConverter(String rawKey, + MappingContext, MongoPersistentProperty> ctx) { + this.keyMapper = new KeyMapper(rawKey, ctx); } /* @@ -1222,11 +1226,14 @@ public class QueryMapper { static class KeyMapper { private final Iterator iterator; + private final MappingContext, MongoPersistentProperty> mappingContext; - public KeyMapper(String key) { + public KeyMapper(String key, + MappingContext, MongoPersistentProperty> mappingContext) { this.iterator = Arrays.asList(key.split("\\.")).iterator(); this.iterator.next(); + this.mappingContext = mappingContext; } /** @@ -1240,9 +1247,22 @@ public class QueryMapper { StringBuilder mappedName = new StringBuilder(PropertyToFieldNameConverter.INSTANCE.convert(property)); boolean inspect = iterator.hasNext(); + int depth = 0; while (inspect) { String partial = iterator.next(); + + if (depth > 0 && property.isCollectionLike()) { + + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(property.getComponentType()); + if (persistentEntity != null) { + MongoPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(partial); + if(persistentProperty != null) { + partial = mapPropertyName(persistentProperty); + } + } + } + boolean isPositional = (isPositionalParameter(partial) && (property.isMap() || property.isCollectionLike())); if (isPositional) { @@ -1250,6 +1270,7 @@ public class QueryMapper { } inspect = isPositional && iterator.hasNext(); + depth++; } return mappedName.toString(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index 5cf86823c..463c15d4e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java @@ -308,7 +308,7 @@ public class UpdateMapper extends QueryMapper { */ @Override protected Converter getPropertyConverter() { - return new PositionParameterRetainingPropertyKeyConverter(key); + return new PositionParameterRetainingPropertyKeyConverter(key, getMappingContext()); } /* @@ -317,7 +317,7 @@ public class UpdateMapper extends QueryMapper { */ @Override protected Converter getAssociationConverter() { - return new UpdateAssociationConverter(getAssociation(), key); + return new UpdateAssociationConverter(getMappingContext(), getAssociation(), key); } /** @@ -334,10 +334,12 @@ public class UpdateMapper extends QueryMapper { * * @param association must not be {@literal null}. */ - public UpdateAssociationConverter(Association association, String key) { + public UpdateAssociationConverter( + MappingContext, MongoPersistentProperty> mappingContext, + Association association, String key) { super(association); - this.mapper = new KeyMapper(key); + this.mapper = new KeyMapper(key, mappingContext); } /* 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 e3ab12d40..a9ecdc88b 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 @@ -36,7 +36,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - import org.springframework.data.annotation.Id; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -927,6 +926,70 @@ public class QueryMapperUnitTests { assertThat(target).isEqualTo(org.bson.Document.parse("{\"_id\": {\"$in\": [{\"$oid\": \"" + id + "\"}]}}")); } + @Test // DATAMONGO-2488 + public void mapsNestedArrayPathCorrectlyForNonMatchingPath() { + + org.bson.Document target = mapper.getMappedObject( + query(where("array.$[some_item].nested.$[other_item]").is("value")).getQueryObject(), + context.getPersistentEntity(Foo.class)); + + assertThat(target).isEqualTo(new org.bson.Document("array.$[some_item].nested.$[other_item]", "value")); + } + + @Test // DATAMONGO-2488 + public void mapsNestedArrayPathCorrectlyForObjectTargetArray() { + + org.bson.Document target = mapper.getMappedObject( + query(where("arrayObj.$[some_item].nested.$[other_item]").is("value")).getQueryObject(), + context.getPersistentEntity(WithNestedArray.class)); + + assertThat(target).isEqualTo(new org.bson.Document("arrayObj.$[some_item].nested.$[other_item]", "value")); + } + + @Test // DATAMONGO-2488 + public void mapsNestedArrayPathCorrectlyForStringTargetArray() { + + org.bson.Document target = mapper.getMappedObject( + query(where("arrayString.$[some_item].nested.$[other_item]").is("value")).getQueryObject(), + context.getPersistentEntity(WithNestedArray.class)); + + assertThat(target).isEqualTo(new org.bson.Document("arrayString.$[some_item].nested.$[other_item]", "value")); + } + + @Test // DATAMONGO-2488 + public void mapsCustomFieldNamesForNestedArrayPathCorrectly() { + + org.bson.Document target = mapper.getMappedObject( + query(where("arrayCustomName.$[some_item].nested.$[other_item]").is("value")).getQueryObject(), + context.getPersistentEntity(WithNestedArray.class)); + + assertThat(target).isEqualTo(new org.bson.Document("arrayCustomName.$[some_item].nes-ted.$[other_item]", "value")); + } + + class WithNestedArray { + + List arrayObj; + List arrayString; + List arrayCustomName; + } + + class NestedArrayOfObj { + List nested; + } + + class NestedArrayOfObjCustomFieldName { + + @Field("nes-ted") List nested; + } + + class NestedArrayOfString { + List nested; + } + + class ArrayObj { + String foo; + } + @Document class Foo { @Id private ObjectId id;