1717
1818import  java .time .Duration ;
1919import  java .util .Map ;
20+ import  java .util .function .Function ;
2021import  java .util .function .IntUnaryOperator ;
2122import  java .util .function .LongUnaryOperator ;
2223
2829import  org .springframework .data .mongodb .core .aggregation .Aggregation ;
2930import  org .springframework .data .mongodb .core .aggregation .AggregationOptions ;
3031import  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 ;
3134import  org .springframework .data .mongodb .core .convert .MongoConverter ;
3235import  org .springframework .data .mongodb .core .mapping .FieldName ;
36+ import  org .springframework .data .mongodb .core .mapping .MongoSimpleTypes ;
3337import  org .springframework .data .mongodb .core .query .Collation ;
3438import  org .springframework .data .mongodb .core .query .Meta ;
3539import  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 ;
3644import  org .springframework .lang .Nullable ;
3745import  org .springframework .util .ClassUtils ;
3846import  org .springframework .util .ObjectUtils ;
@@ -116,13 +124,15 @@ static AggregationOptions.Builder applyHint(AggregationOptions.Builder builder,
116124	}
117125
118126	/** 
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. 
120129	 * 
121130	 * @param builder must not be {@literal null}. 
122131	 * @return never {@literal null}. 
123132	 * @since 4.2 
124133	 */ 
125- 	static  AggregationOptions .Builder  applyReadPreference (AggregationOptions .Builder  builder , MongoQueryMethod  queryMethod ) {
134+ 	static  AggregationOptions .Builder  applyReadPreference (AggregationOptions .Builder  builder ,
135+ 			MongoQueryMethod  queryMethod ) {
126136
127137		if  (!queryMethod .hasAnnotatedReadPreference ()) {
128138			return  builder ;
@@ -131,6 +141,93 @@ static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder
131141		return  builder .readPreference (ReadPreference .valueOf (queryMethod .getAnnotatedReadPreference ()));
132142	}
133143
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+ 
134231	/** 
135232	 * Append {@code $sort} aggregation stage if {@link ConvertingParameterAccessor#getSort()} is present. 
136233	 * 
@@ -139,7 +236,7 @@ static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder
139236	 * @param targetType 
140237	 */ 
141238	static  void  appendSortIfPresent (AggregationPipeline  aggregationPipeline , ConvertingParameterAccessor  accessor ,
142- 			Class <?> targetType ) {
239+ 			@ Nullable   Class <?> targetType ) {
143240
144241		if  (accessor .getSort ().isUnsorted ()) {
145242			return ;
@@ -254,4 +351,26 @@ private static <T> T getPotentiallyConvertedSimpleTypeValue(MongoConverter conve
254351
255352		return  converter .getConversionService ().convert (value , targetType );
256353	}
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+ 	}
257376}
0 commit comments