15
15
*/
16
16
package org .springframework .data .mongodb .repository .query ;
17
17
18
+ import java .util .ArrayList ;
19
+ import java .util .List ;
20
+
18
21
import org .bson .Document ;
19
22
import org .bson .codecs .configuration .CodecRegistry ;
20
- import org .springframework .data .domain .Pageable ;
21
23
import org .springframework .data .mapping .model .SpELExpressionEvaluator ;
22
24
import org .springframework .data .mongodb .core .ExecutableFindOperation .ExecutableFind ;
23
25
import org .springframework .data .mongodb .core .ExecutableFindOperation .FindWithQuery ;
24
26
import org .springframework .data .mongodb .core .ExecutableFindOperation .TerminatingFind ;
27
+ import org .springframework .data .mongodb .core .ExecutableUpdateOperation .ExecutableUpdate ;
25
28
import org .springframework .data .mongodb .core .MongoOperations ;
29
+ import org .springframework .data .mongodb .core .aggregation .AggregationOperation ;
30
+ import org .springframework .data .mongodb .core .aggregation .AggregationUpdate ;
31
+ import org .springframework .data .mongodb .core .query .BasicUpdate ;
26
32
import org .springframework .data .mongodb .core .query .Query ;
27
- import org .springframework .data .mongodb .core .query .Update ;
33
+ import org .springframework .data .mongodb .core .query .UpdateDefinition ;
34
+ import org .springframework .data .mongodb .repository .Update ;
28
35
import org .springframework .data .mongodb .repository .query .MongoQueryExecution .DeleteExecution ;
29
36
import org .springframework .data .mongodb .repository .query .MongoQueryExecution .GeoNearExecution ;
30
37
import org .springframework .data .mongodb .repository .query .MongoQueryExecution .PagedExecution ;
31
38
import org .springframework .data .mongodb .repository .query .MongoQueryExecution .PagingGeoNearExecution ;
32
39
import org .springframework .data .mongodb .repository .query .MongoQueryExecution .SlicedExecution ;
40
+ import org .springframework .data .mongodb .repository .query .MongoQueryExecution .UpdateExecution ;
41
+ import org .springframework .data .mongodb .util .json .ParameterBindingContext ;
42
+ import org .springframework .data .mongodb .util .json .ParameterBindingDocumentCodec ;
33
43
import org .springframework .data .repository .query .ParameterAccessor ;
34
44
import org .springframework .data .repository .query .QueryMethodEvaluationContextProvider ;
35
45
import org .springframework .data .repository .query .RepositoryQuery ;
36
46
import org .springframework .data .repository .query .ResultProcessor ;
37
47
import org .springframework .data .spel .ExpressionDependencies ;
48
+ import org .springframework .data .util .Lazy ;
38
49
import org .springframework .expression .EvaluationContext ;
39
50
import org .springframework .expression .ExpressionParser ;
51
+ import org .springframework .lang .NonNull ;
40
52
import org .springframework .lang .Nullable ;
41
53
import org .springframework .util .Assert ;
54
+ import org .springframework .util .ObjectUtils ;
55
+ import org .springframework .util .StringUtils ;
42
56
43
57
import com .mongodb .client .MongoDatabase ;
44
58
@@ -55,8 +69,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
55
69
private final MongoQueryMethod method ;
56
70
private final MongoOperations operations ;
57
71
private final ExecutableFind <?> executableFind ;
72
+ private final ExecutableUpdate <?> executableUpdate ;
58
73
private final ExpressionParser expressionParser ;
59
74
private final QueryMethodEvaluationContextProvider evaluationContextProvider ;
75
+ private final Lazy <ParameterBindingDocumentCodec > codec = Lazy
76
+ .of (() -> new ParameterBindingDocumentCodec (getCodecRegistry ()));
60
77
61
78
/**
62
79
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
@@ -81,6 +98,7 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, E
81
98
Class <?> type = metadata .getCollectionEntity ().getType ();
82
99
83
100
this .executableFind = operations .query (type );
101
+ this .executableUpdate = operations .update (type );
84
102
this .expressionParser = expressionParser ;
85
103
this .evaluationContextProvider = evaluationContextProvider ;
86
104
}
@@ -130,7 +148,17 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
130
148
131
149
if (isDeleteQuery ()) {
132
150
return new DeleteExecution (operations , method );
133
- } else if (method .isGeoNearQuery () && method .isPageQuery ()) {
151
+ }
152
+
153
+ if (method .isModifyingQuery ()) {
154
+ if (isLimiting ()) {
155
+ throw new IllegalStateException (
156
+ String .format ("Update method must not be limiting. Offending method: %s" , method ));
157
+ }
158
+ return new UpdateExecution (executableUpdate , method , () -> createUpdate (accessor ), accessor );
159
+ }
160
+
161
+ if (method .isGeoNearQuery () && method .isPageQuery ()) {
134
162
return new PagingGeoNearExecution (operation , method , accessor , this );
135
163
} else if (method .isGeoNearQuery ()) {
136
164
return new GeoNearExecution (operation , method , accessor );
@@ -139,11 +167,6 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
139
167
} else if (method .isStreamQuery ()) {
140
168
return q -> operation .matching (q ).stream ();
141
169
} else if (method .isCollectionQuery ()) {
142
-
143
- if (method .isModifyingQuery ()) {
144
- return q -> new UpdatingCollectionExecution (accessor .getPageable (), accessor .getUpdate ()).execute (q );
145
- }
146
-
147
170
return q -> operation .matching (q .with (accessor .getPageable ()).with (accessor .getSort ())).all ();
148
171
} else if (method .isPageQuery ()) {
149
172
return new PagedExecution (operation , accessor .getPageable ());
@@ -153,11 +176,6 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
153
176
return q -> operation .matching (q ).exists ();
154
177
} else {
155
178
return q -> {
156
-
157
- if (method .isModifyingQuery ()) {
158
- return new UpdatingSingleEntityExecution (accessor .getUpdate ()).execute (q );
159
- }
160
-
161
179
TerminatingFind <?> find = operation .matching (q );
162
180
return isLimiting () ? find .firstValue () : find .oneValue ();
163
181
};
@@ -217,6 +235,94 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) {
217
235
return applyQueryMetaAttributesWhenPresent (createQuery (accessor ));
218
236
}
219
237
238
+ /**
239
+ * Retrieves the {@link UpdateDefinition update} from the given
240
+ * {@link org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate() accessor} or creates
241
+ * one via by parsing the annotated statement extracted from {@link Update}.
242
+ *
243
+ * @param accessor never {@literal null}.
244
+ * @return the computed {@link UpdateDefinition}.
245
+ * @throws IllegalStateException if no update could be found.
246
+ * @since 3.4
247
+ */
248
+ protected UpdateDefinition createUpdate (ConvertingParameterAccessor accessor ) {
249
+
250
+ if (accessor .getUpdate () != null ) {
251
+ return accessor .getUpdate ();
252
+ }
253
+
254
+ if (method .hasAnnotatedUpdate ()) {
255
+
256
+ Update updateSource = method .getUpdateSource ();
257
+ if (StringUtils .hasText (updateSource .update ())) {
258
+ return new BasicUpdate (bindParameters (updateSource .update (), accessor ));
259
+ }
260
+ if (!ObjectUtils .isEmpty (updateSource .pipeline ())) {
261
+ return AggregationUpdate .from (parseAggregationPipeline (updateSource .pipeline (), accessor ));
262
+ }
263
+ }
264
+
265
+ throw new IllegalStateException (String .format ("No Update provided for method %s." , method ));
266
+ }
267
+
268
+ /**
269
+ * Parse the given aggregation pipeline stages applying values to placeholders to compute the actual list of
270
+ * {@link AggregationOperation operations}.
271
+ *
272
+ * @param sourcePipeline must not be {@literal null}.
273
+ * @param accessor must not be {@literal null}.
274
+ * @return the parsed aggregation pipeline.
275
+ * @since 3.4
276
+ */
277
+ protected List <AggregationOperation > parseAggregationPipeline (String [] sourcePipeline ,
278
+ ConvertingParameterAccessor accessor ) {
279
+
280
+ List <AggregationOperation > stages = new ArrayList <>(sourcePipeline .length );
281
+ for (String source : sourcePipeline ) {
282
+ stages .add (computePipelineStage (source , accessor ));
283
+ }
284
+ return stages ;
285
+ }
286
+
287
+ private AggregationOperation computePipelineStage (String source , ConvertingParameterAccessor accessor ) {
288
+ return ctx -> ctx .getMappedObject (bindParameters (source , accessor ), getQueryMethod ().getDomainClass ());
289
+ }
290
+
291
+ protected Document decode (String source , ParameterBindingContext bindingContext ) {
292
+ return getParameterBindingCodec ().decode (source , bindingContext );
293
+ }
294
+
295
+ private Document bindParameters (String source , ConvertingParameterAccessor accessor ) {
296
+ return decode (source , prepareBindingContext (source , accessor ));
297
+ }
298
+
299
+ /**
300
+ * Create the {@link ParameterBindingContext binding context} used for SpEL evaluation.
301
+ *
302
+ * @param source the JSON source.
303
+ * @param accessor value provider for parameter binding.
304
+ * @return never {@literal null}.
305
+ * @since 3.4
306
+ */
307
+ protected ParameterBindingContext prepareBindingContext (String source , ConvertingParameterAccessor accessor ) {
308
+
309
+ ExpressionDependencies dependencies = getParameterBindingCodec ().captureExpressionDependencies (source ,
310
+ accessor ::getBindableValue , expressionParser );
311
+
312
+ SpELExpressionEvaluator evaluator = getSpELExpressionEvaluatorFor (dependencies , accessor );
313
+ return new ParameterBindingContext (accessor ::getBindableValue , evaluator );
314
+ }
315
+
316
+ /**
317
+ * Obtain the {@link ParameterBindingDocumentCodec} used for parsing JSON expressions.
318
+ *
319
+ * @return never {@literal null}.
320
+ * @since 3.4
321
+ */
322
+ protected ParameterBindingDocumentCodec getParameterBindingCodec () {
323
+ return codec .get ();
324
+ }
325
+
220
326
/**
221
327
* Obtain a the {@link EvaluationContext} suitable to evaluate expressions backed by the given dependencies.
222
328
*
@@ -278,53 +384,4 @@ protected CodecRegistry getCodecRegistry() {
278
384
* @since 2.0.4
279
385
*/
280
386
protected abstract boolean isLimiting ();
281
-
282
- /**
283
- * {@link MongoQueryExecution} for collection returning find and update queries.
284
- *
285
- * @author Thomas Darimont
286
- */
287
- final class UpdatingCollectionExecution implements MongoQueryExecution {
288
-
289
- private final Pageable pageable ;
290
- private final Update update ;
291
-
292
- UpdatingCollectionExecution (Pageable pageable , Update update ) {
293
- this .pageable = pageable ;
294
- this .update = update ;
295
- }
296
-
297
- @ Override
298
- public Object execute (Query query ) {
299
-
300
- MongoEntityMetadata <?> metadata = method .getEntityInformation ();
301
- return operations .findAndModify (query .with (pageable ), update , metadata .getJavaType (),
302
- metadata .getCollectionName ());
303
- }
304
- }
305
-
306
- /**
307
- * {@link MongoQueryExecution} to return a single entity with update.
308
- *
309
- * @author Thomas Darimont
310
- */
311
- final class UpdatingSingleEntityExecution implements MongoQueryExecution {
312
-
313
- private final Update update ;
314
-
315
- private UpdatingSingleEntityExecution (Update update ) {
316
- this .update = update ;
317
- }
318
-
319
- /*
320
- * (non-Javadoc)
321
- * @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.core.query.Query)
322
- */
323
- @ Override
324
- public Object execute (Query query ) {
325
-
326
- MongoEntityMetadata <?> metadata = method .getEntityInformation ();
327
- return operations .findAndModify (query .limit (1 ), update , metadata .getJavaType (), metadata .getCollectionName ());
328
- }
329
- }
330
387
}
0 commit comments