Skip to content

Commit 13b2fce

Browse files
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.
1 parent a103e9b commit 13b2fce

File tree

7 files changed

+121
-5
lines changed

7 files changed

+121
-5
lines changed

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,18 @@
1515
*/
1616
package org.springframework.data.mongodb.core.aggregation;
1717

18+
import java.beans.PropertyDescriptor;
19+
import java.lang.reflect.Method;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.stream.Collectors;
23+
1824
import org.bson.Document;
25+
import org.springframework.beans.BeanUtils;
1926
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2027
import org.springframework.lang.Nullable;
28+
import org.springframework.util.Assert;
29+
import org.springframework.util.ReflectionUtils;
2130

2231
/**
2332
* The context for an {@link AggregationOperation}.
@@ -66,4 +75,32 @@ default Document getMappedObject(Document document) {
6675
* @return
6776
*/
6877
FieldReference getReference(String name);
78+
79+
/**
80+
* Returns the {@link Fields} exposed by the type. May be a {@literal class} or an {@literal interface}.
81+
*
82+
* @param type must not be {@literal null}.
83+
* @return never {@literal null}.
84+
* @since 2.2
85+
*/
86+
default Fields getFields(Class<?> type) {
87+
88+
Assert.notNull(type, "Type must not be null!");
89+
90+
List<String> fields = Arrays.stream(BeanUtils.getPropertyDescriptors(type)) //
91+
.filter(it -> { // object and default methods
92+
Method method = it.getReadMethod();
93+
if (method == null) {
94+
return false;
95+
}
96+
if (ReflectionUtils.isObjectMethod(method)) {
97+
return false;
98+
}
99+
return !method.isDefault();
100+
}) //
101+
.map(PropertyDescriptor::getName) //
102+
.collect(Collectors.toList());
103+
104+
return Fields.fields(fields.toArray(new String[0]));
105+
}
69106
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ public FieldReference getReference(String name) {
8181
return getReference(null, name);
8282
}
8383

84+
/*
85+
* (non-Javadoc)
86+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class)
87+
*/
88+
@Override
89+
public Fields getFields(Class<?> type) {
90+
return rootContext.getFields(type);
91+
}
92+
8493
/**
8594
* Returns a {@link FieldReference} to the given {@link Field} with the given {@code name}.
8695
*

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,13 @@ public FieldReference getReference(Field field) {
7878
public FieldReference getReference(String name) {
7979
return new ExpressionFieldReference(delegate.getReference(name));
8080
}
81+
82+
/*
83+
* (non-Javadoc)
84+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class)
85+
*/
86+
@Override
87+
public Fields getFields(Class<?> type) {
88+
return delegate.getFields(type);
89+
}
8190
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ public FieldReference getReference(String name) {
9191
return delegate.getReference(name);
9292
}
9393

94+
/*
95+
* (non-Javadoc)
96+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class)
97+
*/
98+
@Override
99+
public Fields getFields(Class<?> type) {
100+
return delegate.getFields(type);
101+
}
102+
94103
@SuppressWarnings("unchecked")
95104
private Document doPrefix(Document source) {
96105

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1737,8 +1737,9 @@ static class TypeProjection extends Projection {
17371737
public Document toDocument(AggregationOperationContext context) {
17381738

17391739
Document projections = new Document();
1740-
ReflectionUtils.doWithFields(type, it -> projections.append(it.getName(), 1));
17411740

1741+
Fields fields = context.getFields(type);
1742+
fields.asList().forEach(it -> projections.append(it.getName(), 1));
17421743
return context.getMappedObject(projections, type);
17431744
}
17441745
}

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717

1818
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
1919

20+
import java.util.ArrayList;
21+
import java.util.List;
22+
2023
import org.bson.Document;
2124
import org.springframework.data.mapping.PersistentPropertyPath;
22-
import org.springframework.data.mapping.PropertyPath;
25+
import org.springframework.data.mapping.SimplePropertyHandler;
2326
import org.springframework.data.mapping.context.MappingContext;
2427
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
2528
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
@@ -101,6 +104,23 @@ public FieldReference getReference(String name) {
101104
return getReferenceFor(field(name));
102105
}
103106

107+
/*
108+
* (non-Javadoc)
109+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields(java.lang.Class)
110+
*/
111+
@Override
112+
public Fields getFields(Class<?> type) {
113+
114+
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
115+
if (entity == null) {
116+
return AggregationOperationContext.super.getFields(type);
117+
}
118+
119+
List<String> fields = new ArrayList<>();
120+
entity.doWithProperties((SimplePropertyHandler) it -> fields.add(it.getName()));
121+
return Fields.fields(fields.toArray(new String[fields.size()]));
122+
}
123+
104124
private FieldReference getReferenceFor(Field field) {
105125

106126
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext

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

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.util.Arrays;
2828
import java.util.List;
2929

30-
import org.assertj.core.api.Assertions;
3130
import org.bson.Document;
3231
import org.junit.Test;
3332
import org.springframework.data.domain.Range;
@@ -2114,7 +2113,7 @@ public void typeProjectionShouldIncludeTopLevelFieldsOfType() {
21142113
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
21152114
Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT);
21162115

2117-
Assertions.assertThat(projectClause) //
2116+
assertThat(projectClause) //
21182117
.hasSize(2) //
21192118
.containsEntry("title", 1) //
21202119
.containsEntry("author", 1);
@@ -2130,12 +2129,36 @@ public void typeProjectionShouldMapFieldNames() {
21302129
.toDocument(new TypeBasedAggregationOperationContext(Book.class, mappingContext, new QueryMapper(converter)));
21312130
Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT);
21322131

2133-
Assertions.assertThat(projectClause) //
2132+
assertThat(projectClause) //
21342133
.hasSize(2) //
21352134
.containsEntry("ti_tl_e", 1) //
21362135
.containsEntry("author", 1);
21372136
}
21382137

2138+
@Test // DATAMONGO-2200
2139+
public void typeProjectionShouldIncludeInterfaceProjectionValues() {
2140+
2141+
ProjectionOperation operation = Aggregation.project(ProjectionInterface.class);
2142+
2143+
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
2144+
Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT);
2145+
2146+
assertThat(projectClause) //
2147+
.hasSize(1) //
2148+
.containsEntry("title", 1);
2149+
}
2150+
2151+
@Test // DATAMONGO-2200
2152+
public void typeProjectionShouldBeEmptyIfNoPropertiesFound() {
2153+
2154+
ProjectionOperation operation = Aggregation.project(EmptyType.class);
2155+
2156+
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
2157+
Document projectClause = DocumentTestUtils.getAsDocument(document, PROJECT);
2158+
2159+
assertThat(projectClause).isEmpty();
2160+
}
2161+
21392162
private static Document exctractOperation(String field, Document fromProjectClause) {
21402163
return (Document) fromProjectClause.get(field);
21412164
}
@@ -2159,4 +2182,12 @@ static class Author {
21592182
String middle;
21602183
}
21612184

2185+
interface ProjectionInterface {
2186+
String getTitle();
2187+
}
2188+
2189+
static class EmptyType {
2190+
2191+
}
2192+
21622193
}

0 commit comments

Comments
 (0)