Skip to content

Commit 3ea3118

Browse files
Introduce AggregationStage API
With the introduction of AggregationStage we move the API closer to the MongoDB terminology removing kognitive overhead. Also the change allows us to switch back and forth with the default implementations of toDocument and toDocuments which let's us remove the deprecation warnings having dedicated interfaces that indicate what to implement in order to comply with the usage pattern.
1 parent 5305c95 commit 3ea3118

16 files changed

+467
-80
lines changed

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

+14
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
3131
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
3232
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
33+
import org.springframework.data.mongodb.core.aggregation.AggregationStage;
3334
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
3435
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
3536
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
@@ -296,6 +297,19 @@ default MongoCollection<Document> createView(String name, Class<?> source, Aggre
296297
return createView(name, source, AggregationPipeline.of(stages));
297298
}
298299

300+
/**
301+
* Create a view with the provided name. The view content is defined by the {@link AggregationStage pipeline stages}
302+
* on another collection or view identified by the given {@link #getCollectionName(Class) source type}.
303+
*
304+
* @param name the name of the view to create.
305+
* @param source the type defining the views source collection.
306+
* @param stages the {@link AggregationOperation aggregation pipeline stages} defining the view content.
307+
* @since 4.1
308+
*/
309+
default MongoCollection<Document> createView(String name, Class<?> source, AggregationStage... stages) {
310+
return createView(name, source, AggregationPipeline.of(stages));
311+
}
312+
299313
/**
300314
* Create a view with the provided name. The view content is defined by the {@link AggregationPipeline pipeline} on
301315
* another collection or view identified by the given {@link #getCollectionName(Class) source type}.

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ public MongoCollection<Document> createView(String name, Class<?> source, Aggreg
648648
@Nullable ViewOptions options) {
649649

650650
return createView(name, getCollectionName(source),
651-
queryOperations.createAggregation(Aggregation.newAggregation(source, pipeline.getOperations()), source),
651+
queryOperations.createAggregation(Aggregation.newAggregation(source, pipeline.getStages()), source),
652652
options);
653653
}
654654

@@ -657,7 +657,7 @@ public MongoCollection<Document> createView(String name, String source, Aggregat
657657
@Nullable ViewOptions options) {
658658

659659
return createView(name, source,
660-
queryOperations.createAggregation(Aggregation.newAggregation(pipeline.getOperations()), (Class<?>) null),
660+
queryOperations.createAggregation(Aggregation.newAggregation(pipeline.getStages()), (Class<?>) null),
661661
options);
662662
}
663663

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
import org.bson.Document;
2626
import org.reactivestreams.Publisher;
2727
import org.reactivestreams.Subscription;
28-
2928
import org.springframework.data.geo.GeoResult;
3029
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
3130
import org.springframework.data.mongodb.core.aggregation.Aggregation;
3231
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
3332
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
3433
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
34+
import org.springframework.data.mongodb.core.aggregation.AggregationStage;
3535
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
3636
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
3737
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
@@ -256,6 +256,19 @@ default Mono<MongoCollection<Document>> createView(String name, Class<?> source,
256256
return createView(name, source, AggregationPipeline.of(stages));
257257
}
258258

259+
/**
260+
* Create a view with the provided name. The view content is defined by the {@link AggregationStage pipeline stages}
261+
* on another collection or view identified by the given {@link #getCollectionName(Class) source type}.
262+
*
263+
* @param name the name of the view to create.
264+
* @param source the type defining the views source collection.
265+
* @param stages the {@link AggregationOperation aggregation pipeline stages} defining the view content.
266+
* @since 4.1
267+
*/
268+
default Mono<MongoCollection<Document>> createView(String name, Class<?> source, AggregationStage... stages) {
269+
return createView(name, source, AggregationPipeline.of(stages));
270+
}
271+
259272
/**
260273
* Create a view with the provided name. The view content is defined by the {@link AggregationPipeline pipeline} on
261274
* another collection or view identified by the given {@link #getCollectionName(Class) source type}.

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ public Mono<MongoCollection<Document>> createView(String name, Class<?> source,
676676
@Nullable ViewOptions options) {
677677

678678
return createView(name, getCollectionName(source),
679-
queryOperations.createAggregation(Aggregation.newAggregation(source, pipeline.getOperations()), source),
679+
queryOperations.createAggregation(Aggregation.newAggregation(source, pipeline.getStages()), source),
680680
options);
681681
}
682682

@@ -685,7 +685,7 @@ public Mono<MongoCollection<Document>> createView(String name, String source, Ag
685685
@Nullable ViewOptions options) {
686686

687687
return createView(name, source,
688-
queryOperations.createAggregation(Aggregation.newAggregation(pipeline.getOperations()), (Class<?>) null),
688+
queryOperations.createAggregation(Aggregation.newAggregation(pipeline.getStages()), (Class<?>) null),
689689
options);
690690
}
691691

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

+66-14
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ public class Aggregation {
102102
private final AggregationOptions options;
103103

104104
/**
105-
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
105+
* Creates a new {@link Aggregation} from the given {@link AggregationStage}s.
106106
*
107107
* @param operations must not be {@literal null} or empty.
108108
*/
109-
public static Aggregation newAggregation(List<? extends AggregationOperation> operations) {
110-
return newAggregation(operations.toArray(new AggregationOperation[operations.size()]));
109+
public static Aggregation newAggregation(List<? extends AggregationStage> operations) {
110+
return newAggregation(operations.toArray(AggregationStage[]::new));
111111
}
112112

113113
/**
@@ -119,6 +119,16 @@ public static Aggregation newAggregation(AggregationOperation... operations) {
119119
return new Aggregation(operations);
120120
}
121121

122+
/**
123+
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
124+
*
125+
* @param stages must not be {@literal null} or empty.
126+
* @since 4.1
127+
*/
128+
public static Aggregation newAggregation(AggregationStage... stages) {
129+
return new Aggregation(stages);
130+
}
131+
122132
/**
123133
* Creates a new {@link AggregationUpdate} from the given {@link AggregationOperation}s.
124134
*
@@ -130,6 +140,17 @@ public static AggregationUpdate newUpdate(AggregationOperation... operations) {
130140
return AggregationUpdate.from(Arrays.asList(operations));
131141
}
132142

143+
/**
144+
* Creates a new {@link AggregationUpdate} from the given {@link AggregationOperation}s.
145+
*
146+
* @param operations can be {@literal empty} but must not be {@literal null}.
147+
* @return new instance of {@link AggregationUpdate}.
148+
* @since 4.1
149+
*/
150+
public static AggregationUpdate newUpdate(AggregationStage... operations) {
151+
return AggregationUpdate.updateFrom(Arrays.asList(operations));
152+
}
153+
133154
/**
134155
* Returns a copy of this {@link Aggregation} with the given {@link AggregationOptions} set. Note that options are
135156
* supported in MongoDB version 2.6+.
@@ -141,7 +162,7 @@ public static AggregationUpdate newUpdate(AggregationOperation... operations) {
141162
public Aggregation withOptions(AggregationOptions options) {
142163

143164
Assert.notNull(options, "AggregationOptions must not be null");
144-
return new Aggregation(this.pipeline.getOperations(), options);
165+
return new Aggregation(this.pipeline.getStages(), options);
145166
}
146167

147168
/**
@@ -150,8 +171,8 @@ public Aggregation withOptions(AggregationOptions options) {
150171
* @param type must not be {@literal null}.
151172
* @param operations must not be {@literal null} or empty.
152173
*/
153-
public static <T> TypedAggregation<T> newAggregation(Class<T> type, List<? extends AggregationOperation> operations) {
154-
return newAggregation(type, operations.toArray(new AggregationOperation[operations.size()]));
174+
public static <T> TypedAggregation<T> newAggregation(Class<T> type, List<? extends AggregationStage> operations) {
175+
return newAggregation(type, operations.toArray(AggregationStage[]::new));
155176
}
156177

157178
/**
@@ -164,6 +185,17 @@ public static <T> TypedAggregation<T> newAggregation(Class<T> type, AggregationO
164185
return new TypedAggregation<T>(type, operations);
165186
}
166187

188+
/**
189+
* Creates a new {@link TypedAggregation} for the given type and {@link AggregationOperation}s.
190+
*
191+
* @param type must not be {@literal null}.
192+
* @param stages must not be {@literal null} or empty.
193+
* @since 4.1
194+
*/
195+
public static <T> TypedAggregation<T> newAggregation(Class<T> type, AggregationStage... stages) {
196+
return new TypedAggregation<>(type, stages);
197+
}
198+
167199
/**
168200
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
169201
*
@@ -173,6 +205,15 @@ protected Aggregation(AggregationOperation... aggregationOperations) {
173205
this(asAggregationList(aggregationOperations));
174206
}
175207

208+
/**
209+
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
210+
*
211+
* @param aggregationOperations must not be {@literal null} or empty.
212+
*/
213+
protected Aggregation(AggregationStage... aggregationOperations) {
214+
this(Arrays.asList(aggregationOperations));
215+
}
216+
176217
/**
177218
* @param aggregationOperations must not be {@literal null} or empty.
178219
* @return
@@ -189,7 +230,7 @@ protected static List<AggregationOperation> asAggregationList(AggregationOperati
189230
*
190231
* @param aggregationOperations must not be {@literal null} or empty.
191232
*/
192-
protected Aggregation(List<AggregationOperation> aggregationOperations) {
233+
protected Aggregation(List<? extends AggregationStage> aggregationOperations) {
193234
this(aggregationOperations, DEFAULT_OPTIONS);
194235
}
195236

@@ -199,7 +240,7 @@ protected Aggregation(List<AggregationOperation> aggregationOperations) {
199240
* @param aggregationOperations must not be {@literal null}.
200241
* @param options must not be {@literal null} or empty.
201242
*/
202-
protected Aggregation(List<AggregationOperation> aggregationOperations, AggregationOptions options) {
243+
protected Aggregation(List<? extends AggregationStage> aggregationOperations, AggregationOptions options) {
203244

204245
Assert.notNull(aggregationOperations, "AggregationOperations must not be null");
205246
Assert.notNull(options, "AggregationOptions must not be null");
@@ -638,6 +679,17 @@ public static FacetOperationBuilder facet(AggregationOperation... aggregationOpe
638679
return facet().and(aggregationOperations);
639680
}
640681

682+
/**
683+
* Creates a new {@link FacetOperationBuilder} given {@link Aggregation}.
684+
*
685+
* @param stages the sub-pipeline, must not be {@literal null}.
686+
* @return new instance of {@link FacetOperation}.
687+
* @since 4.1
688+
*/
689+
public static FacetOperationBuilder facet(AggregationStage... stages) {
690+
return facet().and(stages);
691+
}
692+
641693
/**
642694
* Creates a new {@link LookupOperation}.
643695
*
@@ -668,14 +720,14 @@ public static LookupOperation lookup(Field from, Field localField, Field foreign
668720

669721
/**
670722
* Entrypoint for creating {@link LookupOperation $lookup} using a fluent builder API.
723+
*
671724
* <pre class="code">
672-
* Aggregation.lookup().from("restaurants")
673-
* .localField("restaurant_name")
674-
* .foreignField("name")
675-
* .let(newVariable("orders_drink").forField("drink"))
676-
* .pipeline(match(ctx -> new Document("$expr", new Document("$in", List.of("$$orders_drink", "$beverages")))))
677-
* .as("matches")
725+
* Aggregation.lookup().from("restaurants").localField("restaurant_name").foreignField("name")
726+
* .let(newVariable("orders_drink").forField("drink"))
727+
* .pipeline(match(ctx -> new Document("$expr", new Document("$in", List.of("$$orders_drink", "$beverages")))))
728+
* .as("matches")
678729
* </pre>
730+
*
679731
* @return new instance of {@link LookupOperationBuilder}.
680732
* @since 4.1
681733
*/

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

+7-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.data.mongodb.core.aggregation;
1717

18-
import java.util.Collections;
1918
import java.util.List;
2019

2120
import org.bson.Document;
@@ -30,30 +29,24 @@
3029
* @author Christoph Strobl
3130
* @since 1.3
3231
*/
33-
public interface AggregationOperation {
32+
public interface AggregationOperation extends MultiOperationAggregationStage {
3433

3534
/**
36-
* Turns the {@link AggregationOperation} into a {@link Document} by using the given
37-
* {@link AggregationOperationContext}.
38-
*
3935
* @param context the {@link AggregationOperationContext} to operate within. Must not be {@literal null}.
40-
* @return the Document
41-
* @deprecated since 2.2 in favor of {@link #toPipelineStages(AggregationOperationContext)}.
36+
* @return
4237
*/
43-
@Deprecated
38+
@Override
4439
Document toDocument(AggregationOperationContext context);
4540

4641
/**
47-
* Turns the {@link AggregationOperation} into list of {@link Document stages} by using the given
48-
* {@link AggregationOperationContext}. This allows a single {@link AggregationOptions} to add additional stages for
49-
* eg. {@code $sort} or {@code $limit}.
42+
* More the exception than the default.
5043
*
5144
* @param context the {@link AggregationOperationContext} to operate within. Must not be {@literal null}.
52-
* @return the pipeline stages to run through. Never {@literal null}.
53-
* @since 2.2
45+
* @return never {@literal null}.
5446
*/
47+
@Override
5548
default List<Document> toPipelineStages(AggregationOperationContext context) {
56-
return Collections.singletonList(toDocument(context));
49+
return List.of(toDocument(context));
5750
}
5851

5952
/**

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,19 @@ class AggregationOperationRenderer {
4545
* @param rootContext must not be {@literal null}.
4646
* @return the {@link List} of {@link Document}.
4747
*/
48-
static List<Document> toDocument(List<AggregationOperation> operations, AggregationOperationContext rootContext) {
48+
static List<Document> toDocument(List<? extends AggregationStage> operations, AggregationOperationContext rootContext) {
4949

5050
List<Document> operationDocuments = new ArrayList<Document>(operations.size());
5151

5252
AggregationOperationContext contextToUse = rootContext;
5353

54-
for (AggregationOperation operation : operations) {
54+
for (AggregationStage operation : operations) {
5555

56-
operationDocuments.addAll(operation.toPipelineStages(contextToUse));
56+
if(operation instanceof MultiOperationAggregationStage mops) {
57+
operationDocuments.addAll(mops.toPipelineStages(contextToUse));
58+
} else {
59+
operationDocuments.add(operation.toDocument(contextToUse));
60+
}
5761

5862
if (operation instanceof FieldsExposingAggregationOperation) {
5963

0 commit comments

Comments
 (0)