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
}
@@ -138,7 +156,17 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
138
156
139
157
if (isDeleteQuery ()) {
140
158
return new DeleteExecution (operations , method );
141
- } else if (method .isGeoNearQuery () && method .isPageQuery ()) {
159
+ }
160
+
161
+ if (method .isModifyingQuery ()) {
162
+ if (isLimiting ()) {
163
+ throw new IllegalStateException (
164
+ String .format ("Update method must not be limiting. Offending method: %s" , method ));
165
+ }
166
+ return new UpdateExecution (executableUpdate , method , () -> createUpdate (accessor ), accessor );
167
+ }
168
+
169
+ if (method .isGeoNearQuery () && method .isPageQuery ()) {
142
170
return new PagingGeoNearExecution (operation , method , accessor , this );
143
171
} else if (method .isGeoNearQuery ()) {
144
172
return new GeoNearExecution (operation , method , accessor );
@@ -147,11 +175,6 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
147
175
} else if (method .isStreamQuery ()) {
148
176
return q -> operation .matching (q ).stream ();
149
177
} else if (method .isCollectionQuery ()) {
150
-
151
- if (method .isModifyingQuery ()) {
152
- return q -> new UpdatingCollectionExecution (accessor .getPageable (), accessor .getUpdate ()).execute (q );
153
- }
154
-
155
178
return q -> operation .matching (q .with (accessor .getPageable ()).with (accessor .getSort ())).all ();
156
179
} else if (method .isPageQuery ()) {
157
180
return new PagedExecution (operation , accessor .getPageable ());
@@ -161,11 +184,6 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
161
184
return q -> operation .matching (q ).exists ();
162
185
} else {
163
186
return q -> {
164
-
165
- if (method .isModifyingQuery ()) {
166
- return new UpdatingSingleEntityExecution (accessor .getUpdate ()).execute (q );
167
- }
168
-
169
187
TerminatingFind <?> find = operation .matching (q );
170
188
return isLimiting () ? find .firstValue () : find .oneValue ();
171
189
};
@@ -225,6 +243,94 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) {
225
243
return applyQueryMetaAttributesWhenPresent (createQuery (accessor ));
226
244
}
227
245
246
+ /**
247
+ * Retrieves the {@link UpdateDefinition update} from the given
248
+ * {@link org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate() accessor} or creates
249
+ * one via by parsing the annotated statement extracted from {@link Update}.
250
+ *
251
+ * @param accessor never {@literal null}.
252
+ * @return the computed {@link UpdateDefinition}.
253
+ * @throws IllegalStateException if no update could be found.
254
+ * @since 3.4
255
+ */
256
+ protected UpdateDefinition createUpdate (ConvertingParameterAccessor accessor ) {
257
+
258
+ if (accessor .getUpdate () != null ) {
259
+ return accessor .getUpdate ();
260
+ }
261
+
262
+ if (method .hasAnnotatedUpdate ()) {
263
+
264
+ Update updateSource = method .getUpdateSource ();
265
+ if (StringUtils .hasText (updateSource .update ())) {
266
+ return new BasicUpdate (bindParameters (updateSource .update (), accessor ));
267
+ }
268
+ if (!ObjectUtils .isEmpty (updateSource .pipeline ())) {
269
+ return AggregationUpdate .from (parseAggregationPipeline (updateSource .pipeline (), accessor ));
270
+ }
271
+ }
272
+
273
+ throw new IllegalStateException (String .format ("No Update provided for method %s." , method ));
274
+ }
275
+
276
+ /**
277
+ * Parse the given aggregation pipeline stages applying values to placeholders to compute the actual list of
278
+ * {@link AggregationOperation operations}.
279
+ *
280
+ * @param sourcePipeline must not be {@literal null}.
281
+ * @param accessor must not be {@literal null}.
282
+ * @return the parsed aggregation pipeline.
283
+ * @since 3.4
284
+ */
285
+ protected List <AggregationOperation > parseAggregationPipeline (String [] sourcePipeline ,
286
+ ConvertingParameterAccessor accessor ) {
287
+
288
+ List <AggregationOperation > stages = new ArrayList <>(sourcePipeline .length );
289
+ for (String source : sourcePipeline ) {
290
+ stages .add (computePipelineStage (source , accessor ));
291
+ }
292
+ return stages ;
293
+ }
294
+
295
+ private AggregationOperation computePipelineStage (String source , ConvertingParameterAccessor accessor ) {
296
+ return ctx -> ctx .getMappedObject (bindParameters (source , accessor ), getQueryMethod ().getDomainClass ());
297
+ }
298
+
299
+ protected Document decode (String source , ParameterBindingContext bindingContext ) {
300
+ return getParameterBindingCodec ().decode (source , bindingContext );
301
+ }
302
+
303
+ private Document bindParameters (String source , ConvertingParameterAccessor accessor ) {
304
+ return decode (source , prepareBindingContext (source , accessor ));
305
+ }
306
+
307
+ /**
308
+ * Create the {@link ParameterBindingContext binding context} used for SpEL evaluation.
309
+ *
310
+ * @param source the JSON source.
311
+ * @param accessor value provider for parameter binding.
312
+ * @return never {@literal null}.
313
+ * @since 3.4
314
+ */
315
+ protected ParameterBindingContext prepareBindingContext (String source , ConvertingParameterAccessor accessor ) {
316
+
317
+ ExpressionDependencies dependencies = getParameterBindingCodec ().captureExpressionDependencies (source ,
318
+ accessor ::getBindableValue , expressionParser );
319
+
320
+ SpELExpressionEvaluator evaluator = getSpELExpressionEvaluatorFor (dependencies , accessor );
321
+ return new ParameterBindingContext (accessor ::getBindableValue , evaluator );
322
+ }
323
+
324
+ /**
325
+ * Obtain the {@link ParameterBindingDocumentCodec} used for parsing JSON expressions.
326
+ *
327
+ * @return never {@literal null}.
328
+ * @since 3.4
329
+ */
330
+ protected ParameterBindingDocumentCodec getParameterBindingCodec () {
331
+ return codec .get ();
332
+ }
333
+
228
334
/**
229
335
* Obtain a the {@link EvaluationContext} suitable to evaluate expressions backed by the given dependencies.
230
336
*
@@ -286,53 +392,4 @@ protected CodecRegistry getCodecRegistry() {
286
392
* @since 2.0.4
287
393
*/
288
394
protected abstract boolean isLimiting ();
289
-
290
- /**
291
- * {@link MongoQueryExecution} for collection returning find and update queries.
292
- *
293
- * @author Thomas Darimont
294
- */
295
- final class UpdatingCollectionExecution implements MongoQueryExecution {
296
-
297
- private final Pageable pageable ;
298
- private final Update update ;
299
-
300
- UpdatingCollectionExecution (Pageable pageable , Update update ) {
301
- this .pageable = pageable ;
302
- this .update = update ;
303
- }
304
-
305
- @ Override
306
- public Object execute (Query query ) {
307
-
308
- MongoEntityMetadata <?> metadata = method .getEntityInformation ();
309
- return operations .findAndModify (query .with (pageable ), update , metadata .getJavaType (),
310
- metadata .getCollectionName ());
311
- }
312
- }
313
-
314
- /**
315
- * {@link MongoQueryExecution} to return a single entity with update.
316
- *
317
- * @author Thomas Darimont
318
- */
319
- final class UpdatingSingleEntityExecution implements MongoQueryExecution {
320
-
321
- private final Update update ;
322
-
323
- private UpdatingSingleEntityExecution (Update update ) {
324
- this .update = update ;
325
- }
326
-
327
- /*
328
- * (non-Javadoc)
329
- * @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.core.query.Query)
330
- */
331
- @ Override
332
- public Object execute (Query query ) {
333
-
334
- MongoEntityMetadata <?> metadata = method .getEntityInformation ();
335
- return operations .findAndModify (query .limit (1 ), update , metadata .getJavaType (), metadata .getCollectionName ());
336
- }
337
- }
338
395
}
0 commit comments