Skip to content

Commit 95a991d

Browse files
DATAMONGO-2153 - Annotated aggregation support (sync).
The repository layer offers means interact with the aggregation framework via annotated repository finder methods. Similar to the JSON based queries a pipeline can be defined via the Aggregation annotation. The definition may contain simple placeholders like `?0` as well as SpEL expression markers `?#{ ... }`. public interface PersonRepository extends CrudReppsitory<Person, String> { @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }") List<PersonAggregate> groupByLastnameAnd(String property); @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }") List<PersonAggregate> groupByLastnameAndFirstnames(Sort sort); @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }") List<PersonAggregate> groupByLastnameAnd(String property, Pageable page); @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }") SumValue sumAgeUsingValueWrapper(); @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }") Long sumAge(); @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }") AggregationResults<SumValue> sumAgeRaw(); }
1 parent 2065a77 commit 95a991d

21 files changed

+883
-24
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.bson.Document;
1919
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
20+
import org.springframework.lang.Nullable;
2021

2122
/**
2223
* The context for an {@link AggregationOperation}.
@@ -33,7 +34,20 @@ public interface AggregationOperationContext {
3334
* @param document will never be {@literal null}.
3435
* @return must not be {@literal null}.
3536
*/
36-
Document getMappedObject(Document document);
37+
default Document getMappedObject(Document document) {
38+
return getMappedObject(document, null);
39+
}
40+
41+
/**
42+
* Returns the mapped {@link Document}, potentially converting the source considering mapping metadata for the given
43+
* type.
44+
*
45+
* @param document will never be {@literal null}.
46+
* @param type can be {@literal null}.
47+
* @return must not be {@literal null}.
48+
* @since 2.2
49+
*/
50+
Document getMappedObject(Document document, @Nullable Class<?> type);
3751

