17
17
18
18
import java .time .Duration ;
19
19
import java .util .Map ;
20
+ import java .util .function .Function ;
20
21
import java .util .function .IntUnaryOperator ;
21
22
import java .util .function .LongUnaryOperator ;
22
23
28
29
import org .springframework .data .mongodb .core .aggregation .Aggregation ;
29
30
import org .springframework .data .mongodb .core .aggregation .AggregationOptions ;
30
31
import org .springframework .data .mongodb .core .aggregation .AggregationPipeline ;
32
+ import org .springframework .data .mongodb .core .aggregation .AggregationResults ;
33
+ import org .springframework .data .mongodb .core .aggregation .TypedAggregation ;
31
34
import org .springframework .data .mongodb .core .convert .MongoConverter ;
32
35
import org .springframework .data .mongodb .core .mapping .FieldName ;
36
+ import org .springframework .data .mongodb .core .mapping .MongoSimpleTypes ;
33
37
import org .springframework .data .mongodb .core .query .Collation ;
34
38
import org .springframework .data .mongodb .core .query .Meta ;
35
39
import org .springframework .data .mongodb .core .query .Query ;
40
+ import org .springframework .data .repository .query .ResultProcessor ;
41
+ import org .springframework .data .repository .query .ReturnedType ;
42
+ import org .springframework .data .util .ReflectionUtils ;
43
+ import org .springframework .data .util .TypeInformation ;
36
44
import org .springframework .lang .Nullable ;
37
45
import org .springframework .util .ClassUtils ;
38
46
import org .springframework .util .ObjectUtils ;
@@ -116,13 +124,15 @@ static AggregationOptions.Builder applyHint(AggregationOptions.Builder builder,
116
124
}
117
125
118
126
/**
119
- * If present apply the preference from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
127
+ * If present apply the preference from the {@link org.springframework.data.mongodb.repository.ReadPreference}
128
+ * annotation.
120
129
*
121
130
* @param builder must not be {@literal null}.
122
131
* @return never {@literal null}.
123
132
* @since 4.2
124
133
*/
125
- static AggregationOptions .Builder applyReadPreference (AggregationOptions .Builder builder , MongoQueryMethod queryMethod ) {
134
+ static AggregationOptions .Builder applyReadPreference (AggregationOptions .Builder builder ,
135
+ MongoQueryMethod queryMethod ) {
126
136
127
137
if (!queryMethod .hasAnnotatedReadPreference ()) {
128
138
return builder ;
@@ -131,6 +141,93 @@ static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder
131
141
return builder .readPreference (ReadPreference .valueOf (queryMethod .getAnnotatedReadPreference ()));
132
142
}
133
143
144
+ static AggregationOptions computeOptions (MongoQueryMethod method , ConvertingParameterAccessor accessor ,
145
+ AggregationPipeline pipeline , ValueExpressionEvaluator evaluator ) {
146
+
147
+ AggregationOptions .Builder builder = Aggregation .newAggregationOptions ();
148
+
149
+ AggregationUtils .applyCollation (builder , method .getAnnotatedCollation (), accessor , evaluator );
150
+ AggregationUtils .applyMeta (builder , method );
151
+ AggregationUtils .applyHint (builder , method );
152
+ AggregationUtils .applyReadPreference (builder , method );
153
+
154
+ TypeInformation <?> returnType = method .getReturnType ();
155
+ if (returnType .getComponentType () != null ) {
156
+ returnType = returnType .getRequiredComponentType ();
157
+ }
158
+ if (ReflectionUtils .isVoid (returnType .getType ()) && pipeline .isOutOrMerge ()) {
159
+ builder .skipOutput ();
160
+ }
161
+
162
+ return builder .build ();
163
+ }
164
+
165
+ /**
166
+ * Prepares the AggregationPipeline including type discovery and calling {@link AggregationCallback} to run the
167
+ * aggregation.
168
+ */
169
+ @ Nullable
170
+ static <T > T doAggregate (AggregationPipeline pipeline , MongoQueryMethod method , ResultProcessor processor ,
171
+ ConvertingParameterAccessor accessor ,
172
+ Function <MongoParameterAccessor , ValueExpressionEvaluator > evaluatorFunction , AggregationCallback <T > callback ) {
173
+
174
+ Class <?> sourceType = method .getDomainClass ();
175
+ ReturnedType returnedType = processor .getReturnedType ();
176
+ // 🙈Interface Projections do not happen on the Aggregation level but through our repository infrastructure.
177
+ // Non-projections and raw results (AggregationResults<…>) are handled here. Interface projections read a Document
178
+ // and DTO projections read the returned type.
179
+ // We also support simple return types (String) that are read from a Document
180
+ TypeInformation <?> returnType = method .getReturnType ();
181
+ Class <?> returnElementType = (returnType .getComponentType () != null ? returnType .getRequiredComponentType ()
182
+ : returnType ).getType ();
183
+ Class <?> entityType ;
184
+
185
+ boolean isRawAggregationResult = ClassUtils .isAssignable (AggregationResults .class , method .getReturnedObjectType ());
186
+
187
+ if (returnElementType .equals (Document .class )) {
188
+ entityType = sourceType ;
189
+ } else {
190
+ entityType = returnElementType ;
191
+ }
192
+
193
+ AggregationUtils .appendSortIfPresent (pipeline , accessor , entityType );
194
+
195
+ if (method .isSliceQuery ()) {
196
+ AggregationUtils .appendLimitAndOffsetIfPresent (pipeline , accessor , LongUnaryOperator .identity (),
197
+ limit -> limit + 1 );
198
+ } else {
199
+ AggregationUtils .appendLimitAndOffsetIfPresent (pipeline , accessor );
200
+ }
201
+
202
+ AggregationOptions options = AggregationUtils .computeOptions (method , accessor , pipeline ,
203
+ evaluatorFunction .apply (accessor ));
204
+ TypedAggregation <?> aggregation = new TypedAggregation <>(sourceType , pipeline .getOperations (), options );
205
+
206
+ boolean isSimpleReturnType = MongoSimpleTypes .HOLDER .isSimpleType (returnElementType );
207
+ Class <?> typeToRead ;
208
+
209
+ if (isSimpleReturnType ) {
210
+ typeToRead = Document .class ;
211
+ } else if (isRawAggregationResult ) {
212
+ typeToRead = returnElementType ;
213
+ } else {
214
+
215
+ if (returnedType .isProjecting ()) {
216
+ typeToRead = returnedType .getReturnedType ().isInterface () ? Document .class : returnedType .getReturnedType ();
217
+ } else {
218
+ typeToRead = entityType ;
219
+ }
220
+ }
221
+
222
+ return callback .doAggregate (aggregation , sourceType , typeToRead , returnElementType , isSimpleReturnType ,
223
+ isRawAggregationResult );
224
+ }
225
+
226
+ static AggregationPipeline computePipeline (AbstractMongoQuery mongoQuery , MongoQueryMethod method ,
227
+ ConvertingParameterAccessor accessor ) {
228
+ return new AggregationPipeline (mongoQuery .parseAggregationPipeline (method .getAnnotatedAggregation (), accessor ));
229
+ }
230
+
134
231
/**
135
232
* Append {@code $sort} aggregation stage if {@link ConvertingParameterAccessor#getSort()} is present.
136
233
*
@@ -139,7 +236,7 @@ static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder
139
236
* @param targetType
140
237
*/
141
238
static void appendSortIfPresent (AggregationPipeline aggregationPipeline , ConvertingParameterAccessor accessor ,
142
- Class <?> targetType ) {
239
+ @ Nullable Class <?> targetType ) {
143
240
144
241
if (accessor .getSort ().isUnsorted ()) {
145
242
return ;
@@ -254,4 +351,26 @@ private static <T> T getPotentiallyConvertedSimpleTypeValue(MongoConverter conve
254
351
255
352
return converter .getConversionService ().convert (value , targetType );
256
353
}
354
+
355
+ /**
356
+ * Interface to invoke an aggregation along with source, intermediate, and target types.
357
+ *
358
+ * @param <T>
359
+ */
360
+ interface AggregationCallback <T > {
361
+
362
+ /**
363
+ * @param aggregation
364
+ * @param domainType
365
+ * @param typeToRead
366
+ * @param elementType
367
+ * @param simpleType whether the aggregation returns {@link Document} or a
368
+ * {@link org.springframework.data.mapping.model.SimpleTypeHolder simple type}.
369
+ * @param rawResult whether the aggregation returns {@link AggregationResults}.
370
+ * @return
371
+ */
372
+ @ Nullable
373
+ T doAggregate (TypedAggregation <?> aggregation , Class <?> domainType , Class <?> typeToRead , Class <?> elementType ,
374
+ boolean simpleType , boolean rawResult );
375
+ }
257
376
}
0 commit comments