From e24c5e0846e74dee1b77b52b3d5fb54b0ac3d7f4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 18 Jun 2019 09:31:22 +0200 Subject: [PATCH] DATAMONGO-2200 - Use mapping context where available. Use the mapping context to get the required fields or fall back to property descriptors if no mapping context is available. Original pull request: #748. --- .../AggregationOperationContext.java | 37 +++++++++++++++++++ ...osedFieldsAggregationOperationContext.java | 9 +++++ ...ExpressionAggregationOperationContext.java | 9 +++++ ...DelegatingAggregationOperationContext.java | 9 +++++ .../core/aggregation/ProjectionOperation.java | 3 +- .../TypeBasedAggregationOperationContext.java | 22 ++++++++++- .../ProjectionOperationUnitTests.java | 37 +++++++++++++++++-- 7 files changed, 121 insertions(+), 5 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java index a0e9bcde6..e73441958 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java @@ -15,9 +15,18 @@ */ package org.springframework.data.mongodb.core.aggregation; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + import org.bson.Document; +import org.springframework.beans.BeanUtils; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * The context for an {@link AggregationOperation}. @@ -66,4 +75,32 @@ public interface AggregationOperationContext { * @return */ FieldReference getReference(String name); + + /** + * Returns the {@link Fields} exposed by the type. May be a {@literal class} or an {@literal interface}. + * + * @param type must not be {@literal null}. + * @return never {@literal null}. + * @since 2.2 + */ + default Fields getFields(Class type) { + + Assert.notNull(type, "Type must not be null!"); + + List fields = Arrays.stream(BeanUtils.getPropertyDescriptors(type)) // + .filter(it -> { // object and default methods + Method method = it.getReadMethod(); + if (method == null) { + return false; + } + if (ReflectionUtils.isObjectMethod(method)) { + return false; + } + return !method.isDefault(); + }) // + .map(PropertyDescriptor::getName) // + .collect(Collectors.toList()); + + return Fields.fields(fields.toArray(new String[0])); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java index 5bb01ffbb..1603e2cc4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java @@ -81,6 +81,15 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo return getReference(null, name); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class) + */ + @Override + public Fields getFields(Class type) { + return rootContext.getFields(type); + } + /** * Returns a {@link FieldReference} to the given {@link Field} with the given {@code name}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java index 3f84da4e4..bcdce3bab 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java @@ -78,4 +78,13 @@ class NestedDelegatingExpressionAggregationOperationContext implements Aggregati public FieldReference getReference(String name) { return new ExpressionFieldReference(delegate.getReference(name)); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class) + */ + @Override + public Fields getFields(Class type) { + return delegate.getFields(type); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java index a82673cc9..911b69f2e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java @@ -91,6 +91,15 @@ public class PrefixingDelegatingAggregationOperationContext implements Aggregati return delegate.getReference(name); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class) + */ + @Override + public Fields getFields(Class type) { + return delegate.getFields(type); + } + @SuppressWarnings("unchecked") private Document doPrefix(Document source) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index 93b381b4b..3b0e94e42 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java @@ -1737,8 +1737,9 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { public Document toDocument(AggregationOperationContext context) { Document projections = new Document(); - ReflectionUtils.doWithFields(type, it -> projections.append(it.getName(), 1)); + Fields fields = context.getFields(type); + fields.asList().forEach(it -> projections.append(it.getName(), 1)); return context.getMappedObject(projections, type); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java index 06c125c53..ce116e40c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java @@ -17,9 +17,12 @@ package org.springframework.data.mongodb.core.aggregation; import static org.springframework.data.mongodb.core.aggregation.Fields.*; +import java.util.ArrayList; +import java.util.List; + import org.bson.Document; import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; @@ -101,6 +104,23 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio return getReferenceFor(field(name)); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class) + */ + @Override + public Fields getFields(Class type) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(type); + if (entity == null) { + return AggregationOperationContext.super.getFields(type); + } + + List fields = new ArrayList<>(); + entity.doWithProperties((SimplePropertyHandler) it -> fields.add(it.getName())); + return Fields.fields(fields.toArray(new String[fields.size()])); + } + private FieldReference getReferenceFor(Field field) { PersistentPropertyPath propertyPath = mappingContext diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java index 9cd0140bd..5e1c7354e 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java @@ -27,7 +27,6 @@ import lombok.Data; import java.util.Arrays; import java.util.List; -import org.assertj.core.api.Assertions; import org.bson.Document; import org.junit.Test; import org.springframework.data.domain.Range; @@ -2114,7 +2113,7 @@ public class ProjectionOperationUnitTests { Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT); Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT); - Assertions.assertThat(projectClause) // + assertThat(projectClause) // .hasSize(2) // .containsEntry("title", 1) // .containsEntry("author", 1); @@ -2130,12 +2129,36 @@ public class ProjectionOperationUnitTests { .toDocument(new TypeBasedAggregationOperationContext(Book.class, mappingContext, new QueryMapper(converter))); Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT); - Assertions.assertThat(projectClause) // + assertThat(projectClause) // .hasSize(2) // .containsEntry("ti_tl_e", 1) // .containsEntry("author", 1); } + @Test // DATAMONGO-2200 + public void typeProjectionShouldIncludeInterfaceProjectionValues() { + + ProjectionOperation operation = Aggregation.project(ProjectionInterface.class); + + Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT); + Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT); + + assertThat(projectClause) // + .hasSize(1) // + .containsEntry("title", 1); + } + + @Test // DATAMONGO-2200 + public void typeProjectionShouldBeEmptyIfNoPropertiesFound() { + + ProjectionOperation operation = Aggregation.project(EmptyType.class); + + Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT); + Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT); + + assertThat(projectClause).isEmpty(); + } + private static Document exctractOperation(String field, Document fromProjectClause) { return (Document) fromProjectClause.get(field); } @@ -2159,4 +2182,12 @@ public class ProjectionOperationUnitTests { String middle; } + interface ProjectionInterface { + String getTitle(); + } + + static class EmptyType { + + } + }