3852
/**
3953
* Returns a {@link FieldReference} for the given field or {@literal null} if the context does not expose the given

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2525
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
2626
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
27+
import org.springframework.lang.Nullable;
2728

2829
/**
2930
* Rendering support for {@link AggregationOperation} into a {@link List} of {@link org.bson.Document}.
@@ -75,15 +76,16 @@ static List<Document> toDocument(List<AggregationOperation> operations, Aggregat
7576
* Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is.
7677
*
7778
* @author Oliver Gierke
79+
* @author Christoph Strobl
7880
*/
7981
private static class NoOpAggregationOperationContext implements AggregationOperationContext {
8082

8183
/*
8284
* (non-Javadoc)
83-
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
85+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document, java.lang.Class)
8486
*/
8587
@Override
86-
public Document getMappedObject(Document document) {
88+
public Document getMappedObject(Document document, @Nullable Class<?> type) {
8789
return document;
8890
}
8991

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ public ExposedFieldsAggregationOperationContext(ExposedFields exposedFields,
5656

5757
/*
5858
* (non-Javadoc)
59-
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
59+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document, java.lang.Class)
6060
*/
6161
@Override
62-
public Document getMappedObject(Document document) {
63-
return rootContext.getMappedObject(document);
62+
public Document getMappedObject(Document document, @Nullable Class<?> type) {
63+
return rootContext.getMappedObject(document, type);
6464
}
6565

6666
/*

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ public NestedDelegatingExpressionAggregationOperationContext(AggregationOperatio
4545

4646
/*
4747
* (non-Javadoc)
48-
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
48+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document, java.lang.Class)
4949
*/
5050
@Override
51-
public Document getMappedObject(Document document) {
52-
return delegate.getMappedObject(document);
51+
public Document getMappedObject(Document document, Class<?> type) {
52+
return delegate.getMappedObject(document, type);
5353
}
5454

5555
/*

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.bson.Document;
2727
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
28+
import org.springframework.lang.Nullable;
2829

2930
/**
3031
* {@link AggregationOperationContext} implementation prefixing non-command keys on root level with the given prefix.
@@ -56,11 +57,11 @@ public PrefixingDelegatingAggregationOperationContext(AggregationOperationContex
5657

5758
/*
5859
* (non-Javadoc)
59-
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
60+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document, java.lang.Class)
6061
*/
6162
@Override
62-
public Document getMappedObject(Document document) {
63-
return doPrefix(delegate.getMappedObject(document));
63+
public Document getMappedObject(Document document, @Nullable Class<?> type) {
64+
return doPrefix(delegate.getMappedObject(document, type));
6465
}
6566

6667
/*

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.mongodb.core.convert.QueryMapper;
2828
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
2929
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
30+
import org.springframework.lang.Nullable;
3031
import org.springframework.util.Assert;
3132

3233
/**
@@ -70,7 +71,16 @@ public TypeBasedAggregationOperationContext(Class<?> type,
7071
*/
7172
@Override
7273
public Document getMappedObject(Document document) {
73-
return mapper.getMappedObject(document, mappingContext.getPersistentEntity(type));
74+
return getMappedObject(document, type);
75+
}
76+
77+
/*
78+
* (non-Javadoc)
79+
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document, java.lang.Class)
80+
*/
81+
@Override
82+
public Document getMappedObject(Document document, @Nullable Class<?> type) {
83+
return mapper.getMappedObject(document, type != null ? mappingContext.getPersistentEntity(type) : null);
7484
}
7585

7686
/*
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.repository;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
import org.springframework.core.annotation.AliasFor;
25+
import org.springframework.data.annotation.QueryAnnotation;
26+
27+
/**
28+
* NOTE for self: <br />
29+
* - Creates a typed aggregation using the repository type as input type and the method return type as target. <br />
30+
* - Collation still needs to be done. <br />
31+
*
32+
* @author Christoph Strobl
33+
* @since 2.2
34+
*/
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
37+
@Documented
38+
@QueryAnnotation
39+
public @interface Aggregation {
40+
41+
@AliasFor("pipeline")
42+
String[] value() default {};
43+
44+
@AliasFor("value")
45+
String[] pipeline() default {};
46+
47+
// TODO: add collation support once merged
48+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,28 @@ public MongoQueryMethod getQueryMethod() {
8080
@Override
8181
public Object execute(Object[] parameters) {
8282

83-
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(),
83+
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(),
8484
new MongoParametersParameterAccessor(method, parameters));
85+
86+
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
87+
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
88+
89+
return processor.processResult(doExecute(method, accessor, typeToRead));
90+
}
91+
92+
protected Object doExecute(MongoQueryMethod method, ConvertingParameterAccessor accessor, Class<?> typeToRead) {
93+
8594
Query query = createQuery(accessor);
8695

8796
applyQueryMetaAttributesWhenPresent(query);
8897
query = applyAnnotatedDefaultSortIfPresent(query);
8998

90-
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
91-
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
92-
9399
FindWithQuery<?> find = typeToRead == null //
94100
? executableFind //
95101
: executableFind.as(typeToRead);
96102

97103
MongoQueryExecution execution = getExecution(accessor, find);
98-
99-
return processor.processResult(execution.execute(query));
104+
return execution.execute(query);
100105
}
101106

102107
private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, FindWithQuery<?> operation) {
@@ -204,4 +209,5 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) {
204209
* @since 2.0.4
205210
*/
206211
protected abstract boolean isLimiting();
212+
207213
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.mapping.context.MappingContext;
3131
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3232
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
33+
import org.springframework.data.mongodb.repository.Aggregation;
3334
import org.springframework.data.mongodb.repository.Meta;
3435
import org.springframework.data.mongodb.repository.Query;
3536
import org.springframework.data.mongodb.repository.Tailable;
@@ -167,6 +168,15 @@ public MongoEntityMetadata<?> getEntityInformation() {
167168
return this.metadata;
168169
}
169170

171+
/**
172+
*
173+
* @return
174+
* @since 2.2
175+
*/
176+
Class<?> repositoryDomainType() {
177+
return getDomainClass();
178+
}
179+
170180
/*
171181
* (non-Javadoc)
172182
* @see org.springframework.data.repository.query.QueryMethod#getParameters()
@@ -322,6 +332,39 @@ public String getAnnotatedSort() {
322332
"Expected to find @Query annotation but did not. Make sure to check hasAnnotatedSort() before."));
323333
}
324334

335+
/**
336+
* Returns whether the method has an annotated query.
337+
*
338+
* @return
339+
* @since 2.2
340+
*/
341+
public boolean hasAnnotatedAggregation() {
342+
return findAnnotatedAggregation().isPresent();
343+
}
344+
345+
/**
346+
* Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
347+
* nor the attribute was specified.
348+
*
349+
* @return
350+
* @since 2.2
351+
*/
352+
@Nullable
353+
public String[] getAnnotatedAggregation() {
354+
return findAnnotatedAggregation().orElse(null);
355+
}
356+
357+
private Optional<String[]> findAnnotatedAggregation() {
358+
359+
return lookupAggregationAnnotation() //
360+
.map(Aggregation::pipeline) //
361+
.filter(it -> !ObjectUtils.isEmpty(it));
362+
}
363+
364+
Optional<Aggregation> lookupAggregationAnnotation() {
365+
return doFindAnnotation(Aggregation.class);
366+
}
367+
325368
@SuppressWarnings("unchecked")
326369
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
327370

0 commit comments

Comments
 (0)