diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index 5cf5c852c..32f2f6e9a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -244,6 +244,19 @@ public class Aggregation { return new ProjectionOperation(fields); } + /** + * Creates a new {@link ProjectionOperation} including all top level fields of the given given {@link Class}. + * + * @param type must not be {@literal null}. + * @return new instance of {@link ProjectionOperation}. + * @since 2.2 + */ + public static ProjectionOperation project(Class type) { + + Assert.notNull(type, "Type must not be null!"); + return new ProjectionOperation(type); + } + /** * Factory method to create a new {@link UnwindOperation} for the field with the given name. * 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 052700543..93b381b4b 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 @@ -30,6 +30,7 @@ import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Pro import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * Encapsulates the aggregation framework {@code $project}-operation. @@ -73,6 +74,16 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields)); } + /** + * Creates a new {@link ProjectionOperation} including all top level fields of the given {@link Class type}. + * + * @param type must not be {@literal null}. + * @since 2.2 + */ + public ProjectionOperation(Class type) { + this(NONE, Collections.singletonList(new TypeProjection(type))); + } + /** * Copy constructor to allow building up {@link ProjectionOperation} instances from already existing * {@link Projection}s. @@ -1700,4 +1711,35 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { return new Document(field.getName(), expression.toDocument(context)); } } + + /** + * A {@link Projection} including all top level fields of the given target type mapped to include potentially + * deviating field names. + * + * @since 2.2 + * @author Christoph Strobl + */ + static class TypeProjection extends Projection { + + private final Class type; + + TypeProjection(Class type) { + + super(Fields.field(type.getSimpleName())); + this.type = type; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public Document toDocument(AggregationOperationContext context) { + + Document projections = new Document(); + ReflectionUtils.doWithFields(type, it -> projections.append(it.getName(), 1)); + + return context.getMappedObject(projections, type); + } + } } 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 92c26eb28..9cd0140bd 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,6 +27,7 @@ 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; @@ -41,6 +42,12 @@ import org.springframework.data.mongodb.core.aggregation.DateOperators.Timezone; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; import org.springframework.data.mongodb.core.aggregation.StringOperators.Concat; import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; +import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; /** * Unit tests for {@link ProjectionOperation}. @@ -61,7 +68,7 @@ public class ProjectionOperationUnitTests { @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void rejectsNullFields() { - new ProjectionOperation(null); + new ProjectionOperation((Fields) null); } @Test // DATAMONGO-586 @@ -2099,6 +2106,36 @@ public class ProjectionOperationUnitTests { "{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", format : \"dd/mm/yyyy\" } } } }")); } + @Test // DATAMONGO-2200 + public void typeProjectionShouldIncludeTopLevelFieldsOfType() { + + ProjectionOperation operation = Aggregation.project(Book.class); + + Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT); + Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT); + + Assertions.assertThat(projectClause) // + .hasSize(2) // + .containsEntry("title", 1) // + .containsEntry("author", 1); + } + + @Test // DATAMONGO-2200 + public void typeProjectionShouldMapFieldNames() { + + MongoMappingContext mappingContext = new MongoMappingContext(); + MongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); + + Document document = Aggregation.project(BookRenamed.class) + .toDocument(new TypeBasedAggregationOperationContext(Book.class, mappingContext, new QueryMapper(converter))); + Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT); + + Assertions.assertThat(projectClause) // + .hasSize(2) // + .containsEntry("ti_tl_e", 1) // + .containsEntry("author", 1); + } + private static Document exctractOperation(String field, Document fromProjectClause) { return (Document) fromProjectClause.get(field); } @@ -2109,6 +2146,12 @@ public class ProjectionOperationUnitTests { Author author; } + @Data + static class BookRenamed { + @Field("ti_tl_e") String title; + Author author; + } + @Data static class Author { String first;