Skip to content

Commit 51933a8

Browse files
DATAMONGO-2200 - Derive fields for aggregation $project stage from a given type.
We now allow to derive field names for a $project stage from a given type by including all top level fields. // $project : { title : 1, author : 1 } Aggregation.project(Book.class)
1 parent f752e7d commit 51933a8

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,19 @@ public static ProjectionOperation project(Fields fields) {
244244
return new ProjectionOperation(fields);
245245
}
246246

247+
/**
248+
* Creates a new {@link ProjectionOperation} including all top level fields of the given given {@link Class}.
249+
*
250+
* @param type must not be {@literal null}.
251+
* @return new instance of {@link ProjectionOperation}.
252+
* @since 2.2
253+
*/
254+
public static ProjectionOperation project(Class<?> type) {
255+
256+
Assert.notNull(type, "Type must not be null!");
257+
return new ProjectionOperation(type);
258+
}
259+
247260
/**
248261
* Factory method to create a new {@link UnwindOperation} for the field with the given name.
249262
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable;
3131
import org.springframework.lang.Nullable;
3232
import org.springframework.util.Assert;
33+
import org.springframework.util.ReflectionUtils;
3334

3435
/**
3536
* Encapsulates the aggregation framework {@code $project}-operation.
@@ -73,6 +74,16 @@ public ProjectionOperation(Fields fields) {
7374
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields));
7475
}
7576

77+
/**
78+
* Creates a new {@link ProjectionOperation} including all top level fields of the given {@link Class type}.
79+
*
80+
* @param type must not be {@literal null}.
81+
* @since 2.2
82+
*/
83+
public ProjectionOperation(Class<?> type) {
84+
this(NONE, Collections.singletonList(new TypeProjection(type)));
85+
}
86+
7687
/**
7788
* Copy constructor to allow building up {@link ProjectionOperation} instances from already existing
7889
* {@link Projection}s.
@@ -1700,4 +1711,36 @@ public Document toDocument(AggregationOperationContext context) {
17001711
return new Document(field.getName(), expression.toDocument(context));
17011712
}
17021713
}
1714+
1715+
/**
1716+
* A {@link Projection} including all top level fields of the given target type mapped to include potentially
1717+
* deviating field names.
1718+
*
1719+
* @since 2.2
1720+
* @author Christoph Strobl
1721+
*/
1722+
static class TypeProjection extends Projection {
1723+
1724+
private final Class<?> type;
1725+
1726+
TypeProjection(Class<?> type) {
1727+
1728+
super(Fields.field(type.getSimpleName()));
1729+
this.type = type;
1730+
}
1731+
1732+
/*
1733+
* (non-Javadoc)
1734+
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
1735+
*/
1736+
@Override
1737+
public Document toDocument(AggregationOperationContext context) {
1738+
1739+
Document projections = new Document();
1740+
ReflectionUtils.doWithFields(type, it -> projections.append(it.getName(), 1));
1741+
1742+
// TODO: need to map field names here! waiting for DATAMONGO-2153 introducing ctx.getMappedObject(doc, type)
1743+
return context.getMappedObject(projections);
1744+
}
1745+
}
17031746
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import java.util.Arrays;
2828
import java.util.List;
2929

30+
import org.assertj.core.api.Assertions;
3031
import org.bson.Document;
32+
import org.junit.Ignore;
3133
import org.junit.Test;
3234
import org.springframework.data.domain.Range;
3335
import org.springframework.data.domain.Range.Bound;
@@ -41,6 +43,12 @@
4143
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
4244
import org.springframework.data.mongodb.core.aggregation.StringOperators.Concat;
4345
import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable;
46+
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
47+
import org.springframework.data.mongodb.core.convert.MongoConverter;
48+
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
49+
import org.springframework.data.mongodb.core.convert.QueryMapper;
50+
import org.springframework.data.mongodb.core.mapping.Field;
51+
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
4452

4553
/**
4654
* Unit tests for {@link ProjectionOperation}.
@@ -61,7 +69,7 @@ public class ProjectionOperationUnitTests {
6169

6270
@Test(expected = IllegalArgumentException.class) // DATAMONGO-586
6371
public void rejectsNullFields() {
64-
new ProjectionOperation(null);
72+
new ProjectionOperation((Fields) null);
6573
}
6674

6775
@Test // DATAMONGO-586
@@ -2099,6 +2107,37 @@ public void shouldRenderDateFromStringWithFormat() {
20992107
"{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", format : \"dd/mm/yyyy\" } } } }"));
21002108
}
21012109

2110+
@Test // DATAMONGO-2200
2111+
public void typeProjectionShouldIncludeTopLevelFieldsOfType() {
2112+
2113+
ProjectionOperation operation = Aggregation.project(Book.class);
2114+
2115+
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
2116+
Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT);
2117+
2118+
Assertions.assertThat(projectClause) //
2119+
.hasSize(2) //
2120+
.containsEntry("title", 1) //
2121+
.containsEntry("author", 1);
2122+
}
2123+
2124+
@Test // DATAMONGO-2200
2125+
@Ignore("requires DATAMONGO-2153")
2126+
public void typeProjectionShouldMapFieldNames() {
2127+
2128+
MongoMappingContext mappingContext = new MongoMappingContext();
2129+
MongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
2130+
2131+
Document document = Aggregation.project(BookRenamed.class)
2132+
.toDocument(new TypeBasedAggregationOperationContext(Book.class, mappingContext, new QueryMapper(converter)));
2133+
Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT);
2134+
2135+
Assertions.assertThat(projectClause) //
2136+
.hasSize(2) //
2137+
.containsEntry("ti_tl_e", 1) //
2138+
.containsEntry("author", 1);
2139+
}
2140+
21022141
private static Document exctractOperation(String field, Document fromProjectClause) {
21032142
return (Document) fromProjectClause.get(field);
21042143
}
@@ -2109,6 +2148,12 @@ static class Book {
21092148
Author author;
21102149
}
21112150

2151+
@Data
2152+
static class BookRenamed {
2153+
@Field("ti_tl_e") String title;
2154+
Author author;
2155+
}
2156+
21122157
@Data
21132158
static class Author {
21142159
String first;

0 commit comments

Comments
 (0)